qvm-trim-template 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. #!/usr/bin/python2
  2. # -*- encoding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2014 Matt McCutchen <matt@mattmccutchen.net>
  7. # Copyright (C) 2015 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
  8. #
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. #
  23. #
  24. import os
  25. import subprocess
  26. import sys
  27. import time
  28. from qubes import qubes
  29. from qubes import qubesutils
  30. def is_dvm_up_to_date(tmpl, dvm_tmpl):
  31. dvm_savefile_path = os.path.join(dvm_tmpl.dir_path, "dvm-savefile")
  32. if not os.path.isfile(dvm_savefile_path):
  33. return False
  34. dvm_mtime = os.path.getmtime(dvm_savefile_path)
  35. root_mtime = os.path.getmtime(tmpl.root_img)
  36. if dvm_mtime < root_mtime:
  37. return False
  38. else:
  39. return True
  40. def main():
  41. if len(sys.argv) != 2:
  42. print >> sys.stderr, 'Usage: qvm-trim-template TEMPLATEVM_NAME'
  43. sys.exit(1)
  44. tvm_name = sys.argv[1]
  45. qvm_collection = qubes.QubesVmCollection()
  46. qvm_collection.lock_db_for_writing()
  47. qvm_collection.load()
  48. tvm = qvm_collection.get_vm_by_name(tvm_name)
  49. if tvm is None:
  50. print >> sys.stderr, 'VM \'{}\' does not exists'.format(tvm_name)
  51. sys.exit(1)
  52. if not tvm.is_template():
  53. print >> sys.stderr, '{} is not template'.format(tvm_name)
  54. sys.exit(1)
  55. if tvm.is_running():
  56. print >> sys.stderr, 'Please stop the TemplateVM first.'
  57. sys.exit(1)
  58. outdated_children = [c for c in qvm_collection.get_vms_based_on(tvm.qid) if
  59. c.is_outdated()]
  60. if outdated_children:
  61. print >> sys.stderr, 'Please stop (or restart) the following outdated VMs based on the template first:\n%s' % ', '.join(
  62. c.name for c in outdated_children)
  63. sys.exit(1)
  64. rootcow_old = tvm.rootcow_img + '.old'
  65. print 'Disk usage before:'
  66. subprocess.check_call(['du', tvm.root_img] + (
  67. ['--total', rootcow_old] if os.path.exists(rootcow_old) else []))
  68. # root-cow.img.old is likely to be invalid once we trim root.img, so go ahead and delete it.
  69. # (Note, root-cow.img should be logically empty because the TemplateVM is not running.)
  70. if os.path.exists(rootcow_old):
  71. os.remove(rootcow_old)
  72. dvm_tmpl = qvm_collection.get_vm_by_name(tvm.name + '-dvm')
  73. if dvm_tmpl is None:
  74. touch_dvm_savefile = False
  75. else:
  76. touch_dvm_savefile = is_dvm_up_to_date(tvm, dvm_tmpl)
  77. print >> sys.stderr, "Creating temporary VM..."
  78. trim_vmname = "trim-{}".format(tvm_name[:31 - len('trim-')])
  79. fstrim_vm = qvm_collection.get_vm_by_name(trim_vmname)
  80. if fstrim_vm is not None:
  81. if not fstrim_vm.internal:
  82. print >>sys.stderr, \
  83. "ERROR: VM '{}' already exists and is not marked as internal. " \
  84. "Remove it manually."
  85. fstrim_vm.remove_from_disk()
  86. qvm_collection.pop(fstrim_vm.qid)
  87. fstrim_vm = qvm_collection.add_new_vm(
  88. "QubesAppVm",
  89. template=tvm,
  90. name=trim_vmname,
  91. netvm=None,
  92. internal=True,
  93. )
  94. if not fstrim_vm:
  95. print >> sys.stderr, "ERROR: Failed to create new VM"
  96. sys.exit(1)
  97. fstrim_vm.create_on_disk()
  98. fstrim_vm.start(start_guid=False, verbose=True)
  99. print >> sys.stderr, "Performing fstrim now..."
  100. fstrim_process = fstrim_vm.run("/bin/sh", user="root", passio_popen=True,
  101. gui=False)
  102. fstrim_process.stdin.write('''
  103. until [ -r /dev/xvdi ]; do
  104. sleep 1
  105. done
  106. mkdir /tmp/root
  107. mount -o ro /dev/xvdi /tmp/root
  108. fstrim -v /tmp/root
  109. poweroff
  110. ''')
  111. fstrim_process.stdin.close()
  112. qubesutils.block_attach(qvm_collection, fstrim_vm,
  113. {
  114. 'vm': 'dom0',
  115. 'device': tvm.root_img,
  116. 'mode': 'w',
  117. },
  118. mode='w',
  119. frontend='xvdi')
  120. # At this point, the trim should run and the vm should shut down by itself and
  121. # detach the block device.
  122. fstrim_process.wait()
  123. print >> sys.stderr, "fstrim done, cleaning up..."
  124. while fstrim_vm.is_running():
  125. time.sleep(1)
  126. fstrim_vm.remove_from_disk()
  127. qvm_collection.pop(fstrim_vm.qid)
  128. # if DispVM template was up to date, keep that state
  129. if touch_dvm_savefile:
  130. dvm_savefile_path = os.path.join(dvm_tmpl.dir_path, 'dvm-savefile')
  131. os.utime(dvm_savefile_path, None)
  132. # If this is default DispVM, make sure that tmpfs copy of the file
  133. # (if enabled) also has mtime updated
  134. if os.stat('/var/lib/qubes/dvmdata/default-savefile').st_ino == \
  135. os.stat(dvm_savefile_path).st_ino:
  136. os.utime('/var/run/qubes/current-savefile', None)
  137. # do not save, all changes to qubes.xml should be reversed
  138. qvm_collection.unlock_db()
  139. print 'Disk usage after:'
  140. subprocess.check_call(['du', tvm.root_img])
  141. if __name__ == "__main__":
  142. main()