usb.rb 27.1 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

76
class UpgradeNotSupported < StandardError
77
78
end

79
def usb_install_helper(name)
anonym's avatar
anonym committed
80
  @screen.wait('USBTailsLogo.png', 10)
81
82
  if @screen.exists("USBCannotUpgrade.png")
    raise UpgradeNotSupported
83
  end
84
85
86
87
88
89
90
91
92
  begin
    @screen.wait_and_click('USBCreateLiveUSB.png', 10)
    @screen.wait('USBCreateLiveUSBConfirmWindow.png', 10)
    @screen.wait_and_click('USBCreateLiveUSBConfirmYes.png', 10)
    @screen.wait('USBInstallationComplete.png', 30*60)
  rescue FindFailed => e
    debug_log("Tails Installer debug log:\n" + $vm.file_content('/tmp/tails-installer-*'))
    raise e
  end
93
94
end

95
When /^I start Tails Installer$/ do
96
  step 'I run "export DEBUG=1 ; tails-installer-launcher" in GNOME Terminal'
97
98
99
  @screen.wait('USBCloneAndInstall.png', 30)
end

100
101
When /^I start Tails Installer in "([^"]+)" mode$/ do |mode|
  step 'I start Tails Installer'
102
103
104
105
106
107
108
109
110
111
  case mode
  when 'Clone & Install'
    @screen.wait_and_click('USBCloneAndInstall.png', 10)
  when 'Clone & Upgrade'
    @screen.wait_and_click('USBCloneAndUpgrade.png', 10)
  when 'Upgrade from ISO'
    @screen.wait_and_click('USBUpgradeFromISO.png', 10)
  else
    raise "Unsupported mode '#{mode}'"
  end
112
113
end

114
115
Then /^Tails Installer detects that a device is too small$/ do
  @screen.wait('TailsInstallerTooSmallDevice.png', 10)
116
117
end

118
When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
119
  step 'I start Tails Installer in "Clone & Install" mode'
120
121
122
123
  usb_install_helper(name)
end

When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
124
  step 'I start Tails Installer in "Clone & Upgrade" mode'
125
126
127
  usb_install_helper(name)
end

128
129
130
When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
  begin
    step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
131
  rescue UpgradeNotSupported
132
133
134
135
136
137
138
139
140
    # this is what we expect
  else
    raise "The USB installer should not succeed"
  end
end

When /^I try to "Upgrade from ISO" USB drive "([^"]+)"$/ do |name|
  begin
    step "I do a \"Upgrade from ISO\" on USB drive \"#{name}\""
141
  rescue UpgradeNotSupported
142
143
144
145
146
147
    # this is what we expect
  else
    raise "The USB installer should not succeed"
  end
end

Tails developers's avatar
Tails developers committed
148
When /^I am suggested to do a "Clone & Install"$/ do
149
  @screen.find("USBCannotUpgrade.png")
150
151
end

152
153
When /^I am told that the destination device cannot be upgraded$/ do
  @screen.find("USBCannotUpgrade.png")
154
155
end

156
Given /^I setup a filesystem share containing the Tails ISO$/ do
anonym's avatar
anonym committed
157
158
  shared_iso_dir_on_host = "#{$config["TMPDIR"]}/shared_iso_dir"
  @shared_iso_dir_on_guest = "/tmp/shared_iso_dir"
159
160
161
  FileUtils.mkdir_p(shared_iso_dir_on_host)
  FileUtils.cp(TAILS_ISO, shared_iso_dir_on_host)
  add_after_scenario_hook { FileUtils.rm_r(shared_iso_dir_on_host) }
162
  $vm.add_share(shared_iso_dir_on_host, @shared_iso_dir_on_guest)
163
164
end

165
When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
166
  step 'I start Tails Installer in "Upgrade from ISO" mode'
167
168
  @screen.wait('USBUseLiveSystemISO.png', 10)
  match = @screen.find('USBUseLiveSystemISO.png')
169
  @screen.click(match.getCenter.offset(0, match.h*2))
170
  @screen.wait('USBSelectISO.png', 10)
171
  @screen.wait_and_click('GnomeFileDiagHome.png', 10)
172
173
  @screen.type("l", Sikuli::KeyModifier.CTRL)
  @screen.wait('GnomeFileDiagTypeFilename.png', 10)
174
  iso = "#{@shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
175
176
  @screen.type(iso)
  @screen.wait_and_click('GnomeFileDiagOpenButton.png', 10)
177
178
179
180
181
  usb_install_helper(name)
end

Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
182
183
184
185
186
187
  # 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)
188
  end
189
  @screen.wait_and_click('PersistenceWizardSave.png', 10)
intrigeri's avatar
intrigeri committed
190
  @screen.wait('PersistenceWizardDone.png', 60)
191
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
192
193
end

194
When /^I disable the first persistence preset$/ do
195
  step 'I start "Configure persistent volume" via the GNOME "Tails" applications menu'
196
197
198
199
200
201
202
  @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

203
Given /^I create a persistent partition$/ do
204
  step 'I start "Configure persistent volume" via the GNOME "Tails" applications menu'
intrigeri's avatar
intrigeri committed
205
  @screen.wait('PersistenceWizardStart.png', 60)
206
  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
Tails developers's avatar
Tails developers committed
207
  @screen.wait('PersistenceWizardPresets.png', 300)
208
209
210
  step "I enable all persistence presets"
end

211
def check_disk_integrity(name, dev, scheme)
212
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
213
214
215
216
217
218
219
  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

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

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

245
  target_root = "/mnt/new"
246
  $vm.execute("mkdir -p #{target_root}")
247
  $vm.execute("mount #{part_dev} #{target_root}")
248

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

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

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

269
270
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
271
272
end

273
274
275
276
277
278
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|
279
  iso = "#{@shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
280
  iso_root = "/mnt/iso"
281
282
  $vm.execute("mkdir -p #{iso_root}")
  $vm.execute("mount -o loop #{iso} #{iso_root}")
Tails developers's avatar
Tails developers committed
283
  tails_is_installed_helper(target_name, iso_root, "isolinux")
284
  $vm.execute("umount #{iso_root}")
285
286
end

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

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

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

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

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

329
330
331
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
332
333
end

334
Given /^I enable persistence$/ do
335
336
  @screen.wait('TailsGreeterPersistence.png', 10)
  @screen.type(Sikuli::Key.SPACE)
337
338
  @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
  match = @screen.find('TailsGreeterPersistencePassphrase.png')
339
  @screen.click(match.getCenter.offset(match.w*2, match.h/2))
340
  @screen.type(@persistence_password)
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
385
Given /^I enable read-only persistence$/ do
  step "I enable persistence"
386
  @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
387
388
end

389
def boot_device
390
391
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
392
393
  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
394
395
396
  return boot_dev
end

anonym's avatar
anonym committed
397
def device_info(dev)
398
399
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
anonym's avatar
anonym committed
400
401
402
403
404
405
  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']
406
407
end

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

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

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

455
  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
intrigeri's avatar
intrigeri committed
456
  assert(info.match("^    HintSystem: +true$"),
457
         "Boot device '#{super_boot_dev}' is not system internal for udisks")
458
459
end

460
Then /^all persistent filesystems have safe access rights$/ do
461
  persistent_volumes_mountpoints.each do |mountpoint|
462
463
464
    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
465
466
467
    assert_equal("root", fs_owner)
    assert_equal("root", fs_group)
    assert_equal('775', fs_perms)
468
469
470
  end
end

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

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

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

528
529
530
531
532
533
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

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

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

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

570
571
572
573
574
575
576
577
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

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

When /^I delete the persistent partition$/ do
617
  step 'I start "Delete persistent volume" via the GNOME "Tails" applications menu'
intrigeri's avatar
intrigeri committed
618
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
619
620
621
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
622
623

Then /^Tails has started in UEFI mode$/ do
624
  assert($vm.execute("test -d /sys/firmware/efi").success?,
625
626
         "/sys/firmware/efi does not exist")
 end
627
628

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

632
Then /^a suitable USB device is (?:still )?not found$/ do
633
  @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30)
634
635
end

636
Then /^the "(?:[^"]+)" USB drive is selected$/ do
637
638
639
640
641
642
  @screen.wait("TailsInstallerQEMUHardDisk.png", 30)
end

Then /^no USB drive is selected$/ do
  @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30)
end
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
716
717
718
719
720

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|
721
  recovery_proc = Proc.new do
722
    recover_from_upgrader_failure
723
724
725
726
727
728
729
  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
730
731
732
733
734
735
end

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

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