qvm-backup 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #!/usr/bin/python2
  2. # -*- encoding: utf8 -*-
  3. #
  4. # The Qubes OS Project, http://www.qubes-os.org
  5. #
  6. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  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 qubes.backup import backup_prepare, backup_do
  26. from qubes.qubesutils import size_to_human
  27. from optparse import OptionParser
  28. import qubes.backup
  29. import os
  30. import sys
  31. import getpass
  32. from locale import getpreferredencoding
  33. def print_progress(progress):
  34. print >> sys.stderr, "\r-> Backing up files: {0}%...".format (progress),
  35. def main():
  36. usage = "usage: %prog [options] <backup-dir-path> [vms-to-be-included ...]"
  37. parser = OptionParser (usage)
  38. parser.add_option ("-x", "--exclude", action="append",
  39. dest="exclude_list", default=[],
  40. help="Exclude the specified VM from the backup (may be "
  41. "repeated)")
  42. parser.add_option ("--force-root", action="store_true", dest="force_root", default=False,
  43. help="Force to run with root privileges")
  44. parser.add_option ("-d", "--dest-vm", action="store", dest="appvm",
  45. help="Specify the destination VM to which the backup "
  46. "will be sent (implies -e)")
  47. parser.add_option ("-e", "--encrypt", action="store_true", dest="encrypt", default=False,
  48. help="Encrypt the backup")
  49. parser.add_option ("--no-encrypt", action="store_true",
  50. dest="no_encrypt", default=False,
  51. help="Skip encryption even if sending the backup to a "
  52. "VM")
  53. parser.add_option ("-p", "--passphrase-file", action="store",
  54. dest="pass_file", default=None,
  55. help="Read passphrase from a file, or use '-' to read "
  56. "from stdin")
  57. parser.add_option ("-E", "--enc-algo", action="store",
  58. dest="crypto_algorithm", default=None,
  59. help="Specify a non-default encryption algorithm. For a "
  60. "list of supported algorithms, execute 'openssl "
  61. "list-cipher-algorithms' (implies -e)")
  62. parser.add_option ("-H", "--hmac-algo", action="store",
  63. dest="hmac_algorithm", default=None,
  64. help="Specify a non-default HMAC algorithm. For a list "
  65. "of supported algorithms, execute 'openssl "
  66. "list-message-digest-algorithms'")
  67. parser.add_option ("-z", "--compress", action="store_true", dest="compress", default=False,
  68. help="Compress the backup")
  69. parser.add_option ("-Z", "--compress-filter", action="store",
  70. dest="compress_filter", default=False,
  71. help="Specify a non-default compression filter program "
  72. "(default: gzip)")
  73. parser.add_option("--tmpdir", action="store", dest="tmpdir", default=None,
  74. help="Specify a temporary directory (if you have at least "
  75. "1GB free RAM in dom0, use of /tmp is advised) ("
  76. "default: /var/tmp)")
  77. parser.add_option ("--debug", action="store_true", dest="debug",
  78. default=False, help="Enable (a lot of) debug output")
  79. (options, args) = parser.parse_args ()
  80. if (len (args) < 1):
  81. print >> sys.stderr, "You must specify the target backup directory "\
  82. " (e.g. /mnt/backup)."
  83. print >> sys.stderr, "qvm-backup will create a subdirectory there for "\
  84. " each individual backup."
  85. exit (0)
  86. base_backup_dir = args[0]
  87. if hasattr(os, "geteuid") and os.geteuid() == 0:
  88. if not options.force_root:
  89. print >> sys.stderr, "*** Running this tool as root is strongly "\
  90. "discouraged. This will lead to permissions "\
  91. "problems."
  92. print >> sys.stderr, "Retry as an unprivileged user, or use "\
  93. "--force-root to continue anyway."
  94. exit(1)
  95. # Only for locking
  96. qvm_collection = QubesVmCollection()
  97. qvm_collection.lock_db_for_reading()
  98. qvm_collection.load()
  99. vms = None
  100. if (len (args) > 1):
  101. vms = [qvm_collection.get_vm_by_name(vmname) for vmname in args[1:]]
  102. if options.appvm:
  103. options.exclude_list.append(options.appvm)
  104. if options.appvm or options.crypto_algorithm:
  105. options.encrypt = True
  106. if options.no_encrypt:
  107. options.encrypt = False
  108. if options.debug:
  109. qubes.backup.BACKUP_DEBUG = True
  110. files_to_backup = None
  111. try:
  112. files_to_backup = backup_prepare(
  113. vms_list=vms,
  114. exclude_list=options.exclude_list,
  115. hide_vm_names=options.encrypt)
  116. except QubesException as e:
  117. print >>sys.stderr, "ERROR: %s" % str(e)
  118. exit(1)
  119. total_backup_sz = reduce(lambda size, file: size+file["size"],
  120. files_to_backup, 0)
  121. if not options.appvm:
  122. appvm = None
  123. if os.path.isdir(base_backup_dir):
  124. stat = os.statvfs(base_backup_dir)
  125. else:
  126. stat = os.statvfs(os.path.dirname(base_backup_dir))
  127. backup_fs_free_sz = stat.f_bsize * stat.f_bavail
  128. print
  129. if (total_backup_sz > backup_fs_free_sz):
  130. print >>sys.stderr, "ERROR: Not enough space available on the "\
  131. "backup filesystem!"
  132. exit(1)
  133. print "-> Available space: {0}".format(size_to_human(backup_fs_free_sz))
  134. else:
  135. appvm = qvm_collection.get_vm_by_name(options.appvm)
  136. if appvm is None:
  137. print >>sys.stderr, "ERROR: VM {0} does not exist!".format(options.appvm)
  138. exit(1)
  139. stat = os.statvfs('/var/tmp')
  140. backup_fs_free_sz = stat.f_bsize * stat.f_bavail
  141. print
  142. if (backup_fs_free_sz < 1000000000):
  143. print >>sys.stderr, "ERROR: Not enough space available " \
  144. "on the local filesystem (1GB required for temporary files)!"
  145. exit(1)
  146. if not appvm.is_running():
  147. appvm.start(verbose=True)
  148. if options.appvm:
  149. print >>sys.stderr, ("NOTE: VM {} will be excluded because it is "
  150. "the backup destination.").format(options.appvm)
  151. options.exclude_list.append(options.appvm)
  152. if not options.encrypt:
  153. print >>sys.stderr, "WARNING: The backup will NOT be encrypted!"
  154. if options.pass_file is not None:
  155. f = open(options.pass_file) if options.pass_file != "-" else sys.stdin
  156. passphrase = f.readline().rstrip()
  157. if f is not sys.stdin:
  158. f.close()
  159. else:
  160. if raw_input("Do you want to proceed? [y/N] ").upper() != "Y":
  161. exit(0)
  162. s = ("Please enter the passphrase that will be used to {}verify "
  163. "the backup: ").format('encrypt and ' if options.encrypt else '')
  164. passphrase = getpass.getpass(s)
  165. if getpass.getpass("Enter again for verification: ") != passphrase:
  166. print >>sys.stderr, "ERROR: Passphrase mismatch!"
  167. exit(1)
  168. encoding = sys.stdin.encoding or getpreferredencoding()
  169. passphrase = passphrase.decode(encoding)
  170. kwargs = {}
  171. if options.hmac_algorithm:
  172. kwargs['hmac_algorithm'] = options.hmac_algorithm
  173. if options.crypto_algorithm:
  174. kwargs['crypto_algorithm'] = options.crypto_algorithm
  175. if options.tmpdir:
  176. kwargs['tmpdir'] = options.tmpdir
  177. try:
  178. backup_do(base_backup_dir, files_to_backup, passphrase,
  179. progress_callback=print_progress,
  180. encrypted=options.encrypt,
  181. compressed=options.compress_filter or options.compress,
  182. appvm=appvm, **kwargs)
  183. except QubesException as e:
  184. print >>sys.stderr, "ERROR: %s" % str(e)
  185. exit(1)
  186. print
  187. print "-> Backup completed."
  188. qvm_collection.unlock_db()
  189. main()