usb.rb 27 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
77
78
79
80
81
82
def tails_installer_selected_device
  @installer.child('Target Device:', roleName: 'label').parent
    .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
def usb_install_helper(name)
94
  if tails_installer_match_status(/It is impossible to upgrade the device .+ #{$vm.disk_dev(name)}\d* /)
95
    raise UpgradeNotSupported
96
  end
97
  assert(tails_installer_is_device_selected?(name))
98
  begin
99
100
    @installer.button('Install Tails').click
    @installer.child('Question', roleName: 'alert').button('Yes').click
101
102
103
104
105
106
    try_for(30*60) do
      @installer
        .child('Information', roleName: 'alert')
        .child('Installation complete!', roleName: 'label')
      true
    end
107
  rescue FindFailed => e
108
109
    path = $vm.execute_successfully('ls -1 /tmp/tails-installer-*').stdout.chomp
    debug_log("Tails Installer debug log:\n" + $vm.file_content(path))
110
111
    raise e
  end
112
113
end

114
When /^I start Tails Installer in "([^"]+)" mode$/ do |mode|
115
116
  step 'I run "export DEBUG=1 ; tails-installer-launcher" in GNOME Terminal'
  installer_launcher = Dogtail::Application.new('tails-installer-launcher')
117
118
119
120
121
122
                         .child('Tails Installer', roleName: 'frame')
  # Sometimes Dogtail will find the button and click it before it is
  # shown (searchShowingOnly is not perfect) which generally means
  # clicking somewhere on the Terminal => the click is lost *and* the
  # installer does no go to the foreground. So let's wait a bit extra.
  sleep 3
123
  installer_launcher.button(mode).click
124
  @installer = Dogtail::Application.new('tails-installer')
125
  @installer.child('Tails Installer', roleName: 'frame')
126
127
end

128
Then /^Tails Installer detects that a device is too small$/ do
129
  try_for(10) do
130
    tails_installer_match_status(/^The device .* is too small to install Tails/)
131
  end
132
133
end

134
135
136
137
138
139
When /^I am told that the destination device cannot be upgraded$/ do
  try_for(10) do
    tails_installer_match_status(/^It is impossible to upgrade the device/)
  end
end

anonym's avatar
anonym committed
140
When /^I am suggested to do a "Install by cloning"$/ do
141
142
143
144
145
146
  try_for(10) do
    tails_installer_match_status(
      /You should instead use "Install by cloning" to upgrade Tails/
    )
  end
end
147
148

Then /^a suitable USB device is (?:still )?not found$/ do
149
150
151
  @installer.child(
    'No device suitable to install Tails could be found', roleName: 'label'
  )
152
153
154
155
156
157
158
159
160
161
162
163
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

164
165
When /^I "([^"]*)" Tails to USB drive "([^"]+)"$/ do |mode, name|
  step "I start Tails Installer in \"#{mode}\" mode"
166
167
168
  usb_install_helper(name)
end

anonym's avatar
anonym committed
169
When /^I fail to "([^"]*)" Tails to USB drive "([^"]+)"$/ do |mode, name|
170
  begin
171
    step "I \"#{mode}\" Tails to USB drive \"#{name}\""
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
When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
185
  step 'I start Tails Installer in "Upgrade from ISO" mode'
186
187
188
  @installer.child('Use existing Live system ISO:', roleName: 'label')
    .parent.button('(None)').click
  file_chooser = @installer.child('Select a File', roleName: 'file chooser')
189
  @screen.type("l", Sikuli::KeyModifier.CTRL)
190
  # The only visible text element will be the path entry
191
  file_chooser.child(roleName: 'text').text = @iso_path
192
  file_chooser.button('Open').click
193
194
195
196
197
  usb_install_helper(name)
end

Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
198
199
200
201
202
203
  # 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)
204
  end
205
  @screen.wait_and_click('PersistenceWizardSave.png', 10)
intrigeri's avatar
intrigeri committed
206
  @screen.wait('PersistenceWizardDone.png', 60)
207
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
208
209
end

210
When /^I disable the first persistence preset$/ do
211
  step 'I start "Configure persistent volume" via the GNOME "Tails" applications menu'
212
213
214
215
216
217
218
  @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

219
Given /^I create a persistent partition$/ do
220
  step 'I start "Configure persistent volume" via the GNOME "Tails" applications menu'
intrigeri's avatar
intrigeri committed
221
  @screen.wait('PersistenceWizardStart.png', 60)
222
  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
Tails developers's avatar
Tails developers committed
223
  @screen.wait('PersistenceWizardPresets.png', 300)
224
225
226
  step "I enable all persistence presets"
end

227
def check_disk_integrity(name, dev, scheme)
228
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
229
230
231
232
233
234
235
  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

236
def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
237
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
238
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.Partition:\n")
239
240
  dev_info = info_split[0]
  part_info = info_split[1]
241
  assert(dev_info.match("^    IdUsage: +#{usage}$"),
242
         "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
243
244
245
  assert(dev_info.match("^    IdType: +#{fs_type}$"),
         "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
  assert(part_info.match("^    Name: +#{part_label}$"),
246
         "Unexpected partition label on USB drive '#{name}', '#{dev}'")
247
248
249
250
  if part_type
    assert(part_info.match("^    Type: +#{part_type}$"),
           "Unexpected partition type on USB drive '#{name}', '#{dev}'")
  end
251
252
end

253
def tails_is_installed_helper(name, tails_root, loader)
254
  disk_dev = $vm.disk_dev(name)
255
256
  part_dev = disk_dev + "1"
  check_disk_integrity(name, disk_dev, "gpt")
257
258
259
  check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails",
                       # EFI System Partition
                       'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
260

261
  target_root = "/mnt/new"
262
  $vm.execute("mkdir -p #{target_root}")
263
  $vm.execute("mount #{part_dev} #{target_root}")
264

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

269
  syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
270
  # We deal with these files separately
271
  ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"]
272
  for f in syslinux_files - ignores do
273
    c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
274
                    "'#{target_root}/syslinux/#{f}'")
275
276
277
278
279
    assert(c.success?, "USB drive '#{name}' has differences in " +
           "'/syslinux/#{f}'")
  end

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

285
286
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
287
288
end

289
290
291
292
293
294
295
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"
296
  $vm.execute("mkdir -p #{iso_root}")
297
  $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
Tails developers's avatar
Tails developers committed
298
  tails_is_installed_helper(target_name, iso_root, "isolinux")
299
  $vm.execute("umount #{iso_root}")
300
301
end

302
Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
303
304
  data_part_dev = $vm.disk_dev(name) + "2"
  assert(!$vm.execute("test -b #{data_part_dev}").success?,
305
306
307
         "USB drive #{name} has a partition '#{data_part_dev}'")
end

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

312
313
  # The LUKS container may already be opened, e.g. by udisks after
  # we've run tails-persistence-setup.
314
  c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
315
316
  if c.success?
    for candidate in c.stdout.split("\n")
317
      luks_info = $vm.execute("cryptsetup status '#{candidate}'")
318
319
320
321
322
323
324
      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?
325
326
    c = $vm.execute("echo #{@persistence_password} | " +
                    "cryptsetup luksOpen #{dev} #{name}")
327
328
329
    assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
    luks_dev = "/dev/mapper/#{name}"
  end
330
331

  # Adapting check_part_integrity() seems like a bad idea so here goes
332
  info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout
333
334
335
336
  assert info.match("^    CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$")
  assert info.match("^    IdUsage: +filesystem$")
  assert info.match("^    IdType: +ext[34]$")
  assert info.match("^    IdLabel: +TailsData$")
337
338

  mount_dir = "/mnt/#{name}"
339
  $vm.execute("mkdir -p #{mount_dir}")
340
  c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
341
  assert(c.success?,
Tails developers's avatar
Tails developers committed
342
         "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
343

344
345
346
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
347
348
end

349
Given /^I enable persistence$/ do
350
351
352
  @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 10)
  @screen.type(@persistence_password + Sikuli::Key.ENTER)
  @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
353
354
end

355
356
def tails_persistence_enabled?
  persistence_state_file = "/var/lib/live/config/tails.persistence"
357
358
  return $vm.execute("test -e '#{persistence_state_file}'").success? &&
         $vm.execute(". '#{persistence_state_file}' && " +
359
                     'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
360
361
end

362
363
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.")
364
365
366
  try_for(120, :msg => "Persistence is disabled") do
    tails_persistence_enabled?
  end
367
  unexpected_mounts = Array.new
368
  # Check that all persistent directories are mounted
369
370
  if old_tails.empty?
    expected_mounts = persistent_mounts
371
372
373
374
375
376
    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
377
  else
378
    assert_not_nil($remembered_persistence_mounts)
379
    expected_mounts = $remembered_persistence_mounts
380
  end
381
  mount = $vm.execute("mount").stdout.chomp
382
  for _, dir in expected_mounts do
383
384
385
    assert(mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is not mounted")
  end
386
387
388
389
  for dir in unexpected_mounts do
    assert(! mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is mounted")
  end
390
391
end

392
393
394
395
Given /^persistence is disabled$/ do
  assert(!tails_persistence_enabled?, "Persistence is enabled")
end

396
def boot_device
397
398
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
399
400
  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
401
402
403
  return boot_dev
end

anonym's avatar
anonym committed
404
def device_info(dev)
405
406
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
anonym's avatar
anonym committed
407
408
409
410
411
412
  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']
413
414
end

415
416
417
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
  bus = bus.downcase
  case bus
418
  when "sata"
419
420
421
422
423
    expected_bus = "ata"
  else
    expected_bus = bus
  end
  assert_equal(expected_bus, boot_device_type)
424
  actual_dev = boot_device
425
426
427
428
429
430
  # 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),
431
         "We are running from device #{actual_dev}, but for #{bus} drive " +
432
         "'#{name}' we expected to run from one of #{expected_devs}")
433
434
435
436
437
end

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

  super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
438
  devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
439
  assert(devs.size > 0, "Could not determine boot device")
440
  all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
441
  all_users_with_groups = all_users.collect do |user|
442
    groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
443
444
445
    [user, groups]
  end
  for dev in devs do
446
447
448
    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
449
    assert_equal("root", dev_owner)
450
451
    assert(dev_group == "disk" || dev_group == "root",
           "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
452
           "'disk' or 'root'.")
453
    assert_equal("660", dev_perms)
454
455
456
457
458
459
460
    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
461

462
  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
intrigeri's avatar
intrigeri committed
463
  assert(info.match("^    HintSystem: +true$"),
464
         "Boot device '#{super_boot_dev}' is not system internal for udisks")
465
466
end

467
Then /^all persistent filesystems have safe access rights$/ do
468
  persistent_volumes_mountpoints.each do |mountpoint|
469
470
471
    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
472
473
474
    assert_equal("root", fs_owner)
    assert_equal("root", fs_group)
    assert_equal('775', fs_perms)
475
476
477
  end
end

478
Then /^all persistence configuration files have safe access rights$/ do
479
  persistent_volumes_mountpoints.each do |mountpoint|
480
    assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?,
481
           "#{mountpoint}/persistence.conf does not exist, while it should")
482
    assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
483
           "#{mountpoint}/live-persistence.conf does exist, while it should not")
484
    $vm.execute(
485
486
      "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
    ).stdout.chomp.split.each do |f|
487
488
489
      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
490
491
492
      assert_equal("tails-persistence-setup", file_owner)
      assert_equal("tails-persistence-setup", file_group)
      assert_equal("600", file_perms)
493
    end
Tails developers's avatar
Tails developers committed
494
  end
495
496
end

497
Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
498
499
500
  if old_tails.empty?
    expected_dirs = persistent_dirs
  else
501
    assert_not_nil($remembered_persistence_dirs)
502
    expected_dirs = $remembered_persistence_dirs
503
  end
504
  persistent_volumes_mountpoints.each do |mountpoint|
505
    expected_dirs.each do |src, dest|
Tails developers's avatar
Tails developers committed
506
      full_src = "#{mountpoint}/#{src}"
507
508
509
      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
510
      if dest.start_with?("/home/#{LIVE_USER}")
511
        expected_perms = "700"
512
        expected_owner = LIVE_USER
513
514
515
516
517
518
519
520
521
522
      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}")
523
524
525
526
    end
  end
end

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

535
536
537
538
539
540
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

541
When /^I remove some files expected to persist$/ do
542
  persistent_mounts.each do |_, dir|
543
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
544
    assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?,
545
           "Could not remove file in persistent directory #{dir}")
546
547
548
549
  end
end

When /^I write some files not expected to persist$/ do
550
  persistent_mounts.each do |_, dir|
551
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
552
    assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?,
553
           "Could not create file in persistent directory #{dir}")
554
555
556
  end
end

557
When /^I take note of which persistence presets are available$/ do
558
559
  $remembered_persistence_mounts = persistent_mounts
  $remembered_persistence_dirs = persistent_dirs
560
561
562
563
564
565
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
566
    assert_not_nil($remembered_persistence_mounts)
567
    expected_mounts = $remembered_persistence_mounts
568
569
  end
  expected_mounts.each do |_, dir|
570
    assert($vm.execute("test -e #{dir}/XXX_persist").success?,
571
           "Could not find expected file in persistent directory #{dir}")
572
    assert(!$vm.execute("test -e #{dir}/XXX_gone").success?,
573
574
575
576
           "Found file that should not have persisted in persistent directory #{dir}")
  end
end

577
578
579
580
581
582
583
584
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

585
Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
586
  assert(!$vm.is_running?)
587
  disk = {
588
    :path => $vm.storage.disk_path(name),
589
    :opts => {
590
      :format => $vm.storage.disk_format(name),
591
592
593
      :readonly => true
    }
  }
594
  $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
595
596
597
598
599
600
601
602
603
    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"
604
    g.luks_open(partition, @persistence_password, luks_mapping)
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
    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
621
end
622
623

When /^I delete the persistent partition$/ do
624
  step 'I start "Delete persistent volume" via the GNOME "Tails" applications menu'
intrigeri's avatar
intrigeri committed
625
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
626
627
628
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
629
630

Then /^Tails has started in UEFI mode$/ do
631
  assert($vm.execute("test -d /sys/firmware/efi").success?,
632
633
         "/sys/firmware/efi does not exist")
 end
634
635

Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
636
  $vm.storage.disk_mklabel(name, type)
637
end
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
704
705
706
707
708
709
710
711
712
713
714
715
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|
716
  recovery_proc = Proc.new do
717
    recover_from_upgrader_failure
718
719
720
721
722
723
724
  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
725
726
727
728
729
730
end

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

anonym's avatar
anonym committed
731
732
Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
  step 'I agree to install the incremental upgrade'
733
734
735
736
737
738
739
740
741
742
743
  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
744
end