usb.rb 28.5 KB
Newer Older
1 2
# Returns a hash that for each persistence preset the running Tails is aware of,
# for each of the corresponding configuration lines,
3
# maps the source to the destination.
4 5 6
def get_persistence_presets_config(skip_links = false)
  # Perl script that prints all persistence configuration lines (one per line)
  # in the form: <mount_point>:<comma-separated-list-of-options>
7 8 9 10
  script = <<-EOF
  use strict;
  use warnings FATAL => "all";
  use Tails::Persistence::Configuration::Presets;
11 12
  foreach my $atom (Tails::Persistence::Configuration::Presets->new()->atoms) {
    say $atom->destination, ":", join(",", @{$atom->options});
13
  }
14 15 16 17
EOF
  # VMCommand:s cannot handle newlines, and they're irrelevant in the
  # above perl script any way
  script.delete!("\n")
18 19 20 21 22
  presets_configs = $vm.execute_successfully("perl -E '#{script}'")
                       .stdout.chomp.split("\n")
  assert presets_configs.size >= 10,
         "Got #{presets_configs.size} persistence preset configuration lines, " +
         "which is too few"
23
  persistence_mapping = Hash.new
24
  for line in presets_configs
25 26 27 28 29
    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 }
30 31 32 33 34 35 36
    # 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
37 38 39 40 41 42
    persistence_mapping[source] = destination
  end
  return persistence_mapping
end

def persistent_dirs
43
  get_persistence_presets_config
44 45 46
end

def persistent_mounts
47
  get_persistence_presets_config(true)
48 49
end

50
def persistent_volumes_mountpoints
51
  $vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
52 53
end

54 55 56 57 58 59 60 61 62 63 64 65 66 67
# Returns an array that for each persistence preset the running Tails is aware of,
# contains a hash with the following keys: id, enabled, has_configuration_button.
def persistent_presets_ui_settings
  # Perl script that prints all persistence presets
  # in the form: <id>:<enabled>:<has_configuration_button>
  script = <<-EOF
  use strict;
  use warnings FATAL => "all";
  use Tails::Persistence::Configuration::Presets;
  foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) {
    say(sprintf(
      "%s:%s:%s",
      $preset->{id},
      ($preset->{enabled} ? 1 : 0),
68
      (exists($preset->{configuration_app_desktop_id}) && defined($preset->{configuration_app_desktop_id})
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
         ? 1
         : 0
      ),
    ));
  }
EOF
  # VMCommand:s cannot handle newlines, and they're irrelevant in the
  # above perl script any way
  script.delete!("\n")
  presets = $vm.execute_successfully("perl -E '#{script}'")
               .stdout.chomp.split("\n")
  assert presets.size >= 10,
         "Got #{presets.size} persistence presets, " +
         "which is too few"
  presets_ui_settings = Array.new
  for line in presets
    id, enabled, has_configuration_button = line.split(":")
    presets_ui_settings += [{
      'id'                       => id,
      'enabled'                  => (enabled == '1'),
      'has_configuration_button' => (has_configuration_button == '1'),
    }]
  end
  return presets_ui_settings
end


96
def recover_from_upgrader_failure
intrigeri's avatar
intrigeri committed
97 98 99 100 101
  $vm.execute('pkill --full tails-upgrade-frontend-wrapper')
  $vm.execute('killall tails-upgrade-frontend zenity')
  # Do not sleep when retrying
  $vm.execute_successfully('/usr/local/bin/tails-upgrade-frontend-wrapper --no-wait')
  $vm.spawn('tails-upgrade-frontend-wrapper', user: LIVE_USER)
102 103
end

104
Given /^I clone USB drive "([^"]+)" to a (new|temporary) USB drive "([^"]+)"$/ do |from, mode, to|
105
  $vm.storage.clone_to_new_disk(from, to)
106 107 108
  if mode == 'temporary'
    add_after_scenario_hook { $vm.storage.delete_volume(to) }
  end
109 110 111
end

Given /^I unplug USB drive "([^"]+)"$/ do |name|
112
  $vm.unplug_drive(name)
113 114
end

115
Given /^the computer is set to boot from the old Tails DVD$/ do
116
  $vm.set_cdrom_boot(OLD_TAILS_ISO)
117 118
end

119
Given /^the computer is set to boot in UEFI mode$/ do
120
  $vm.set_os_loader('UEFI')
121 122 123
  @os_loader = 'UEFI'
end

anonym's avatar
anonym committed
124
def tails_installer_selected_device
125
  @installer.child('Target USB stick:', roleName: 'label').parent
anonym's avatar
anonym committed
126 127 128 129 130
    .child('', roleName: 'combo box', recursive: false).name
end

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

anonym's avatar
anonym committed
134
def tails_installer_match_status(pattern)
anonym's avatar
anonym committed
135
  @installer.child('', roleName: 'text').text[pattern]
anonym's avatar
anonym committed
136 137
end

138
When /^I start Tails Installer$/ do
anonym's avatar
anonym committed
139
  @installer_log_path = '/tmp/tails-installer.log'
140
  step "I run \"/usr/bin/tails-installer --verbose > #{@installer_log_path} 2>&1\" in GNOME Terminal"
141
  @installer = Dogtail::Application.new('tails-installer')
142
  @installer.child('Tails Installer', roleName: 'frame')
143 144 145 146 147
  # 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.
148 149
  sleep 3
  $vm.focus_window('Tails Installer')
150 151
end

152
When /^I am told by Tails Installer that.*"([^"]+)".*$/ do |status|
153
  try_for(10) do
154
    tails_installer_match_status(status)
155 156 157
  end
end

158
Then /^a suitable USB device is (?:still )?not found$/ do
159 160 161
  @installer.child(
    'No device suitable to install Tails could be found', roleName: 'label'
  )
162 163 164 165 166 167 168 169 170 171 172 173
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

174
When /^I (install|reinstall|upgrade) Tails (?:to|on) USB drive "([^"]+)" (by cloning|from an ISO)$/ do |action, name, source|
175
  step "I start Tails Installer"
176 177 178
  # If the device was plugged *just* before this step, it might not be
  # completely ready (so it's shown) at this stage.
  try_for(10) { tails_installer_is_device_selected?(name) }
179 180 181 182 183 184 185 186 187 188 189 190
  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
191 192 193 194 195 196
    if action == 'reinstall'
      label = 'Reinstall (delete all data)'
    else
      label = action.capitalize
    end
    @installer.button(label).click
197 198
    if action == 'upgrade'
      confirmation_label = 'Upgrade'
199
    else
200
      confirmation_label = 'Install'
201
    end
202
    @installer.child('Question', roleName: 'alert').button(confirmation_label).click
203
    try_for(15*60, { :delay => 10 }) do
204 205 206 207 208 209
      @installer
        .child('Information', roleName: 'alert')
        .child('Installation complete!', roleName: 'label')
      true
    end
  rescue Exception => e
anonym's avatar
anonym committed
210 211
    debug_log("Tails Installer debug log:\n" +
              $vm.file_content(@installer_log_path))
212 213
    raise e
  end
214 215
end

216 217 218 219 220
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

221 222
Given /^I enable all persistence presets$/ do
  @screen.wait('PersistenceWizardPresets.png', 20)
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
  presets = persistent_presets_ui_settings
  presets[0]['is_first'] = true
  debug_log("presets: #{presets}")
  for setting in presets
    debug_log("on preset: #{setting}")
    tabs_to_select_switch  = 3 # previous switch -> separator -> row -> switch
    tabs_to_select_switch -= 1 if setting['is_first']
    tabs_to_select_switch += 1 if setting['has_configuration_button']
    # Select the switch
    debug_log("typing TAB #{tabs_to_select_switch} times to select the switch")
    tabs_to_select_switch.times do
      debug_log("typing TAB")
      @screen.type(Sikuli::Key.TAB)
    end
    # Activate the switch
    if ! setting['enabled']
      debug_log("pressing space")
      @screen.type(Sikuli::Key.SPACE)
    else
      debug_log("setting already enabled, skipping")
    end
244
  end
245
  @screen.type(Sikuli::Key.ENTER) # Press the Save button
intrigeri's avatar
intrigeri committed
246
  @screen.wait('PersistenceWizardDone.png', 60)
247
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
248 249
end

250
When /^I disable the first persistence preset$/ do
anonym's avatar
anonym committed
251
  step 'I start "Configure persistent volume" via GNOME Activities Overview'
252
  @screen.wait('PersistenceWizardPresets.png', 300)
253
  @screen.type(Sikuli::Key.TAB)
254
  @screen.type(Sikuli::Key.SPACE)
255
  @screen.type(Sikuli::Key.ENTER)
256 257 258 259
  @screen.wait('PersistenceWizardDone.png', 30)
  @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
end

260
Given /^I create a persistent partition$/ do
anonym's avatar
anonym committed
261
  step 'I start "Configure persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
262
  @screen.wait('PersistenceWizardStart.png', 60)
263
  @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
Tails developers's avatar
Tails developers committed
264
  @screen.wait('PersistenceWizardPresets.png', 300)
265 266 267
  step "I enable all persistence presets"
end

268
def check_disk_integrity(name, dev, scheme)
269
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
270 271 272 273 274 275 276
  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

277
def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil)
278
  info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout
279
  info_split = info.split("\n  org\.freedesktop\.UDisks2\.Partition:\n")
280 281
  dev_info = info_split[0]
  part_info = info_split[1]
282
  assert(dev_info.match("^    IdUsage: +#{usage}$"),
283
         "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
284 285 286
  assert(dev_info.match("^    IdType: +#{fs_type}$"),
         "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'")
  assert(part_info.match("^    Name: +#{part_label}$"),
287
         "Unexpected partition label on USB drive '#{name}', '#{dev}'")
288 289 290 291
  if part_type
    assert(part_info.match("^    Type: +#{part_type}$"),
           "Unexpected partition type on USB drive '#{name}', '#{dev}'")
  end
292 293
end

294
def tails_is_installed_helper(name, tails_root, loader)
295
  disk_dev = $vm.disk_dev(name)
296 297
  part_dev = disk_dev + "1"
  check_disk_integrity(name, disk_dev, "gpt")
298 299 300
  check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails",
                       # EFI System Partition
                       'c12a7328-f81f-11d2-ba4b-00a0c93ec93b')
301

302
  target_root = "/mnt/new"
303
  $vm.execute("mkdir -p #{target_root}")
304
  $vm.execute("mount #{part_dev} #{target_root}")
305

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

310
  syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split
311
  # We deal with these files separately
312
  ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"]
313
  for f in syslinux_files - ignores do
314
    c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " +
315
                    "'#{target_root}/syslinux/#{f}'")
316 317 318 319 320
    assert(c.success?, "USB drive '#{name}' has differences in " +
           "'/syslinux/#{f}'")
  end

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

326 327
  $vm.execute("umount #{target_root}")
  $vm.execute("sync")
328 329
end

330 331 332 333 334 335 336
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"
337
  $vm.execute("mkdir -p #{iso_root}")
338
  $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
Tails developers's avatar
Tails developers committed
339
  tails_is_installed_helper(target_name, iso_root, "isolinux")
340
  $vm.execute("umount #{iso_root}")
341 342
end

343
Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
344 345
  data_part_dev = $vm.disk_dev(name) + "2"
  assert(!$vm.execute("test -b #{data_part_dev}").success?,
346 347 348
         "USB drive #{name} has a partition '#{data_part_dev}'")
end

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

353 354
  # The LUKS container may already be opened, e.g. by udisks after
  # we've run tails-persistence-setup.
355
  c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
356 357
  if c.success?
    for candidate in c.stdout.split("\n")
358
      luks_info = $vm.execute("cryptsetup status '#{candidate}'")
359 360 361 362 363 364 365
      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?
366 367
    c = $vm.execute("echo #{@persistence_password} | " +
                    "cryptsetup luksOpen #{dev} #{name}")
368 369 370
    assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
    luks_dev = "/dev/mapper/#{name}"
  end
371 372

  # Adapting check_part_integrity() seems like a bad idea so here goes
373
  info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout
374 375 376 377
  assert info.match("^    CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$")
  assert info.match("^    IdUsage: +filesystem$")
  assert info.match("^    IdType: +ext[34]$")
  assert info.match("^    IdLabel: +TailsData$")
378 379

  mount_dir = "/mnt/#{name}"
380
  $vm.execute("mkdir -p #{mount_dir}")
381
  c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
382
  assert(c.success?,
Tails developers's avatar
Tails developers committed
383
         "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
384

385 386 387
  $vm.execute("umount #{mount_dir}")
  $vm.execute("sync")
  $vm.execute("cryptsetup luksClose #{name}")
388 389
end

390
Given /^I enable persistence$/ do
intrigeri's avatar
intrigeri committed
391
  @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 60)
392 393
  @screen.type(@persistence_password + Sikuli::Key.ENTER)
  @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
394 395
end

396 397
def tails_persistence_enabled?
  persistence_state_file = "/var/lib/live/config/tails.persistence"
398 399
  return $vm.execute("test -e '#{persistence_state_file}'").success? &&
         $vm.execute(". '#{persistence_state_file}' && " +
400
                     'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
401 402
end

403 404
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.")
405 406 407
  try_for(120, :msg => "Persistence is disabled") do
    tails_persistence_enabled?
  end
408
  unexpected_mounts = Array.new
409
  # Check that all persistent directories are mounted
410 411
  if old_tails.empty?
    expected_mounts = persistent_mounts
412 413 414 415 416 417
    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
418
  else
419
    assert_not_nil($remembered_persistence_mounts)
420
    expected_mounts = $remembered_persistence_mounts
421
  end
422
  mount = $vm.execute("mount").stdout.chomp
423
  for _, dir in expected_mounts do
424 425 426
    assert(mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is not mounted")
  end
427 428 429 430
  for dir in unexpected_mounts do
    assert(! mount.include?("on #{dir} "),
           "Persistent directory '#{dir}' is mounted")
  end
431 432
end

433 434 435 436
Given /^persistence is disabled$/ do
  assert(!tails_persistence_enabled?, "Persistence is enabled")
end

437
def boot_device
438 439
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
440 441
  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
442 443 444
  return boot_dev
end

anonym's avatar
anonym committed
445
def device_info(dev)
446 447
  # Approach borrowed from
  # config/chroot_local_includes/lib/live/config/998-permissions
anonym's avatar
anonym committed
448 449 450 451 452 453
  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']
454 455
end

456 457 458
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
  bus = bus.downcase
  case bus
459
  when "sata"
460 461 462 463 464
    expected_bus = "ata"
  else
    expected_bus = bus
  end
  assert_equal(expected_bus, boot_device_type)
465
  actual_dev = boot_device
466 467 468 469 470 471
  # 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),
472
         "We are running from device #{actual_dev}, but for #{bus} drive " +
473
         "'#{name}' we expected to run from one of #{expected_devs}")
474 475 476 477 478
end

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

  super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
479
  devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
480
  assert(devs.size > 0, "Could not determine boot device")
481
  all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
482
  all_users_with_groups = all_users.collect do |user|
483
    groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
484 485 486
    [user, groups]
  end
  for dev in devs do
487 488 489
    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
490
    assert_equal("root", dev_owner)
491 492
    assert(dev_group == "disk" || dev_group == "root",
           "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
493
           "'disk' or 'root'.")
494
    assert_equal("660", dev_perms)
495 496 497 498 499 500 501
    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
502

503
  info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout
intrigeri's avatar
intrigeri committed
504
  assert(info.match("^    HintSystem: +true$"),
505
         "Boot device '#{super_boot_dev}' is not system internal for udisks")
506 507
end

508
Then /^all persistent filesystems have safe access rights$/ do
509
  persistent_volumes_mountpoints.each do |mountpoint|
510 511 512
    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
513 514 515
    assert_equal("root", fs_owner)
    assert_equal("root", fs_group)
    assert_equal('775', fs_perms)
516 517 518
  end
end

519
Then /^all persistence configuration files have safe access rights$/ do
520
  # XXX: #14596
521
  next
522
  persistent_volumes_mountpoints.each do |mountpoint|
523
    assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?,
524
           "#{mountpoint}/persistence.conf does not exist, while it should")
525
    assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
526
           "#{mountpoint}/live-persistence.conf does exist, while it should not")
527
    $vm.execute(
528 529
      "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
    ).stdout.chomp.split.each do |f|
530 531 532
      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
533 534 535
      assert_equal("tails-persistence-setup", file_owner)
      assert_equal("tails-persistence-setup", file_group)
      assert_equal("600", file_perms)
536
    end
Tails developers's avatar
Tails developers committed
537
  end
538 539
end

540
Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails|
541 542 543
  if old_tails.empty?
    expected_dirs = persistent_dirs
  else
544
    assert_not_nil($remembered_persistence_dirs)
545
    expected_dirs = $remembered_persistence_dirs
546
  end
547
  persistent_volumes_mountpoints.each do |mountpoint|
548
    expected_dirs.each do |src, dest|
Tails developers's avatar
Tails developers committed
549
      full_src = "#{mountpoint}/#{src}"
550 551 552
      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
553
      if dest.start_with?("/home/#{LIVE_USER}")
554
        expected_perms = "700"
555
        expected_owner = LIVE_USER
556 557 558 559 560 561 562 563 564 565
      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}")
566 567 568 569
    end
  end
end

570
When /^I write some files expected to persist$/ do
571
  persistent_mounts.each do |_, dir|
572
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
573
    assert($vm.execute("touch #{dir}/XXX_persist", :user => owner).success?,
574
           "Could not create file in persistent directory #{dir}")
575 576 577
  end
end

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

584
When /^I remove some files expected to persist$/ do
585
  persistent_mounts.each do |_, dir|
586
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
587
    assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?,
588
           "Could not remove file in persistent directory #{dir}")
589 590 591 592
  end
end

When /^I write some files not expected to persist$/ do
593
  persistent_mounts.each do |_, dir|
594
    owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
595
    assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?,
596
           "Could not create file in persistent directory #{dir}")
597 598 599
  end
end

600
When /^I take note of which persistence presets are available$/ do
601 602
  $remembered_persistence_mounts = persistent_mounts
  $remembered_persistence_dirs = persistent_dirs
603 604 605 606 607 608
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
609
    assert_not_nil($remembered_persistence_mounts)
610
    expected_mounts = $remembered_persistence_mounts
611 612
  end
  expected_mounts.each do |_, dir|
613
    assert($vm.execute("test -e #{dir}/XXX_persist").success?,
614
           "Could not find expected file in persistent directory #{dir}")
615
    assert(!$vm.execute("test -e #{dir}/XXX_gone").success?,
616 617 618 619
           "Found file that should not have persisted in persistent directory #{dir}")
  end
end

620 621 622 623 624 625 626 627
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

628
Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
629
  assert(!$vm.is_running?)
630
  disk = {
631
    :path => $vm.storage.disk_path(name),
632
    :opts => {
633
      :format => $vm.storage.disk_format(name),
634 635 636
      :readonly => true
    }
  }
637
  $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle|
638 639 640 641 642 643 644 645 646
    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"
647
    g.luks_open(partition, @persistence_password, luks_mapping)
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
    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
664
end
665 666

When /^I delete the persistent partition$/ do
anonym's avatar
anonym committed
667
  step 'I start "Delete persistent volume" via GNOME Activities Overview'
intrigeri's avatar
intrigeri committed
668
  @screen.wait("PersistenceWizardDeletionStart.png", 120)
669 670 671
  @screen.type(" ")
  @screen.wait("PersistenceWizardDone.png", 120)
end
672 673

Then /^Tails has started in UEFI mode$/ do
674
  assert($vm.execute("test -d /sys/firmware/efi").success?,
675 676
         "/sys/firmware/efi does not exist")
 end
677 678

Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
679
  $vm.storage.disk_mklabel(name, type)
680
end
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 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
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|
759
  recovery_proc = Proc.new do
760
    recover_from_upgrader_failure
761 762 763 764 765 766 767
  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
768 769 770 771 772 773
end

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

anonym's avatar
anonym committed
774 775
Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
  step 'I agree to install the incremental upgrade'
776 777 778 779 780 781 782 783 784 785 786
  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
787
end