Update changelog for 0.20.
[tails-test.git] / features / step_definitions / usb.rb
blob19f62f585a802dfddb92ea38df75adbb5b878377
1 def persistent_dirs
2   ["/home/#{$live_user}/.claws-mail",
3    "/home/#{$live_user}/.gconf/system/networking/connections",
4    "/home/#{$live_user}/.gnome2/keyrings",
5    "/home/#{$live_user}/.gnupg",
6    "/home/#{$live_user}/.mozilla/firefox/bookmarks",
7    "/home/#{$live_user}/.purple",
8    "/home/#{$live_user}/.ssh",
9    "/home/#{$live_user}/Persistent",
10    "/var/cache/apt/archives",
11    "/var/lib/apt/lists"]
12 end
14 Given /^I create a new (\d+) ([[:alpha:]]+) USB drive named "([^"]+)"$/ do |size, unit, name|
15   next if @skip_steps_while_restoring_background
16   @vm.storage.create_new_disk(name, {:size => size, :unit => unit})
17 end
19 Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
20   next if @skip_steps_while_restoring_background
21   @vm.storage.clone_to_new_disk(from, to)
22 end
24 Given /^I unplug USB drive "([^"]+)"$/ do |name|
25   next if @skip_steps_while_restoring_background
26   @vm.unplug_drive(name)
27 end
29 Given /^the computer is set to boot from the old Tails DVD$/ do
30   next if @skip_steps_while_restoring_background
31   @vm.set_cdrom_boot($old_tails_iso)
32 end
34 class ISOHybridUpgradeNotSupported < StandardError
35 end
37 def usb_install_helper(name)
38   @screen.wait('USBCreateLiveUSB.png', 10)
40   # Here we'd like to select USB drive using #{name}, but Sikuli's
41   # OCR seems to be too unreliable.
42 #  @screen.wait('USBTargetDevice.png', 10)
43 #  match = @screen.find('USBTargetDevice.png')
44 #  region_x = match.x
45 #  region_y = match.y + match.height
46 #  region_w = match.width*3
47 #  region_h = match.height*2
48 #  ocr = Sikuli::Region.new(region_x, region_y, region_w, region_h).text
49 #  STDERR.puts ocr
50 #  # Unfortunately this results in almost garbage, like "|]dev/sdm"
51 #  # when it should be /dev/sda1
53   @screen.wait_and_click('USBCreateLiveUSB.png', 10)
54   begin
55     if @screen.find("USBSuggestsInstall.png")
56       raise ISOHybridUpgradeNotSupported
57     end
58   rescue Sikuli::ImageNotFound
59     # we didn't get the warning, so we can proceed with the install
60   end
61 #  @screen.hide_cursor
62   @screen.wait_and_click('USBCreateLiveUSBNext.png', 10)
63 #  @screen.hide_cursor
64   @screen.wait('USBInstallationComplete.png', 60*60)
65   @screen.type(Sikuli::KEY_RETURN)
66   @screen.type(Sikuli::KEY_F4, Sikuli::KEY_ALT)
67 end
69 When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
70   next if @skip_steps_while_restoring_background
71   step "I run \"liveusb-creator-launcher\""
72   @screen.wait_and_click('USBCloneAndInstall.png', 30)
73   usb_install_helper(name)
74 end
76 When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
77   next if @skip_steps_while_restoring_background
78   step "I run \"liveusb-creator-launcher\""
79   @screen.wait_and_click('USBCloneAndUpgrade.png', 30)
80   usb_install_helper(name)
81 end
83 When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
84   next if @skip_steps_while_restoring_background
85   begin
86     step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
87   rescue ISOHybridUpgradeNotSupported
88     # this is what we expect
89   else
90     raise "The USB installer should not succeed"
91   end
92 end
94 When /^I am suggested to do a "Clone & Upgrade"$/ do
95   next if @skip_steps_while_restoring_background
96   @screen.find("USBSuggestsInstall.png")
97 end
99 def shared_iso_dir_on_guest
100   "/tmp/shared_dir"
103 Given /^I setup a filesystem share containing the Tails ISO$/ do
104   next if @skip_steps_while_restoring_background
105   @vm.add_share(File.dirname($tails_iso), shared_iso_dir_on_guest)
108 When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
109   next if @skip_steps_while_restoring_background
110   step "I run \"liveusb-creator-launcher\""
111   @screen.wait_and_click('USBUpgradeFromISO.png', 10)
112   @screen.wait('USBUseLiveSystemISO.png', 10)
113   match = @screen.find('USBUseLiveSystemISO.png')
114   pos_x = match.x + match.width/2
115   pos_y = match.y + match.height*2
116   @screen.click(pos_x, pos_y)
117   @screen.wait('USBSelectISO.png', 10)
118   @screen.wait_and_click('GnomeFileDiagTypeFilename.png', 10)
119   iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
120   @screen.type(iso + Sikuli::KEY_RETURN)
121   usb_install_helper(name)
124 Given /^I enable all persistence presets$/ do
125   next if @skip_steps_while_restoring_background
126   @screen.wait('PersistenceWizardPresets.png', 20)
127   # Mark first non-default persistence preset
128   @screen.type("\t\t")
129   # Check all non-default persistence presets
130   10.times do
131     @screen.type(" \t")
132   end
133   @screen.wait_and_click('PersistenceWizardSave.png', 10)
134   @screen.wait('PersistenceWizardDone.png', 20)
135   @screen.type(Sikuli::KEY_F4, Sikuli::KEY_ALT)
138 Given /^I create a persistent partition with password "([^"]+)"$/ do |pwd|
139   next if @skip_steps_while_restoring_background
140   step "I run \"tails-persistence-setup\""
141   @screen.wait('PersistenceWizardWindow.png', 20)
142   @screen.wait('PersistenceWizardStart.png', 20)
143   @screen.type(pwd + "\t" + pwd + Sikuli::KEY_RETURN)
144   @screen.wait('PersistenceWizardPresets.png', 300)
145   step "I enable all persistence presets"
148 def check_part_integrity(name, dev, usage, type, scheme, label)
149   info = @vm.execute("udisks --show-info #{dev}").stdout
150   info_split = info.split("\n  partition:\n")
151   dev_info = info_split[0]
152   part_info = info_split[1]
153   assert(dev_info.match("^  usage: +#{usage}$"),
154          "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
155   assert(dev_info.match("^  type: +#{type}$"),
156          "Unexpected device field 'type' on USB drive '#{name}', '#{dev}'")
157   assert(part_info.match("^    scheme: +#{scheme}$"),
158          "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
159   assert(part_info.match("^    label: +#{label}$"),
160          "Unexpected partition label on USB drive '#{name}', '#{dev}'")
163 Then /^Tails is installed on USB drive "([^"]+)"$/ do |name|
164   next if @skip_steps_while_restoring_background
165   dev = @vm.disk_dev(name) + "1"
166   check_part_integrity(name, dev, "filesystem", "vfat", "gpt", "Tails")
168   old_root = "/lib/live/mount/medium"
169   new_root = "/mnt/new"
170   @vm.execute("mkdir -p #{new_root}")
171   @vm.execute("mount #{dev} #{new_root}")
173   c = @vm.execute("diff -qr '#{old_root}/live' '#{new_root}/live'")
174   assert(c.success?,
175          "USB drive '#{name}' has differences in /live:\n#{c.stdout}")
177   loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
178   syslinux_files = @vm.execute("ls -1 #{new_root}/syslinux").stdout.chomp.split
179   # We deal with these files separately
180   ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.sys"]
181   for f in syslinux_files - ignores do
182     c = @vm.execute("diff -q '#{old_root}/#{loader}/#{f}' " +
183                     "'#{new_root}/syslinux/#{f}'")
184     assert(c.success?, "USB drive '#{name}' has differences in " +
185            "'/syslinux/#{f}'")
186   end
188   # The main .cfg is named differently vs isolinux
189   c = @vm.execute("diff -q '#{old_root}/#{loader}/#{loader}.cfg' " +
190                   "'#{new_root}/syslinux/syslinux.cfg'")
191   assert(c.success?, "USB drive '#{name}' has differences in " +
192          "'/syslinux/syslinux.cfg'")
194   # We have to account for the different path vs isolinux
195   old_exithelp = @vm.execute("cat '#{old_root}/#{loader}/exithelp.cfg'").stdout
196   new_exithelp = @vm.execute("cat '#{new_root}/syslinux/exithelp.cfg'").stdout
197   new_exithelp_undiffed = new_exithelp.sub("kernel /syslinux/vesamenu.c32",
198                                            "kernel /#{loader}/vesamenu.c32")
199   assert(new_exithelp_undiffed == old_exithelp,
200          "USB drive '#{name}' has unexpected differences in " +
201          "'/syslinux/exithelp.cfg'")
203   @vm.execute("umount #{new_root}")
204   @vm.execute("sync")
207 Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
208   next if @skip_steps_while_restoring_background
209   data_part_dev = @vm.disk_dev(name) + "2"
210   assert(!@vm.execute("test -b #{data_part_dev}").success?,
211          "USB drive #{name} has a partition '#{data_part_dev}'")
214 Then /^a Tails persistence partition with password "([^"]+)" exists on USB drive "([^"]+)"$/ do |pwd, name|
215   next if @skip_steps_while_restoring_background
216   dev = @vm.disk_dev(name) + "2"
217   check_part_integrity(name, dev, "crypto", "crypto_LUKS", "gpt", "TailsData")
219   c = @vm.execute("echo #{pwd} | cryptsetup luksOpen #{dev} #{name}")
220   assert(c.success?, "Couldn't open LUKS device '#{dev}' on  drive '#{name}'")
221   luks_dev = "/dev/mapper/#{name}"
223   # Adapting check_part_integrity() seems like a bad idea so here goes
224   info = @vm.execute("udisks --show-info #{luks_dev}").stdout
225   assert info.match("^  cleartext luks device:$")
226   assert info.match("^  usage: +filesystem$")
227   assert info.match("^  type: +ext[34]$")
228   assert info.match("^  label: +TailsData$")
230   mount_dir = "/mnt/#{name}"
231   @vm.execute("mkdir -p #{mount_dir}")
232   c = @vm.execute("mount #{luks_dev} #{mount_dir}")
233   assert(c.success?,
234          "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
236   @vm.execute("umount #{mount_dir}")
237   @vm.execute("sync")
238   @vm.execute("cryptsetup luksClose #{name}")
241 Given /^I enable persistence with password "([^"]+)"$/ do |pwd|
242   next if @skip_steps_while_restoring_background
243   match = @screen.find('TailsGreeterPersistence.png')
244   pos_x = match.x + match.width/2
245   # height*2 may seem odd, but we want to click the button below the
246   # match. This may even work accross different screen resolutions.
247   pos_y = match.y + match.height*2
248   @screen.click(pos_x, pos_y)
249   @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
250   match = @screen.find('TailsGreeterPersistencePassphrase.png')
251   pos_x = match.x + match.width*2
252   pos_y = match.y + match.height/2
253   @screen.click(pos_x, pos_y)
254   @screen.type(pwd)
257 Given /^persistence is not enabled$/ do
258   next if @skip_steps_while_restoring_background
259   data_part_dev = boot_device + "2"
260   assert(!@vm.execute("grep -q '^#{data_part_dev} ' /proc/mounts").success?,
261          "Partition '#{data_part_dev}' from the boot device is mounted")
264 Given /^I enable read-only persistence with password "([^"]+)"$/ do |pwd|
265   step "I enable persistence with password \"#{pwd}\""
266   next if @skip_steps_while_restoring_background
267   @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
270 Given /^persistence has been enabled$/ do
271   next if @skip_steps_while_restoring_background
272   try_for(120, :msg => "Some persistent dir was not mounted") {
273     mount = @vm.execute("mount").stdout.chomp
274     persistent_dirs.each do |dir|
275       if ! mount.include? "on #{dir} "
276         raise "persistent dir #{dir} missing"
277       end
278     end
279   }
282 def boot_device
283   # Approach borrowed from
284   # config/chroot_local_includes/lib/live/config/998-permissions
285   boot_dev_id = @vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
286   boot_dev = @vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
287   return boot_dev
290 def boot_device_type
291   # Approach borrowed from
292   # config/chroot_local_includes/lib/live/config/998-permissions
293   boot_dev_info = @vm.execute("udevadm info --query=property --name='#{boot_device}'").stdout.chomp
294   boot_dev_type = (boot_dev_info.split("\n").select { |x| x.start_with? "ID_BUS=" })[0].split("=")[1]
295   return boot_dev_type
298 Then /^Tails is running from USB drive "([^"]+)"$/ do |name|
299   next if @skip_steps_while_restoring_background
300   assert(boot_device_type == "usb",
301          "Got device type '#{boot_device_type}' while expecting 'usb'")
302   actual_dev = boot_device
303   # The boot partition differs between a "normal" install using the
304   # USB installer and isohybrid installations
305   expected_dev_normal = @vm.disk_dev(name) + "1"
306   expected_dev_isohybrid = @vm.disk_dev(name) + "4"
307   assert(actual_dev == expected_dev_normal ||
308          actual_dev == expected_dev_isohybrid,
309          "We are running from device #{actual_dev}, but for USB drive " +
310          "'#{name}' we expected to run from either device " +
311          "#{expected_dev_normal} (when installed via the USB installer) " +
312          "or #{expected_dev_normal} (when installed from an isohybrid)")
315 Then /^the boot device has safe access rights$/ do
316   next if @skip_steps_while_restoring_background
318   super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
319   devs = @vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
320   assert(devs.size > 0, "Could not determine boot device")
321   all_users = @vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
322   all_users_with_groups = all_users.collect do |user|
323     groups = @vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
324     [user, groups]
325   end
326   for dev in devs do
327     dev_owner = @vm.execute("stat -c %U #{dev}").stdout.chomp
328     dev_group = @vm.execute("stat -c %G #{dev}").stdout.chomp
329     dev_perms = @vm.execute("stat -c %a #{dev}").stdout.chomp
330     assert(dev_owner == "root",
331            "Boot device '#{dev}' owned by user '#{dev_owner}', expected 'root'")
332     assert(dev_group == "disk" || dev_group == "root",
333            "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
334            "'disk' or 'root'. We are probably affected by Debian bug #645466.")
335     assert(dev_perms == "660",
336            "Boot device '#{dev}' has permissions '#{dev_perms}', expected '660'")
337     for user, groups in all_users_with_groups do
338       next if user == "root"
339       assert(!(groups.include?(dev_group)),
340              "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
341              "owns boot device '#{dev}'")
342     end
343   end
346 When /^I write some files expected to persist$/ do
347   next if @skip_steps_while_restoring_background
348   persistent_dirs.each do |dir|
349     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
350     assert(@vm.execute("touch #{dir}/XXX_persist", user=owner).success?,
351            "Could not create file in persistent directory #{dir}")
352   end
355 When /^I remove some files expected to persist$/ do
356   next if @skip_steps_while_restoring_background
357   persistent_dirs.each do |dir|
358     assert(@vm.execute("rm #{dir}/XXX_persist").success?,
359            "Could not remove file in persistent directory #{dir}")
360   end
363 When /^I write some files not expected to persist$/ do
364   next if @skip_steps_while_restoring_background
365   persistent_dirs.each do |dir|
366     owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
367     assert(@vm.execute("touch #{dir}/XXX_gone", user=owner).success?,
368            "Could not create file in persistent directory #{dir}")
369   end
372 Then /^the expected persistent files are present in the filesystem$/ do
373   next if @skip_steps_while_restoring_background
374   persistent_dirs.each do |dir|
375     assert(@vm.execute("test -e #{dir}/XXX_persist").success?,
376            "Could not find expected file in persistent directory #{dir}")
377     assert(!@vm.execute("test -e #{dir}/XXX_gone").success?,
378            "Found file that should not have persisted in persistent directory #{dir}")
379   end
382 Then /^only the expected files should persist on USB drive "([^"]+)"$/ do |name|
383   next if @skip_steps_while_restoring_background
384   step "a computer"
385   step "the computer is set to boot from USB drive \"#{name}\""
386   step "the network is unplugged"
387   step "I start the computer"
388   step "the computer boots Tails"
389   step "I enable read-only persistence with password \"asdf\""
390   step "I log in to a new session"
391   step "persistence has been enabled"
392   step "GNOME has started"
393   step "I have closed all annoying notifications"
394   step "the expected persistent files are present in the filesystem"
395   step "I completely shutdown Tails"
398 When /^I delete the persistent partition$/ do
399   next if @skip_steps_while_restoring_background
400   step "I run \"tails-persistence-setup --step delete\""
401   @screen.wait("PersistenceWizardWindow.png", 20)
402   @screen.wait("PersistenceWizardDeletionStart.png", 20)
403   @screen.type(" ")
404   @screen.wait("PersistenceWizardDone.png", 120)