qvm-revert-template-changes 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. #!/usr/bin/python2
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2011 Marek Marczykowski <marmarek@mimuw.edu.pl>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. #
  21. #
  22. from qubes.qubes import QubesVmCollection
  23. from qubes.qubes import QubesException
  24. from optparse import OptionParser
  25. import subprocess
  26. import os
  27. import time
  28. import glob
  29. import sys
  30. def main():
  31. usage = "usage: %prog [options] <template-name>"
  32. parser = OptionParser (usage)
  33. parser.add_option ("--force", action="store_true", dest="force", default=False,
  34. help="Do not prompt for comfirmation")
  35. (options, args) = parser.parse_args ()
  36. if (len (args) != 1):
  37. parser.error ("You must specify TemplateVM name!")
  38. vmname = args[0]
  39. if os.geteuid() != 0:
  40. print >> sys.stderr, "ERROR: This tool must be run as root!"
  41. exit(1)
  42. qvm_collection = QubesVmCollection()
  43. qvm_collection.lock_db_for_reading()
  44. qvm_collection.load()
  45. qvm_collection.unlock_db()
  46. vm = qvm_collection.get_vm_by_name(vmname)
  47. if vm is None:
  48. print >> sys.stderr, "A VM with the name '{0}' does not exist in the system.".format(vmname)
  49. exit(1)
  50. if not vm.is_template():
  51. print >> sys.stderr, "A VM '{0}' is not template.".format(vmname)
  52. exit(1)
  53. if vm.is_running():
  54. print >> sys.stderr, "You must stop VM first."
  55. exit(1)
  56. oldcow_img = vm.rootcow_img + '.old'
  57. oldcow_stat = os.stat(oldcow_img)
  58. oldcow_time_str = time.strftime("%F %T", time.gmtime(oldcow_stat.st_mtime))
  59. root_stat = os.stat(vm.root_img)
  60. old_dmdev = "/dev/mapper/snapshot-{0:x}:{1}-{2:x}:{3}".format(
  61. root_stat[2], root_stat[1],
  62. oldcow_stat[2], oldcow_stat[1])
  63. snapshots = glob.glob('/dev/mapper/snapshot-{0:x}:{1}-*'.format(root_stat[2], root_stat[1]))
  64. snapshot_present = False
  65. for dev in snapshots:
  66. if dev == old_dmdev:
  67. snapshot_present = True
  68. else:
  69. print >> sys.stderr, "ERROR: You must shutdown all VMs running system older/newer than last good one."
  70. exit(1)
  71. root_blocks = os.path.getsize(vm.root_img)/512
  72. if not snapshot_present:
  73. p = subprocess.Popen (["/etc/xen/scripts/block-snapshot", "prepare",
  74. "snapshot", "{0}:{1}".format(vm.root_img, oldcow_img)],
  75. stdout=subprocess.PIPE)
  76. result = p.communicate()
  77. if result[0].strip() != old_dmdev:
  78. print >> sys.stderr, "ERROR: Cannot create snapshot device ({0} != {1})".format(
  79. result[0].strip(), old_dmdev)
  80. exit(1)
  81. print "INFO: Reverting template changes done at {0}".format(oldcow_time_str)
  82. if not options.force:
  83. prompt = raw_input ("Do you want to proceed? [y/N] ")
  84. if not (prompt == "y" or prompt == "Y"):
  85. exit (0)
  86. p = subprocess.Popen(["/sbin/dmsetup", "table", old_dmdev], stdout=subprocess.PIPE)
  87. result = p.communicate()
  88. dm_table = result[0]
  89. dm_table_elements = dm_table.split(' ')
  90. if dm_table_elements[2] != 'snapshot':
  91. print >> sys.stderr, "ERROR: Unexpected device-mapper type ({0}). Template changes reverting already running".format(dm_table_elements[2])
  92. exit(1)
  93. dm_table_elements[2] = 'snapshot-merge'
  94. dm_table = ' '.join(dm_table_elements)
  95. subprocess.check_call(["/sbin/dmsetup", "reload", old_dmdev, "--table", dm_table])
  96. # Reload new table into LIVE slot
  97. subprocess.check_call(["/sbin/dmsetup", "suspend", old_dmdev])
  98. subprocess.check_call(["/sbin/dmsetup", "resume", old_dmdev])
  99. # Wait to snapshot merge completed
  100. while True:
  101. p = subprocess.Popen(["/sbin/dmsetup", "status", old_dmdev], stdout=subprocess.PIPE)
  102. result = p.communicate()
  103. status_details = result[0].split(' ')
  104. blocks_used = status_details[3].split('/')[0]
  105. if int(blocks_used) == int(status_details[4]):
  106. break
  107. print "\r-> Reverting template changes: {0} of {1} left".format(blocks_used, root_blocks),
  108. time.sleep(1)
  109. print "\r-> Reverting template changes: done ".format(blocks_used, root_blocks)
  110. dm_table_elements[2] = 'snapshot'
  111. dm_table = ' '.join(dm_table_elements)
  112. subprocess.check_call(["/sbin/dmsetup", "reload", old_dmdev, "--table", dm_table])
  113. # Reload new table into LIVE slot
  114. subprocess.check_call(["/sbin/dmsetup", "suspend", old_dmdev])
  115. subprocess.check_call(["/sbin/dmsetup", "resume", old_dmdev])
  116. subprocess.check_call(["/etc/xen/scripts/block-snapshot", "cleanup",
  117. "snapshot", old_dmdev])
  118. os.rename(oldcow_img, vm.rootcow_img)
  119. exit(0)
  120. main()