From 1f5c03da3f682b21a153552129384093d2f0d9bb Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 01:14:18 +0200 Subject: [PATCH 01/12] Remove QubesCowVm class StandaloneVM isn't really CowVM; also most AppVM/CowVM features applies also to TemplateVM. So CowVM class is meaningless. --- dom0/qvm-core/qubes.py | 547 +++++++++++++++++++---------------------- 1 file changed, 254 insertions(+), 293 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 59f3a416..863751a0 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -177,6 +177,9 @@ class QubesVm(object): root_img = None, private_img = None, memory = default_memory, + template_vm = None, + firewall_conf = None, + volatile_img = None, vcpus = None): @@ -199,9 +202,6 @@ class QubesVm(object): self.uses_default_netvm = uses_default_netvm self.netvm_vm = netvm_vm - # Create template_vm property - used in AppVM, NetVM, ProxyVM - self.template_vm = None - # We use it in remove from disk to avoid removing rpm files (for templates) self.installed_by_rpm = installed_by_rpm @@ -220,6 +220,11 @@ class QubesVm(object): self.private_img = dir_path + "/" + ( private_img if private_img is not None else default_private_img) + if firewall_conf is None: + self.firewall_conf = dir_path + "/" + default_firewall_conf_file + else: + self.firewall_conf = firewall_conf + self.updateable = updateable self.label = label if label is not None else QubesVmLabels["red"] if self.dir_path is not None: @@ -232,6 +237,21 @@ class QubesVm(object): self.memory = memory + if template_vm is not None: + if updateable: + print "ERROR: Template based VM cannot be updateable!" + return false + if not template_vm.is_template(): + print "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\ + format(template_vm.qid) + return False + + template_vm.appvms[qid] = self + else: + assert self.root_img is not None, "Missing root_img for standalone VM!" + + self.template_vm = template_vm + # By default allow use all VCPUs if vcpus is None: qubes_host = QubesHost() @@ -534,6 +554,220 @@ class QubesVm(object): "/local/domain/{0}/qubes_secondary_dns".format(xid), self.netvm_vm.secondary_dns]) + def create_config_file(self): + assert self.template_vm is not None + + conf_template = None + if self.type == "NetVM": + conf_template = open (self.template_vm.netvms_conf_file, "r") + elif self.updateable: + conf_template = open (self.template_vm.standalonevms_conf_file, "r") + else: + conf_template = open (self.template_vm.appvms_conf_file, "r") + if os.path.isfile(self.conf_file): + shutil.copy(self.conf_file, self.conf_file + ".backup") + conf_appvm = open(self.conf_file, "w") + rx_vmname = re.compile (r"%VMNAME%") + rx_vmdir = re.compile (r"%VMDIR%") + rx_template = re.compile (r"%TEMPLATEDIR%") + rx_pcidevs = re.compile (r"%PCIDEVS%") + rx_mem = re.compile (r"%MEM%") + rx_vcpus = re.compile (r"%VCPUS%") + + for line in conf_template: + line = rx_vmname.sub (self.name, line) + line = rx_vmdir.sub (self.dir_path, line) + line = rx_template.sub (self.template_vm.dir_path, line) + line = rx_pcidevs.sub (self.pcidevs, line) + line = rx_mem.sub (str(self.memory), line) + line = rx_vcpus.sub (str(self.vcpus), line) + conf_appvm.write(line) + + conf_template.close() + conf_appvm.close() + + def create_on_disk(self, verbose): + assert self.template_vm is not None + + if dry_run: + return + + if verbose: + print "--> Creating directory: {0}".format(self.dir_path) + os.mkdir (self.dir_path) + + if verbose: + print "--> Creating the VM config file: {0}".format(self.conf_file) + + self.create_config_file() + + template_priv = self.template_vm.private_img + if verbose: + print "--> Copying the template's private image: {0}".\ + format(template_priv) + + # We prefer to use Linux's cp, because it nicely handles sparse files + retcode = subprocess.call (["cp", template_priv, self.private_img]) + if retcode != 0: + raise IOError ("Error while copying {0} to {1}".\ + format(template_priv, self.private_img)) + + if self.is_updateable(): + template_root = self.template_vm.root_img + if verbose: + print "--> Copying the template's root image: {0}".\ + format(template_root) + + # We prefer to use Linux's cp, because it nicely handles sparse files + retcode = subprocess.call (["cp", template_root, self.root_img]) + if retcode != 0: + raise IOError ("Error while copying {0} to {1}".\ + format(template_root, self.root_img)) + + kernels_dir = self.dir_path + '/' + default_kernels_subdir + if verbose: + print "--> Copying the template's kernel dir: {0}".\ + format(self.template_vm.kernels_dir) + shutil.copytree (self.template_vm.kernels_dir, kernels_dir) + + + # Create volatile.img + self.reset_volatile_storage() + + def verify_files(self): + if dry_run: + return + + if not os.path.exists (self.dir_path): + raise QubesException ( + "VM directory doesn't exist: {0}".\ + format(self.dir_path)) + + if not os.path.exists (self.conf_file): + raise QubesException ( + "VM config file doesn't exist: {0}".\ + format(self.conf_file)) + + if self.is_updateable() and not os.path.exists (self.root_img): + raise QubesException ( + "VM root image file doesn't exist: {0}".\ + format(self.root_img)) + + if not os.path.exists (self.private_img): + raise QubesException ( + "VM private image file doesn't exist: {0}".\ + format(self.private_img)) + return True + + def reset_volatile_storage(self): + assert not self.is_running(), "Attempt to clean volatile image of running VM!" + + # Only makes sense on template based VM + if self.template_vm is None: + return + + print "--> Cleaning volatile image: {0}...".format (self.volatile_img) + if dry_run: + return + if os.path.exists (self.volatile_img): + os.remove (self.volatile_img) + + retcode = subprocess.call (["tar", "xf", self.template_vm.clean_volatile_img, "-C", self.dir_path]) + if retcode != 0: + raise IOError ("Error while unpacking {0} to {1}".\ + format(self.template_vm.clean_volatile_img, self.volatile_img)) + + def remove_from_disk(self): + if dry_run: + return + + shutil.rmtree (self.dir_path) + + def write_firewall_conf(self, conf): + root = xml.etree.ElementTree.Element( + "QubesFirwallRules", + policy = "allow" if conf["allow"] else "deny", + dns = "allow" if conf["allowDns"] else "deny", + icmp = "allow" if conf["allowIcmp"] else "deny" + ) + + for rule in conf["rules"]: + element = xml.etree.ElementTree.Element( + "rule", + address=rule["address"], + port=str(rule["portBegin"]), + ) + if rule["netmask"] is not None and rule["netmask"] != 32: + element.set("netmask", str(rule["netmask"])) + if rule["portEnd"] is not None: + element.set("toport", str(rule["portEnd"])) + root.append(element) + + tree = xml.etree.ElementTree.ElementTree(root) + + try: + f = open(self.firewall_conf, 'a') # create the file if not exist + f.close() + + with open(self.firewall_conf, 'w') as f: + fcntl.lockf(f, fcntl.LOCK_EX) + tree.write(f, "UTF-8") + fcntl.lockf(f, fcntl.LOCK_UN) + f.close() + except EnvironmentError as err: + print "{0}: save error: {1}".format( + os.path.basename(sys.argv[0]), err) + return False + + return True + + def has_firewall(self): + return os.path.exists (self.firewall_conf) + + def get_firewall_conf(self): + conf = { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True } + + try: + tree = xml.etree.ElementTree.parse(self.firewall_conf) + root = tree.getroot() + + conf["allow"] = (root.get("policy") == "allow") + conf["allowDns"] = (root.get("dns") == "allow") + conf["allowIcmp"] = (root.get("icmp") == "allow") + + for element in root: + rule = {} + attr_list = ("address", "netmask", "port", "toport") + + for attribute in attr_list: + rule[attribute] = element.get(attribute) + + if rule["netmask"] is not None: + rule["netmask"] = int(rule["netmask"]) + else: + rule["netmask"] = 32 + + rule["portBegin"] = int(rule["port"]) + + if rule["toport"] is not None: + rule["portEnd"] = int(rule["toport"]) + else: + rule["portEnd"] = None + + del(rule["port"]) + del(rule["toport"]) + + conf["rules"].append(rule) + + except EnvironmentError as err: + return conf + except (xml.parsers.expat.ExpatError, + ValueError, LookupError) as err: + print("{0}: load error: {1}".format( + os.path.basename(sys.argv[0]), err)) + return None + + return conf def get_total_xen_memory(self): hosts = xend_session.session.xenapi.host.get_all() @@ -549,6 +783,8 @@ class QubesVm(object): if self.is_running(): raise QubesException ("VM is already running!") + self.reset_volatile_storage() + if verbose: print "--> Rereading the VM's conf file ({0})...".format(self.conf_file) self.update_xen_storage() @@ -656,6 +892,7 @@ class QubesVm(object): attrs["uses_default_netvm"] = str(self.uses_default_netvm) attrs["netvm_qid"] = str(self.netvm_vm.qid) if self.netvm_vm is not None else "none" attrs["installed_by_rpm"] = str(self.installed_by_rpm) + attrs["template_qid"] = str(self.template_vm.qid) if self.template_vm and not self.is_updateable() else "none" attrs["updateable"] = str(self.updateable) attrs["label"] = self.label.name attrs["memory"] = str(self.memory) @@ -939,196 +1176,7 @@ class QubesTemplateVm(QubesVm): attrs["rootcow_img"] = self.rootcow_img return attrs -class QubesCowVm(QubesVm): - """ - A class that represent a VM that may be based on some template, i.e. doesn't have own root.img - - """ - def __init__(self, **kwargs): - if "dir_path" not in kwargs or kwargs["dir_path"] is None: - kwargs["dir_path"] = qubes_appvms_dir + "/" + kwargs["name"] - - if "updateable" not in kwargs or kwargs["updateable"] is None: - kwargs["updateable"] = False - - if "template_vm" in kwargs: - template_vm = kwargs.pop("template_vm") - else: - template_vm = None - - super(QubesCowVm, self).__init__(**kwargs) - qid = kwargs["qid"] - dir_path = kwargs["dir_path"] - if not self.is_updateable(): - # Dirty hack for QubesDom0NetVm... - if not isinstance(self, QubesDom0NetVm): - assert template_vm is not None, "Missing template_vm for template based VM!" - if not template_vm.is_template(): - print "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\ - format(template_vm.qid) - return False - - template_vm.appvms[qid] = self - else: - assert self.root_img is not None, "Missing root_img for standalone VM!" - - self.template_vm = template_vm - - def set_updateable(self): - if self.is_updateable(): - return - - raise QubesException ("Change 'updateable' flag is not supported. Please use qvm-create.") - - def set_nonupdateable(self): - if self.is_updateable(): - return - - raise QubesException ("Change 'updateable' flag is not supported. Please use qvm-create.") - - def create_config_file(self): - conf_template = None - if self.type == "NetVM": - conf_template = open (self.template_vm.netvms_conf_file, "r") - elif self.updateable: - conf_template = open (self.template_vm.standalonevms_conf_file, "r") - else: - conf_template = open (self.template_vm.appvms_conf_file, "r") - if os.path.isfile(self.conf_file): - shutil.copy(self.conf_file, self.conf_file + ".backup") - conf_appvm = open(self.conf_file, "w") - rx_vmname = re.compile (r"%VMNAME%") - rx_vmdir = re.compile (r"%VMDIR%") - rx_template = re.compile (r"%TEMPLATEDIR%") - rx_pcidevs = re.compile (r"%PCIDEVS%") - rx_mem = re.compile (r"%MEM%") - rx_vcpus = re.compile (r"%VCPUS%") - - for line in conf_template: - line = rx_vmname.sub (self.name, line) - line = rx_vmdir.sub (self.dir_path, line) - line = rx_template.sub (self.template_vm.dir_path, line) - line = rx_pcidevs.sub (self.pcidevs, line) - line = rx_mem.sub (str(self.memory), line) - line = rx_vcpus.sub (str(self.vcpus), line) - conf_appvm.write(line) - - conf_template.close() - conf_appvm.close() - - def create_on_disk(self, verbose): - if dry_run: - return - - if verbose: - print "--> Creating directory: {0}".format(self.dir_path) - os.mkdir (self.dir_path) - - if verbose: - print "--> Creating the VM config file: {0}".format(self.conf_file) - - self.create_config_file() - - template_priv = self.template_vm.private_img - if verbose: - print "--> Copying the template's private image: {0}".\ - format(template_priv) - - # We prefer to use Linux's cp, because it nicely handles sparse files - retcode = subprocess.call (["cp", template_priv, self.private_img]) - if retcode != 0: - raise IOError ("Error while copying {0} to {1}".\ - format(template_priv, self.private_img)) - - if self.is_updateable(): - template_root = self.template_vm.root_img - if verbose: - print "--> Copying the template's root image: {0}".\ - format(template_root) - - # We prefer to use Linux's cp, because it nicely handles sparse files - retcode = subprocess.call (["cp", template_root, self.root_img]) - if retcode != 0: - raise IOError ("Error while copying {0} to {1}".\ - format(template_root, self.root_img)) - - kernels_dir = self.dir_path + '/' + default_kernels_subdir - if verbose: - print "--> Copying the template's kernel dir: {0}".\ - format(self.template_vm.kernels_dir) - shutil.copytree (self.template_vm.kernels_dir, kernels_dir) - - - # Create volatile.img - self.reset_volatile_storage() - - def verify_files(self): - if dry_run: - return - - if not os.path.exists (self.dir_path): - raise QubesException ( - "VM directory doesn't exist: {0}".\ - format(self.dir_path)) - - if not os.path.exists (self.conf_file): - raise QubesException ( - "VM config file doesn't exist: {0}".\ - format(self.conf_file)) - - if self.is_updateable() and not os.path.exists (self.root_img): - raise QubesException ( - "VM root image file doesn't exist: {0}".\ - format(self.root_img)) - - if not os.path.exists (self.private_img): - raise QubesException ( - "VM private image file doesn't exist: {0}".\ - format(self.private_img)) - return True - - def start(self, debug_console = False, verbose = False, preparing_dvm = False): - if dry_run: - return - - if self.is_running(): - raise QubesException("VM is already running!") - - self.reset_volatile_storage() - - return super(QubesCowVm, self).start(debug_console=debug_console, verbose=verbose, preparing_dvm=preparing_dvm) - - def reset_volatile_storage(self): - assert not self.is_running(), "Attempt to clean volatile image of running VM!" - - # Only makes sense on template based VM - if not self.template_vm: - return - - print "--> Cleaning volatile image: {0}...".format (self.volatile_img) - if dry_run: - return - if os.path.exists (self.volatile_img): - os.remove (self.volatile_img) - - retcode = subprocess.call (["tar", "xf", self.template_vm.clean_volatile_img, "-C", self.dir_path]) - if retcode != 0: - raise IOError ("Error while unpacking {0} to {1}".\ - format(self.template_vm.clean_volatile_img, self.volatile_img)) - - def remove_from_disk(self): - if dry_run: - return - - subprocess.check_call ([qubes_appmenu_remove_cmd, self.name]) - shutil.rmtree (self.dir_path) - - def get_xml_attrs(self): - attrs = super(QubesCowVm, self).get_xml_attrs() - attrs["template_qid"] = str(self.template_vm.qid) if not self.is_updateable() else "none" - return attrs - -class QubesNetVm(QubesCowVm): +class QubesNetVm(QubesVm): """ A class that represents a NetVM. A child of QubesCowVM. """ @@ -1438,19 +1486,10 @@ class QubesDisposableVm(QubesVm): """ def __init__(self, **kwargs): - template_vm = kwargs.pop("template_vm") + template_vm = kwargs["template_vm"] + assert template_vm is not None, "Missing template_vm for DisposableVM!" super(QubesDisposableVm, self).__init__(dir_path="/nonexistent", **kwargs) - qid = kwargs["qid"] - - assert template_vm is not None, "Missing template_vm for DisposableVM!" - if not template_vm.is_template(): - print "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\ - format(new_vm.template_vm.qid) - return False - - self.template_vm = template_vm - template_vm.appvms[qid] = self @property def type(self): @@ -1468,19 +1507,16 @@ class QubesDisposableVm(QubesVm): return True -class QubesAppVm(QubesCowVm): +class QubesAppVm(QubesVm): """ A class that represents an AppVM. A child of QubesVm. """ def __init__(self, **kwargs): + if "dir_path" not in kwargs or kwargs["dir_path"] is None: + kwargs["dir_path"] = qubes_appvms_dir + "/" + kwargs["name"] + super(QubesAppVm, self).__init__(**kwargs) - dir_path = self.dir_path - - if "firewall_conf" not in kwargs or kwargs["firewall_conf"] is None: - kwargs["firewall_conf"] = dir_path + "/" + default_firewall_conf_file - - self.firewall_conf = kwargs["firewall_conf"] @property def type(self): @@ -1498,6 +1534,13 @@ class QubesAppVm(QubesCowVm): self.create_appmenus (verbose) + def remove_from_disk(self): + if dry_run: + return + + subprocess.check_call ([qubes_appmenu_remove_cmd, self.name]) + super(QubesAppVm, self).remove_from_disk() + def create_appmenus(self, verbose): if self.template_vm is not None: subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name]) @@ -1505,88 +1548,6 @@ class QubesAppVm(QubesCowVm): # Only add apps to menu subprocess.check_call ([qubes_appmenu_create_cmd, "none", self.name]) - def write_firewall_conf(self, conf): - root = xml.etree.ElementTree.Element( - "QubesFirwallRules", - policy = "allow" if conf["allow"] else "deny", - dns = "allow" if conf["allowDns"] else "deny", - icmp = "allow" if conf["allowIcmp"] else "deny" - ) - - for rule in conf["rules"]: - element = xml.etree.ElementTree.Element( - "rule", - address=rule["address"], - port=str(rule["portBegin"]), - ) - if rule["netmask"] is not None and rule["netmask"] != 32: - element.set("netmask", str(rule["netmask"])) - if rule["portEnd"] is not None: - element.set("toport", str(rule["portEnd"])) - root.append(element) - - tree = xml.etree.ElementTree.ElementTree(root) - - try: - f = open(self.firewall_conf, 'a') # create the file if not exist - f.close() - - with open(self.firewall_conf, 'w') as f: - fcntl.lockf(f, fcntl.LOCK_EX) - tree.write(f, "UTF-8") - fcntl.lockf(f, fcntl.LOCK_UN) - f.close() - except EnvironmentError as err: - print "{0}: save error: {1}".format( - os.path.basename(sys.argv[0]), err) - return False - - return True - - def get_firewall_conf(self): - conf = { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True } - - try: - tree = xml.etree.ElementTree.parse(self.firewall_conf) - root = tree.getroot() - - conf["allow"] = (root.get("policy") == "allow") - conf["allowDns"] = (root.get("dns") == "allow") - conf["allowIcmp"] = (root.get("icmp") == "allow") - - for element in root: - rule = {} - attr_list = ("address", "netmask", "port", "toport") - - for attribute in attr_list: - rule[attribute] = element.get(attribute) - - if rule["netmask"] is not None: - rule["netmask"] = int(rule["netmask"]) - else: - rule["netmask"] = 32 - - rule["portBegin"] = int(rule["port"]) - - if rule["toport"] is not None: - rule["portEnd"] = int(rule["toport"]) - else: - rule["portEnd"] = None - - del(rule["port"]) - del(rule["toport"]) - - conf["rules"].append(rule) - - except EnvironmentError as err: - return conf - except (xml.parsers.expat.ExpatError, - ValueError, LookupError) as err: - print("{0}: load error: {1}".format( - os.path.basename(sys.argv[0]), err)) - return None - - return conf class QubesVmCollection(dict): """ From 97393c54a54290614494f0872435400f2386f0fd Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 01:17:18 +0200 Subject: [PATCH 02/12] Really block 'updateable' flag change --- dom0/qvm-core/qubes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 863751a0..9786fab6 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -306,14 +306,17 @@ class QubesVm(object): else: return False + def set_updateable(self): + if self.is_updateable(): + return + + raise QubesException ("Change 'updateable' flag is not supported. Please use qvm-create.") def set_nonupdateable(self): if not self.is_updateable(): return - assert not self.is_running() - # We can always downgrade a VM to non-updateable... - self.updateable = False + raise QubesException ("Change 'updateable' flag is not supported. Please use qvm-create.") def is_template(self): return isinstance(self, QubesTemplateVm) From f0716c24987878076cf52353a6929543823ffd80 Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 01:17:38 +0200 Subject: [PATCH 03/12] Setup firewall for every VM with FW configuration (no only AppVM) (#167) --- dom0/qvm-core/qubes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 9786fab6..9b258d4a 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1348,7 +1348,7 @@ class QubesProxyVm(QubesNetVm): qvm_collection.load() qvm_collection.unlock_db() - vms = [vm for vm in qvm_collection.values() if vm.is_appvm()] + vms = [vm for vm in qvm_collection.values() if vm.has_firewall()] for vm in vms: conf = vm.get_firewall_conf() From 97403a8e45009cf2823126968337e4dd5a5fb928 Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 01:23:57 +0200 Subject: [PATCH 04/12] Start qrexec daemon when VM is running (but qrexec not) This takes place ex. when VM started from qubes-manager. There is little sense in implementing full start procedure in every qubes tool, so start it here, not in qubes-manager. --- dom0/qvm-tools/qvm-run | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dom0/qvm-tools/qvm-run b/dom0/qvm-tools/qvm-run index fe2ca357..4e63125d 100755 --- a/dom0/qvm-tools/qvm-run +++ b/dom0/qvm-tools/qvm-run @@ -133,6 +133,15 @@ def vm_run_cmd(vm, cmd, options): if options.tray: tray_notify_error ("ERROR: Cannot start the GUI daemon for this VM!") exit (1) + + if not os.path.exists("/var/run/qubes/qrexec.{0}".format(xid)): + retcode = subprocess.call ([qrexec_daemon_path, str(xid)]) + if (retcode != 0) : + print "ERROR: Cannot start qrexec!" + if options.tray: + tray_notify_error ("ERROR: Cannot start the QRexec daemon for this VM!") + exit (1) + actually_execute(str(xid), cmd, options); def main(): From 156778fcd7cd2fa30cc6f5dd5707f1b42f96b8bd Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 02:06:22 +0200 Subject: [PATCH 05/12] Set template field before check its correctness. Backup from Aplha3 with updateable VMs contains case, when updateable VM have template. So set this template (to make qvm-backup-restore working), but give error message. Also fix typo. --- dom0/qvm-core/qubes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 9b258d4a..c17b87c5 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -237,10 +237,11 @@ class QubesVm(object): self.memory = memory + self.template_vm = template_vm if template_vm is not None: if updateable: print "ERROR: Template based VM cannot be updateable!" - return false + return False if not template_vm.is_template(): print "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\ format(template_vm.qid) @@ -250,8 +251,6 @@ class QubesVm(object): else: assert self.root_img is not None, "Missing root_img for standalone VM!" - self.template_vm = template_vm - # By default allow use all VCPUs if vcpus is None: qubes_host = QubesHost() From 136a65e0be3c2ad25df15cec573a42b0f7d0104a Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 02:10:50 +0200 Subject: [PATCH 06/12] Fix indentation - duplicate VMs warning message (#159) --- dom0/qvm-tools/qvm-backup-restore | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dom0/qvm-tools/qvm-backup-restore b/dom0/qvm-tools/qvm-backup-restore index 1bb1f9a9..cbb81bcc 100755 --- a/dom0/qvm-tools/qvm-backup-restore +++ b/dom0/qvm-tools/qvm-backup-restore @@ -244,12 +244,12 @@ def main(): if there_are_conflicting_vms: print "*** There VMs with conflicting names on the host! ***" - if options.skip_conflicting: - print "Those VMs will not be restored, the host VMs will not be overwritten!" - else: - print "Remove VMs with conflicting names from the host before proceeding." - print "... or use --skip-conflicting to restore only those VMs that do not exist on the host." - exit (1) + if options.skip_conflicting: + print "Those VMs will not be restored, the host VMs will not be overwritten!" + else: + print "Remove VMs with conflicting names from the host before proceeding." + print "... or use --skip-conflicting to restore only those VMs that do not exist on the host." + exit (1) print "The above VMs will be copied and added to your system." print "Exisiting VMs will not be removed." From e22f303f79350d122b3bdd61bbc33598275d68ec Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Fri, 1 Apr 2011 02:11:40 +0200 Subject: [PATCH 07/12] Warn user when restoring backup as root (#159) --- dom0/qvm-tools/qvm-backup-restore | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dom0/qvm-tools/qvm-backup-restore b/dom0/qvm-tools/qvm-backup-restore index cbb81bcc..991ebb4f 100755 --- a/dom0/qvm-tools/qvm-backup-restore +++ b/dom0/qvm-tools/qvm-backup-restore @@ -127,6 +127,9 @@ def main(): parser.add_option ("--skip-conflicting", action="store_true", dest="skip_conflicting", default=False, help="Do not restore VMs that are already present on the host") + parser.add_option ("--force-root", action="store_true", dest="force_root", default=False, + help="Force to run, even with root privileges") + parser.add_option ("--recreate-conf-files", action="store_true", dest="recreate_conf", default=False, help="Recreate conf files after restore") @@ -242,6 +245,15 @@ def main(): print + if os.geteuid() == 0: + print "*** Running this tool as root is strongly discouraged, this will lead you in permissions problems." + if options.force_root: + print "Continuing as commanded. You have been warned." + else: + print "Retry as unprivileged user." + print "... or use --force-root to continue anyway." + exit(1) + if there_are_conflicting_vms: print "*** There VMs with conflicting names on the host! ***" if options.skip_conflicting: From 5e3b3fe922b64626d20fd24940756dad9fb8484a Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Sat, 2 Apr 2011 00:37:38 +0200 Subject: [PATCH 08/12] Store and load from qubes.xml memory, vcpus and pcidevs Needed to recreate correct xen config files (ex after template package upgrade) --- dom0/qvm-core/qubes.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index c17b87c5..f89d4ce9 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -180,6 +180,7 @@ class QubesVm(object): template_vm = None, firewall_conf = None, volatile_img = None, + pcidevs = None, vcpus = None): @@ -233,7 +234,10 @@ class QubesVm(object): self.icon_path = None # PCI devices - used only by NetVM - self.pcidevs = "" + if pcidevs is None or pcidevs == "none": + self.pcidevs = "" + else: + self.pcidevs = pcidevs self.memory = memory @@ -898,6 +902,7 @@ class QubesVm(object): attrs["updateable"] = str(self.updateable) attrs["label"] = self.label.name attrs["memory"] = str(self.memory) + attrs["pcidevs"] = str(self.pcidevs) attrs["vcpus"] = str(self.vcpus) return attrs @@ -1851,7 +1856,7 @@ class QubesVmCollection(dict): common_attr_list = ("qid", "name", "dir_path", "conf_file", "private_img", "root_img", "template_qid", "installed_by_rpm", "updateable", - "uses_default_netvm", "label") + "uses_default_netvm", "label", "memory", "vcpus", "pcidevs") for attribute in common_attr_list: kwargs[attribute] = element.get(attribute) From ab244d803faf9d33f9dcd14a1e6366a4dd18a643 Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Sat, 2 Apr 2011 02:11:41 +0200 Subject: [PATCH 09/12] Detect if VMs is outdated (#168) If so - VMs restart is required to see latest template changes. --- dom0/qvm-core/qubes.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index f89d4ce9..54c94273 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -480,6 +480,30 @@ class QubesVm(object): else: return False + def is_outdated(self): + # Makes sense only on VM based on template + if self.template_vm is None: + return False + + if not self.is_running(): + return False + + rootimg_inode = os.stat(self.template_vm.root_img) + rootcow_inode = os.stat(self.template_vm.rootcow_img) + + current_dmdev = "/dev/mapper/snapshot-{0:x}:{1}-{2:x}:{3}".format( + rootimg_inode[2], rootimg_inode[1], + rootcow_inode[2], rootcow_inode[1]) + + # Don't know why, but 51712 is xvda + # backend node name not available through xenapi :( + p = subprocess.Popen (["xenstore-read", + "/local/domain/0/backend/vbd/{0}/51712/node".format(self.get_xid())], + stdout=subprocess.PIPE) + used_dmdev = p.communicate()[0].strip() + + return used_dmdev != current_dmdev + def get_disk_usage(self, file_or_dir): if not os.path.exists(file_or_dir): return 0 From fa703c536f18bc944197ee0a20e8442b2bd2054e Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Sun, 3 Apr 2011 01:54:04 +0200 Subject: [PATCH 10/12] Generate firewall rules only for VMs connected to this firewall (#158) --- dom0/qvm-core/qubes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 54c94273..76f7ee74 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1378,6 +1378,10 @@ class QubesProxyVm(QubesNetVm): vms = [vm for vm in qvm_collection.values() if vm.has_firewall()] for vm in vms: + # Process only VMs connected to this ProxyVM + if not vm.netvm_vm or vm.netvm_vm.qid != self.qid: + continue + conf = vm.get_firewall_conf() xid = vm.get_xid() From 398734dad224f5fa99859ecf9f8fc030cf9cc43f Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Sun, 3 Apr 2011 17:47:20 +0200 Subject: [PATCH 11/12] Internal VMs (hidden in qubes-manager, menus etc) - used for DispVM template (#155) --- dom0/qvm-core/qubes.py | 13 +++++++++++-- dom0/qvm-tools/qvm-create | 5 +++++ dom0/restore/qvm-create-default-dvm | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 76f7ee74..76eb4a4b 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -181,6 +181,7 @@ class QubesVm(object): firewall_conf = None, volatile_img = None, pcidevs = None, + internal = False, vcpus = None): @@ -262,6 +263,9 @@ class QubesVm(object): else: self.vcpus = vcpus + # Internal VM (not shown in qubes-manager, doesn't create appmenus entries + self.internal = internal + if not dry_run and xend_session.session is not None: self.refresh_xend_session() @@ -928,6 +932,7 @@ class QubesVm(object): attrs["memory"] = str(self.memory) attrs["pcidevs"] = str(self.pcidevs) attrs["vcpus"] = str(self.vcpus) + attrs["internal"] = str(self.internal) return attrs def create_xml_element(self): @@ -1567,7 +1572,8 @@ class QubesAppVm(QubesVm): print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path) os.symlink (self.label.icon_path, self.icon_path) - self.create_appmenus (verbose) + if not self.internal: + self.create_appmenus (verbose) def remove_from_disk(self): if dry_run: @@ -1883,7 +1889,7 @@ class QubesVmCollection(dict): kwargs = {} common_attr_list = ("qid", "name", "dir_path", "conf_file", "private_img", "root_img", "template_qid", - "installed_by_rpm", "updateable", + "installed_by_rpm", "updateable", "internal" "uses_default_netvm", "label", "memory", "vcpus", "pcidevs") for attribute in common_attr_list: @@ -1896,6 +1902,9 @@ class QubesVmCollection(dict): if "installed_by_rpm" in kwargs: kwargs["installed_by_rpm"] = True if kwargs["installed_by_rpm"] == "True" else False + if "internal" in kwargs: + kwargs["internal"] = True if kwargs["internal"] == "True" else False + if "template_qid" in kwargs: if kwargs["template_qid"] == "none" or kwargs["template_qid"] is None: kwargs.pop("template_qid") diff --git a/dom0/qvm-tools/qvm-create b/dom0/qvm-tools/qvm-create index c4c80b97..c0f3f478 100755 --- a/dom0/qvm-tools/qvm-create +++ b/dom0/qvm-tools/qvm-create @@ -63,6 +63,8 @@ def main(): help="Initial memory size (in MB)") parser.add_option ("-c", "--vcpus", dest="vcpus", default=None, help="VCPUs count") + parser.add_option ("-i", "--internal", action="store_true", dest="internal", default=False, + help="Create VM for internal use only (hidden in qubes-manager, no appmenus)") parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) (options, args) = parser.parse_args () @@ -134,6 +136,9 @@ def main(): else: vm = qvm_collection.add_new_appvm(vmname, template_vm, label = label, updateable = options.standalone) + if options.internal: + vm.internal = True + if options.mem is not None: vm.memory = options.mem diff --git a/dom0/restore/qvm-create-default-dvm b/dom0/restore/qvm-create-default-dvm index c854c837..04ed97e1 100755 --- a/dom0/restore/qvm-create-default-dvm +++ b/dom0/restore/qvm-create-default-dvm @@ -32,7 +32,7 @@ DVMTMPLDIR="/var/lib/qubes/appvms/$DVMTMPL" if ! [ -d "$DVMTMPLDIR" ] ; then # unfortunately, currently there are reliability issues with save of a domain # with multiple CPUs - if ! qvm-create --vcpus=1 -t "$TEMPLATENAME" -l gray "$DVMTMPL" ; then exit 1 ; fi + if ! qvm-create --vcpus=1 --internal -t "$TEMPLATENAME" -l gray "$DVMTMPL" ; then exit 1 ; fi fi if ! /usr/lib/qubes/qubes_prepare_saved_domain.sh \ "$DVMTMPL" "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $SCRIPTNAME ; then From c10f7ef70b874d2df9d8e9e0a37851d71eed14bf Mon Sep 17 00:00:00 2001 From: Marek Marczykowski Date: Mon, 4 Apr 2011 00:08:24 +0200 Subject: [PATCH 12/12] Add missing coma (#155) --- dom0/qvm-core/qubes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom0/qvm-core/qubes.py b/dom0/qvm-core/qubes.py index 76eb4a4b..efc81daa 100755 --- a/dom0/qvm-core/qubes.py +++ b/dom0/qvm-core/qubes.py @@ -1889,7 +1889,7 @@ class QubesVmCollection(dict): kwargs = {} common_attr_list = ("qid", "name", "dir_path", "conf_file", "private_img", "root_img", "template_qid", - "installed_by_rpm", "updateable", "internal" + "installed_by_rpm", "updateable", "internal", "uses_default_netvm", "label", "memory", "vcpus", "pcidevs") for attribute in common_attr_list: