qvm-revert-template-changes 5.3 KB

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