usb.rb 26.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
# Returns a hash that for each preset the running Tails is aware of
# maps the source to the destination.
def get_persistence_presets(skip_links = false)
  # Perl script that prints all persistence presets (one per line) on
  # the form: <mount_point>:<comma-separated-list-of-options>
  script = <<-EOF
  use strict;
  use warnings FATAL => "all";
  use Tails::Persistence::Configuration::Presets;
Tails developers's avatar
Tails developers committed
10
11
  foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) {
    say $preset->destination, ":", join(",", @{$preset->options});
12
  }
13
14
15
16
EOF
  # VMCommand:s cannot handle newlines, and they're irrelevant in the
  # above perl script any way
  script.delete!("\n")
17
  presets = $vm.execute_successfully("perl -E '#{script}'").stdout.chomp.split("\n")
18
19
20
21
22
23
24
25
26
  assert presets.size >= 10, "Got #{presets.size} persistence presets, " +
                             "which is too few"
  persistence_mapping = Hash.new
  for line in presets
    destination, options_str = line.split(":")
    options = options_str.split(",")
    is_link = options.include? "link"
    next if is_link and skip_links
    source_str = options.find { |option| /^source=/.match option }
27
28
29
30
31
32
33
    # If no source is given as an option, live-boot's persistence
    # feature defaults to the destination minus the initial "/".
    if source_str.nil?
      source = destination.partition("/").last
    else
      source = source_str.split("=")[1]
    end
34
35
36
37
38
39
40
41
42
43
44
    persistence_mapping[source] = destination
  end
  return persistence_mapping
end

def persistent_dirs
  get_persistence_presets
end

def persistent_mounts
  get_persistence_presets(true)
45
46
end

47
def persistent_volumes_mountpoints
48
  $vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
49
50
end

51
52
53
54
55
56
57
58
def recover_from_upgrader_failure
    $vm.execute('killall tails-upgrade-frontend tails-upgrade-frontend-wrapper zenity')
    # Remove unnecessary sleep for retry
    $vm.execute_successfully('sed -i "/^sleep 30$/d" ' +
                             '/usr/local/bin/tails-upgrade-frontend-wrapper')
    $vm.spawn('tails-upgrade-frontend-wrapper', user: LIVE_USER)
end

59
Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
60
  $vm.storage.clone_to_new_disk(from, to)
61
62
63
end

Given /^I unplug USB drive "([^"]+)"$/ do |name|
64
  $vm.unplug_drive(name)
65
66
end

67
Given /^the computer is set to boot from the old Tails DVD$/ do
68
  $vm.set_cdrom_boot(OLD_TAILS_ISO)
69
70
end

71
Given /^the computer is set to boot in UEFI mode$/ do
72
  $vm.set_os_loader('UEFI')
73
74
75
  @os_loader = 'UEFI'
end

anonym's avatar
anonym committed
76
def tails_installer_selected_device
77
  @installer.child('Target USB stick:', roleName: 'label').parent
anonym's avatar
anonym committed
78
79
80
81
82
    .child('', roleName: 'combo box', recursive: false).name
end

def tails_installer_is_device_selected?(name)
  device = $vm.disk_dev(name)
83
  tails_installer_selected_device[/\(#{device}\d*\)$/]
anonym's avatar
anonym committed
84
85
end

anonym's avatar
anonym committed
86
def tails_installer_match_status(pattern)
anonym's avatar
anonym committed
87
  @installer.child('', roleName: 'text').text[pattern]
anonym's avatar
anonym committed
88
89
end

90
class UpgradeNotSupported < StandardError
91
92
end

93
94
When /^I start Tails Installer$/ do
  step 'I run "export DEBUG=1 ; /usr/bin/tails-installer-launcher" in GNOME Terminal'
95
  @installer = Dogtail::Application.new('tails-installer')
96
  @installer.child('Tails Installer', roleName: 'frame')
97
98
99
100
101
  # Sometimes Dogtail will find the Installer and click its window
  # before it is shown (searchShowingOnly is not perfect) which
  # generally means clicking somewhere on the Terminal => the click is
  # lost *and* the installer does not go to the foreground. So let's
  # wait a bit extra.
102
103
  sleep 3
  $vm.focus_window('Tails Installer')
104
105
end

106
When /^I am told that the destination device (.*)$/ do |status|
107
  try_for(10) do
108
    tails_installer_match_status(status)
109
110
111
  end
end

anonym's avatar
anonym committed
112
When /^I am suggested to do a "Install by cloning"$/ do
113
114
115
116
117
118
  try_for(10) do
    tails_installer_match_status(
      /You should instead use "Install by cloning" to upgrade Tails/
    )
  end
end
119
120

Then /^a suitable USB device is (?:still )?not found$/ do
121
122
123
  @installer.child(
    'No device suitable to install Tails could be found', roleName: 'label'
  )
124
125
126
127
128
129
130
131
132
133
134
135
end

Then /^(no|the "([^"]+)") USB drive is selected$/ do |mode, name|
  try_for(30) do
    if mode == 'no'
      tails_installer_selected_device == ''
    else
      tails_installer_is_device_selected?(name)
    end
  end
end

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
When /^I (install|upgrade) Tails (?:to|on) USB drive "([^"]+)" (by cloning|from an ISO)$/ do |action, name, source|
  step "I start Tails Installer"
  if tails_installer_match_status(/It is impossible to upgrade the device .+ #{$vm.disk_dev(name)}\d* /)
    raise UpgradeNotSupported
  end
  assert(tails_installer_is_device_selected?(name))
  if source == 'from an ISO'
    iso_radio = @installer.child('Use a downloaded Tails ISO image',
                                 roleName: 'radio button')
    iso_radio.click
    iso_radio.parent.button('(None)').click
    file_chooser = @installer.child('Select a File', roleName: 'file chooser')
    @screen.type("l", Sikuli::KeyModifier.CTRL)
    # The only visible text element will be the path entry
    file_chooser.child(roleName: 'text').typeText(@iso_path + '\n')
    file_chooser.button('Open').click
  end
  begin
    @installer.button(action.capitalize).click
    @installer.child('Question', roleName: 'alert').button('Yes').click
    try_for(30*60) do
      @installer
        .child('Information', roleName: 'alert')
        .child('Installation complete!', roleName: 'label')
      true
    end
  rescue Exception => e
    path = $vm.execute_successfully('ls -1 /tmp/tails-installer-*').stdout.chomp
    debug_log("Tails Installer debug log:\n" + $vm.file_content(path))
    raise e
  end
167
168
end

169
When /^I fail to (.*)$/ do |step|
170
  begin
171
    step "I #{step}"
172
  rescue UpgradeNotSupported
173
174
175
176
177
178
    # this is what we expect
  else
    raise "The USB installer should not succeed"
  end
end

179
180
181
182
183
Given /^I plug and mount a USB drive containing the Tails ISO$/ do
  iso_dir = share_host_files(TAILS_ISO)
  @iso_path = "#{iso_dir}/#{File.basename(TAILS_ISO)}"
end

184
185
Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
186
187
188
189
190
191
  # Select the "Persistent" folder preset, which is checked by default.
  @screen.type(Sikuli::Key.TAB)
  # Check all non-default persistence presets, i.e. all *after* the
  # "Persistent" folder, which are unchecked by default.
  (persistent_dirs.size - 1).times do
    @screen.type(Sikuli::Key.TAB + Sikuli::Key.SPACE)
192
  end
193
  @screen.wait_and_click('PersistenceWizardSave.png', 10)
intrigeri's avatar
intrigeri committed
194
  @screen.wait('PersistenceWizardDone.png', 60)
195
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
196
197
end

198
When /^I disable the first persistence preset$/ do
anonym's avatar
anonym committed
199
  step 'I start "Configure persistent volume" via GNOME Activities Overview'
200
201
202
203
204
205
206
  @screen.wait('PersistenceWizardPresets.png', 300)
  @screen.type(Sikuli::Key.SPACE)
  @screen.wait_and_click('PersistenceWizardSave.png', 10)
  @screen.wait('PersistenceWizardDone.png', 30)
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
end

207
Given /^I create a persistent partition$/ do
anonym's avatar
anonym committed
208
  step 'I start "Configure persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
209
  @screen.wait('PersistenceWizardStart.png', 60)
210
  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
Tails developers's avatar
Tails developers committed
211
  @screen.wait('PersistenceWizardPresets.png', 300)
212
213
214
  step "I enable all persistence presets"
end

215
def check_disk_integrity(name, dev, scheme)
216
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
217
218
219
220
221
222
223
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.PartitionTable:\n")
  dev_info = info_split[0]
  part_table_info = info_split[1]
  assert(part_table_info.match("^    Type: +#{scheme}$"),
         "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
end

224
def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
225
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
226
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.Partition:\n")
227
228
  dev_info = info_split[0]
  part_info = info_split[1]
229
  assert(dev_info.match("^    IdUsage: +#{usage}$"),
230
         "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
231
232
233
  assert(dev_info.match("^    IdType: +#{fs_type}$"),
         "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
  assert(part_info.match("^    Name: +#{part_label}$"),
234
         "Unexpected partition label on USB drive '#{name}', '#{dev}'")
235
236
237
238
  if part_type
    assert(part_info.match("^    Type: +#{part_type}$"),
           "Unexpected partition type on USB drive '#{name}', '#{dev}'")
  end
239
240
end

241
def tails_is_installed_helper(name, tails_root, loader)
242
  disk_dev = $vm.disk_dev(name)
243
244
  part_dev = disk_dev + "1"
  check_disk_integrity(name, disk_dev, "gpt")
245
246
247
  check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails",
                       # EFI System Partition
                       'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
248

249
  target_root = "/mnt/new"
250
  $vm.execute("mkdir -p #{target_root}")
251
  $vm.execute("mount #{part_dev} #{target_root}")
252

253
  c = $vm.execute("diff -qr '#{tails_root}/live' '#{target_root}/live'")
254
  assert(c.success?,
255
         "USB drive '#{name}' has differences in /live:\n#{c.stdout}\n#{c.stderr}")
256

257
  syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
258
  # We deal with these files separately
259
  ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"]
260
  for f in syslinux_files - ignores do
261
    c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
262
                    "'#{target_root}/syslinux/#{f}'")
263
264
265
266
267
    assert(c.success?, "USB drive '#{name}' has differences in " +
           "'/syslinux/#{f}'")
  end

  # The main .cfg is named differently vs isolinux
268
  c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{loader}.cfg' " +
269
                  "'#{target_root}/syslinux/syslinux.cfg'")
270
271
272
  assert(c.success?, "USB drive '#{name}' has differences in " +
         "'/syslinux/syslinux.cfg'")

273
274
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
275
276
end

277
278
279
280
281
282
283
Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name|
  loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
  tails_is_installed_helper(target_name, "/lib/live/mount/medium", loader)
end

Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
  iso_root = "/mnt/iso"
284
  $vm.execute("mkdir -p #{iso_root}")
285
  $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
Tails developers's avatar
Tails developers committed
286
  tails_is_installed_helper(target_name, iso_root, "isolinux")
287
  $vm.execute("umount #{iso_root}")
288
289
end

290
Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
291
292
  data_part_dev = $vm.disk_dev(name) + "2"
  assert(!$vm.execute("test -b #{data_part_dev}").success?,
293
294
295
         "USB drive #{name} has a partition '#{data_part_dev}'")
end

296
Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name|
297
  dev = $vm.disk_dev(name) + "2"
298
  check_part_integrity(name, dev, "crypto", "crypto_LUKS", "TailsData")
299

300
301
  # The LUKS container may already be opened, e.g. by udisks after
  # we've run tails-persistence-setup.
302
  c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
303
304
  if c.success?
    for candidate in c.stdout.split("\n")
305
      luks_info = $vm.execute("cryptsetup status '#{candidate}'")
306
307
308
309
310
311
312
      if luks_info.success? and luks_info.stdout.match("^\s+device:\s+#{dev}$")
        luks_dev = "/dev/mapper/#{candidate}"
        break
      end
    end
  end
  if luks_dev.nil?
313
314
    c = $vm.execute("echo #{@persistence_password} | " +
                    "cryptsetup luksOpen #{dev} #{name}")
315
316
317
    assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
    luks_dev = "/dev/mapper/#{name}"
  end
318
319

  # Adapting check_part_integrity() seems like a bad idea so here goes
320
  info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout
321
322
323
324
  assert info.match("^    CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$")
  assert info.match("^    IdUsage: +filesystem$")
  assert info.match("^    IdType: +ext[34]$")
  assert info.match("^    IdLabel: +TailsData$")
325
326

  mount_dir = "/mnt/#{name}"
327
  $vm.execute("mkdir -p #{mount_dir}")
328
  c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
329
  assert(c.success?,
Tails developers's avatar
Tails developers committed
330
         "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
331

332
333
334
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
335
336
end

337
Given /^I enable persistence$/ do
338
339
340
  @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 10)
  @screen.type(@persistence_password + Sikuli::Key.ENTER)
  @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
341
342
end

343
344
def tails_persistence_enabled?
  persistence_state_file = "/var/lib/live/config/tails.persistence"
345
346
  return $vm.execute("test -e '#{persistence_state_file}'").success? &&
         $vm.execute(". '#{persistence_state_file}' && " +
347
                     'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
348
349
end

350
351
Given /^all persistence presets(| from the old Tails version)(| but the first one) are enabled$/ do |old_tails, except_first|
  assert(old_tails.empty? || except_first.empty?, "Unsupported case.")
352
353
354
  try_for(120, :msg => "Persistence is disabled") do
    tails_persistence_enabled?
  end
355
  unexpected_mounts = Array.new
356
  # Check that all persistent directories are mounted
357
358
  if old_tails.empty?
    expected_mounts = persistent_mounts
359
360
361
362
363
364
    if ! except_first.empty?
      first_expected_mount_source      = expected_mounts.keys[0]
      first_expected_mount_destination = expected_mounts[first_expected_mount_source]
      expected_mounts.delete(first_expected_mount_source)
      unexpected_mounts = [first_expected_mount_destination]
    end
365
  else
366
    assert_not_nil($remembered_persistence_mounts)
367
    expected_mounts = $remembered_persistence_mounts
368
  end
369
  mount = $vm.execute("mount").stdout.chomp
370
  for _, dir in expected_mounts do
371
372
373
    assert(mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is not mounted")
  end
374
375
376
377
  for dir in unexpected_mounts do
    assert(! mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is mounted")
  end
378
379
end

380
381
382
383
Given /^persistence is disabled$/ do
  assert(!tails_persistence_enabled?, "Persistence is enabled")
end

384
def boot_device
385
386
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
387
388
  boot_dev_id = $vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
  boot_dev = $vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
389
390
391
  return boot_dev
end

anonym's avatar
anonym committed
392
def device_info(dev)
393
394
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
anonym's avatar
anonym committed
395
396
397
398
399
400
  info = $vm.execute("udevadm info --query=property --name='#{dev}'").stdout.chomp
  info.split("\n").map { |e| e.split('=') } .to_h
end

def boot_device_type
  device_info(boot_device)['ID_BUS']
401
402
end

403
404
405
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
  bus = bus.downcase
  case bus
406
  when "sata"
407
408
409
410
411
    expected_bus = "ata"
  else
    expected_bus = bus
  end
  assert_equal(expected_bus, boot_device_type)
412
  actual_dev = boot_device
413
414
415
416
417
418
  # The boot partition differs between an using Tails installer and
  # isohybrids. There's also a strange case isohybrids are thought to
  # be booting from the "raw" device, and not a partition of it
  # (#10504).
  expected_devs = ['', '1', '4'].map { |e| $vm.disk_dev(name) + e }
  assert(expected_devs.include?(actual_dev),
419
         "We are running from device #{actual_dev}, but for #{bus} drive " +
420
         "'#{name}' we expected to run from one of #{expected_devs}")
421
422
423
424
425
end

Then /^the boot device has safe access rights$/ do

  super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
426
  devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
427
  assert(devs.size > 0, "Could not determine boot device")
428
  all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
429
  all_users_with_groups = all_users.collect do |user|
430
    groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
431
432
433
    [user, groups]
  end
  for dev in devs do
434
435
436
    dev_owner = $vm.execute("stat -c %U #{dev}").stdout.chomp
    dev_group = $vm.execute("stat -c %G #{dev}").stdout.chomp
    dev_perms = $vm.execute("stat -c %a #{dev}").stdout.chomp
437
    assert_equal("root", dev_owner)
438
439
    assert(dev_group == "disk" || dev_group == "root",
           "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
440
           "'disk' or 'root'.")
441
    assert_equal("660", dev_perms)
442
443
444
445
446
447
448
    for user, groups in all_users_with_groups do
      next if user == "root"
      assert(!(groups.include?(dev_group)),
             "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
             "owns boot device '#{dev}'")
    end
  end
449

450
  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
intrigeri's avatar
intrigeri committed
451
  assert(info.match("^    HintSystem: +true$"),
452
         "Boot device '#{super_boot_dev}' is not system internal for udisks")
453
454
end

455
Then /^all persistent filesystems have safe access rights$/ do
456
  persistent_volumes_mountpoints.each do |mountpoint|
457
458
459
    fs_owner = $vm.execute("stat -c %U #{mountpoint}").stdout.chomp
    fs_group = $vm.execute("stat -c %G #{mountpoint}").stdout.chomp
    fs_perms = $vm.execute("stat -c %a #{mountpoint}").stdout.chomp
460
461
462
    assert_equal("root", fs_owner)
    assert_equal("root", fs_group)
    assert_equal('775', fs_perms)
463
464
465
  end
end

466
Then /^all persistence configuration files have safe access rights$/ do
467
  persistent_volumes_mountpoints.each do |mountpoint|
468
    assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?,
469
           "#{mountpoint}/persistence.conf does not exist, while it should")
470
    assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
471
           "#{mountpoint}/live-persistence.conf does exist, while it should not")
472
    $vm.execute(
473
474
      "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
    ).stdout.chomp.split.each do |f|
475
476
477
      file_owner = $vm.execute("stat -c %U '#{f}'").stdout.chomp
      file_group = $vm.execute("stat -c %G '#{f}'").stdout.chomp
      file_perms = $vm.execute("stat -c %a '#{f}'").stdout.chomp
478
479
480
      assert_equal("tails-persistence-setup", file_owner)
      assert_equal("tails-persistence-setup", file_group)
      assert_equal("600", file_perms)
481
    end
Tails developers's avatar
Tails developers committed
482
  end
483
484
end

485
Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
486
487
488
  if old_tails.empty?
    expected_dirs = persistent_dirs
  else
489
    assert_not_nil($remembered_persistence_dirs)
490
    expected_dirs = $remembered_persistence_dirs
491
  end
492
  persistent_volumes_mountpoints.each do |mountpoint|
493
    expected_dirs.each do |src, dest|
Tails developers's avatar
Tails developers committed
494
      full_src = "#{mountpoint}/#{src}"
495
496
497
      assert_vmcommand_success $vm.execute("test -d #{full_src}")
      dir_perms = $vm.execute_successfully("stat -c %a '#{full_src}'").stdout.chomp
      dir_owner = $vm.execute_successfully("stat -c %U '#{full_src}'").stdout.chomp
498
      if dest.start_with?("/home/#{LIVE_USER}")
499
        expected_perms = "700"
500
        expected_owner = LIVE_USER
501
502
503
504
505
506
507
508
509
510
      else
        expected_perms = "755"
        expected_owner = "root"
      end
      assert_equal(expected_perms, dir_perms,
                   "Persistent source #{full_src} has permission " \
                   "#{dir_perms}, expected #{expected_perms}")
      assert_equal(expected_owner, dir_owner,
                   "Persistent source #{full_src} has owner " \
                   "#{dir_owner}, expected #{expected_owner}")
511
512
513
514
    end
  end
end

515
When /^I write some files expected to persist$/ do
516
  persistent_mounts.each do |_, dir|
517
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
518
    assert($vm.execute("touch #{dir}/XXX_persist", :user => owner).success?,
519
           "Could not create file in persistent directory #{dir}")
520
521
522
  end
end

523
524
525
526
527
528
When /^I write some dotfile expected to persist$/ do
  assert($vm.execute("touch /live/persistence/TailsData_unlocked/dotfiles/.XXX_persist",
                     :user => LIVE_USER).success?,
         "Could not create a file in the dotfiles persistence.")
end

529
When /^I remove some files expected to persist$/ do
530
  persistent_mounts.each do |_, dir|
531
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
532
    assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?,
533
           "Could not remove file in persistent directory #{dir}")
534
535
536
537
  end
end

When /^I write some files not expected to persist$/ do
538
  persistent_mounts.each do |_, dir|
539
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
540
    assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?,
541
           "Could not create file in persistent directory #{dir}")
542
543
544
  end
end

545
When /^I take note of which persistence presets are available$/ do
546
547
  $remembered_persistence_mounts = persistent_mounts
  $remembered_persistence_dirs = persistent_dirs
548
549
550
551
552
553
end

Then /^the expected persistent files(| created with the old Tails version) are present in the filesystem$/ do |old_tails|
  if old_tails.empty?
    expected_mounts = persistent_mounts
  else
554
    assert_not_nil($remembered_persistence_mounts)
555
    expected_mounts = $remembered_persistence_mounts
556
557
  end
  expected_mounts.each do |_, dir|
558
    assert($vm.execute("test -e #{dir}/XXX_persist").success?,
559
           "Could not find expected file in persistent directory #{dir}")
560
    assert(!$vm.execute("test -e #{dir}/XXX_gone").success?,
561
562
563
564
           "Found file that should not have persisted in persistent directory #{dir}")
  end
end

565
566
567
568
569
570
571
572
Then /^the expected persistent dotfile is present in the filesystem$/ do
  expected_dirs = persistent_dirs
  assert($vm.execute("test -L #{expected_dirs['dotfiles']}/.XXX_persist").success?,
         "Could not find expected persistent dotfile link.")
  assert($vm.execute("test -e $(readlink -f #{expected_dirs['dotfiles']}/.XXX_persist)").success?,
           "Could not find expected persistent dotfile link target.")
end

573
Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
574
  assert(!$vm.is_running?)
575
  disk = {
576
    :path => $vm.storage.disk_path(name),
577
    :opts => {
578
      :format => $vm.storage.disk_format(name),
579
580
581
      :readonly => true
    }
  }
582
  $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
583
584
585
586
587
588
589
590
591
    partitions = g.part_list(disk_handle).map do |part_desc|
      disk_handle + part_desc["part_num"].to_s
    end
    partition = partitions.find do |part|
      g.blkid(part)["PART_ENTRY_NAME"] == "TailsData"
    end
    assert_not_nil(partition, "Could not find the 'TailsData' partition " \
                              "on disk '#{disk_handle}'")
    luks_mapping = File.basename(partition) + "_unlocked"
592
    g.luks_open(partition, @persistence_password, luks_mapping)
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
    luks_dev = "/dev/mapper/#{luks_mapping}"
    mount_point = "/"
    g.mount(luks_dev, mount_point)
    assert_not_nil($remembered_persistence_mounts)
    $remembered_persistence_mounts.each do |dir, _|
      # Guestfs::exists may have a bug; if the file exists, 1 is
      # returned, but if it doesn't exist false is returned. It seems
      # the translation of C types into Ruby types is glitchy.
      assert(g.exists("/#{dir}/XXX_persist") == 1,
             "Could not find expected file in persistent directory #{dir}")
      assert(g.exists("/#{dir}/XXX_gone") != 1,
             "Found file that should not have persisted in persistent directory #{dir}")
    end
    g.umount(mount_point)
    g.luks_close(luks_dev)
  end
609
end
610
611

When /^I delete the persistent partition$/ do
anonym's avatar
anonym committed
612
  step 'I start "Delete persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
613
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
614
615
616
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
617
618

Then /^Tails has started in UEFI mode$/ do
619
  assert($vm.execute("test -d /sys/firmware/efi").success?,
620
621
         "/sys/firmware/efi does not exist")
 end
622
623

Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
624
  $vm.storage.disk_mklabel(name, type)
625
end
626

627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
Given /^the file system changes introduced in version (.+) are (not )?present(?: in the (\S+) Browser's chroot)?$/ do |version, not_present, chroot_browser|
  assert_equal('1.1~test', version)
  upgrade_applied = not_present.nil?
  chroot_browser = "#{chroot_browser.downcase}-browser" if chroot_browser
  changes = [
    {
      filesystem: :rootfs,
      path: 'some_new_file',
      status: :added,
      new_content: <<-EOF
Some content
      EOF
    },
    {
      filesystem: :rootfs,
      path: 'etc/amnesia/version',
      status: :modified,
      new_content: <<-EOF
#{version} - 20380119
ffffffffffffffffffffffffffffffffffffffff
live-build: 3.0.5+really+is+2.0.12-0.tails2
live-boot: 4.0.2-1
live-config: 4.0.4-1
      EOF
    },
    {
      filesystem: :rootfs,
      path: 'etc/os-release',
      status: :modified,
      new_content: <<-EOF
TAILS_PRODUCT_NAME="Tails"
TAILS_VERSION_ID="#{version}"
      EOF
    },
    {
      filesystem: :rootfs,
      path: 'usr/share/common-licenses/BSD',
      status: :removed
    },
    {
      filesystem: :medium,
      path: 'utils/linux/syslinux',
      status: :removed
    },
  ]
  changes.each do |change|
    case change[:filesystem]
    when :rootfs
      path = '/'
      path += "var/lib/#{chroot_browser}/chroot/" if chroot_browser
      path += change[:path]
    when :medium
      path = '/lib/live/mount/medium/' + change[:path]
    else
      raise "Unknown filesysten '#{change[:filesystem]}'"
    end
    case change[:status]
    when :removed
      assert_equal(!upgrade_applied, $vm.file_exist?(path))
    when :added
      assert_equal(upgrade_applied, $vm.file_exist?(path))
      if upgrade_applied && change[:new_content]
        assert_equal(change[:new_content], $vm.file_content(path))
      end
    when :modified
      assert($vm.file_exist?(path))
      if upgrade_applied
        assert_not_nil(change[:new_content])
        assert_equal(change[:new_content], $vm.file_content(path))
      end
    else
      raise "Unknown status '#{change[:status]}'"
    end
  end
end

Then /^I am proposed to install an incremental upgrade to version (.+)$/ do |version|
704
  recovery_proc = Proc.new do
705
    recover_from_upgrader_failure
706
707
708
709
710
711
712
  end
  failure_pic = 'TailsUpgraderFailure.png'
  success_pic = "TailsUpgraderUpgradeTo#{version}.png"
  retry_tor(recovery_proc) do
    match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
    assert_equal(success_pic, match)
  end
713
714
715
716
717
718
end

When /^I agree to install the incremental upgrade$/ do
  @screen.click('TailsUpgraderUpgradeNowButton.png')
end

anonym's avatar
anonym committed
719
720
Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
  step 'I agree to install the incremental upgrade'
721
722
723
724
725
726
727
728
729
730
731
  recovery_proc = Proc.new do
    recover_from_upgrader_failure
    step "I am proposed to install an incremental upgrade to version #{version}"
    step 'I agree to install the incremental upgrade'
  end
  failure_pic = 'TailsUpgraderFailure.png'
  success_pic = "TailsUpgraderDone.png"
  retry_tor(recovery_proc) do
    match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
    assert_equal(success_pic, match)
  end
732
end