qvm-trim-template 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. fstrim_vm = qvm_collection.add_new_vm("QubesAppVm",
  79. template=tvm,
  80. name="{}-fstrim".format(tvm_name),
  81. netvm=None,
  82. )
  83. if not fstrim_vm:
  84. print >> sys.stderr, "ERROR: Failed to create new VM"
  85. sys.exit(1)
  86. fstrim_vm.create_on_disk()
  87. fstrim_vm.start(start_guid=False, verbose=True)
  88. print >> sys.stderr, "Performing fstrim now..."
  89. fstrim_process = fstrim_vm.run("/bin/sh", user="root", passio_popen=True,
  90. gui=False)
  91. fstrim_process.stdin.write('''
  92. until [ -r /dev/xvdi ]; do
  93. sleep 1
  94. done
  95. mkdir /tmp/root
  96. mount -o ro /dev/xvdi /tmp/root
  97. fstrim -v /tmp/root
  98. poweroff
  99. ''')
  100. fstrim_process.stdin.close()
  101. qubesutils.block_attach(qvm_collection, fstrim_vm,
  102. {
  103. 'vm': 'dom0',
  104. 'device': tvm.root_img,
  105. 'mode': 'w',
  106. },
  107. mode='w',
  108. frontend='xvdi')
  109. # At this point, the trim should run and the vm should shut down by itself and
  110. # detach the block device.
  111. fstrim_process.wait()
  112. print >> sys.stderr, "fstrim done, cleaning up..."
  113. while fstrim_vm.is_running():
  114. time.sleep(1)
  115. fstrim_vm.remove_from_disk()
  116. qvm_collection.pop(fstrim_vm.qid)
  117. # if DispVM template was up to date, keep that state
  118. if touch_dvm_savefile:
  119. dvm_savefile_path = os.path.join(dvm_tmpl.dir_path, 'dvm-savefile')
  120. os.utime(dvm_savefile_path, None)
  121. # If this is default DispVM, make sure that tmpfs copy of the file
  122. # (if enabled) also has mtime updated
  123. if os.stat('/var/lib/qubes/dvmdata/default-savefile').st_ino == \
  124. os.stat(dvm_savefile_path).st_ino:
  125. os.utime('/var/run/qubes/current-savefile', None)
  126. # do not save, all changes to qubes.xml should be reversed
  127. qvm_collection.unlock_db()
  128. print 'Disk usage after:'
  129. subprocess.check_call(['du', tvm.root_img])
  130. if __name__ == "__main__":
  131. main()