Merge branch 'master' of git.qubes-os.org:/var/lib/qubes/git/marmarek/core
This commit is contained in:
		
						commit
						3f31a5f3a7
					
				| @ -178,6 +178,11 @@ class QubesVm(object): | |||||||
|                  root_img = None, |                  root_img = None, | ||||||
|                  private_img = None, |                  private_img = None, | ||||||
|                  memory = default_memory, |                  memory = default_memory, | ||||||
|  |                  template_vm = None, | ||||||
|  |                  firewall_conf = None, | ||||||
|  |                  volatile_img = None, | ||||||
|  |                  pcidevs = None, | ||||||
|  |                  internal = False, | ||||||
|                  vcpus = None): |                  vcpus = None): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -200,9 +205,6 @@ class QubesVm(object): | |||||||
|         self.uses_default_netvm = uses_default_netvm |         self.uses_default_netvm = uses_default_netvm | ||||||
|         self.netvm_vm = netvm_vm |         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) |         # We use it in remove from disk to avoid removing rpm files (for templates) | ||||||
|         self.installed_by_rpm = installed_by_rpm |         self.installed_by_rpm = installed_by_rpm | ||||||
| 
 | 
 | ||||||
| @ -221,6 +223,11 @@ class QubesVm(object): | |||||||
|             self.private_img = dir_path + "/" + ( |             self.private_img = dir_path + "/" + ( | ||||||
|                 private_img if private_img is not None else default_private_img) |                 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.updateable = updateable | ||||||
|         self.label = label if label is not None else QubesVmLabels["red"] |         self.label = label if label is not None else QubesVmLabels["red"] | ||||||
|         if self.dir_path is not None: |         if self.dir_path is not None: | ||||||
| @ -229,10 +236,27 @@ class QubesVm(object): | |||||||
|             self.icon_path = None |             self.icon_path = None | ||||||
| 
 | 
 | ||||||
|         # PCI devices - used only by NetVM |         # PCI devices - used only by NetVM | ||||||
|  |         if pcidevs is None or pcidevs == "none": | ||||||
|             self.pcidevs = "" |             self.pcidevs = "" | ||||||
|  |         else: | ||||||
|  |             self.pcidevs  = pcidevs | ||||||
| 
 | 
 | ||||||
|         self.memory = memory |         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 | ||||||
|  |             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!" | ||||||
|  | 
 | ||||||
|         # By default allow use all VCPUs |         # By default allow use all VCPUs | ||||||
|         if vcpus is None: |         if vcpus is None: | ||||||
|             qubes_host = QubesHost() |             qubes_host = QubesHost() | ||||||
| @ -240,6 +264,9 @@ class QubesVm(object): | |||||||
|         else: |         else: | ||||||
|             self.vcpus = vcpus |             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: |         if not dry_run and xend_session.session is not None: | ||||||
|             self.refresh_xend_session() |             self.refresh_xend_session() | ||||||
| 
 | 
 | ||||||
| @ -287,14 +314,17 @@ class QubesVm(object): | |||||||
|         else: |         else: | ||||||
|             return False |             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): |     def set_nonupdateable(self): | ||||||
|         if not self.is_updateable(): |         if not self.is_updateable(): | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         assert not self.is_running() |         raise QubesException ("Change 'updateable' flag is not supported. Please use qvm-create.") | ||||||
|         # We can always downgrade a VM to non-updateable... |  | ||||||
|         self.updateable = False |  | ||||||
| 
 | 
 | ||||||
|     def is_template(self): |     def is_template(self): | ||||||
|         return isinstance(self, QubesTemplateVm) |         return isinstance(self, QubesTemplateVm) | ||||||
| @ -455,6 +485,30 @@ class QubesVm(object): | |||||||
|         else: |         else: | ||||||
|             return False |             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): |     def get_disk_usage(self, file_or_dir): | ||||||
|         if not os.path.exists(file_or_dir): |         if not os.path.exists(file_or_dir): | ||||||
|             return 0 |             return 0 | ||||||
| @ -535,6 +589,220 @@ class QubesVm(object): | |||||||
|                 "/local/domain/{0}/qubes_secondary_dns".format(xid), |                 "/local/domain/{0}/qubes_secondary_dns".format(xid), | ||||||
|                 self.netvm_vm.secondary_dns]) |                 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): |     def get_total_xen_memory(self): | ||||||
|         hosts = xend_session.session.xenapi.host.get_all() |         hosts = xend_session.session.xenapi.host.get_all() | ||||||
| @ -550,6 +818,8 @@ class QubesVm(object): | |||||||
|         if self.is_running(): |         if self.is_running(): | ||||||
|             raise QubesException ("VM is already running!") |             raise QubesException ("VM is already running!") | ||||||
| 
 | 
 | ||||||
|  |         self.reset_volatile_storage() | ||||||
|  | 
 | ||||||
|         if verbose: |         if verbose: | ||||||
|             print "--> Rereading the VM's conf file ({0})...".format(self.conf_file) |             print "--> Rereading the VM's conf file ({0})...".format(self.conf_file) | ||||||
|         self.update_xen_storage() |         self.update_xen_storage() | ||||||
| @ -665,10 +935,13 @@ class QubesVm(object): | |||||||
|         attrs["uses_default_netvm"] = str(self.uses_default_netvm) |         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["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["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["updateable"] = str(self.updateable) | ||||||
|         attrs["label"] = self.label.name |         attrs["label"] = self.label.name | ||||||
|         attrs["memory"] = str(self.memory) |         attrs["memory"] = str(self.memory) | ||||||
|  |         attrs["pcidevs"] = str(self.pcidevs) | ||||||
|         attrs["vcpus"] = str(self.vcpus) |         attrs["vcpus"] = str(self.vcpus) | ||||||
|  |         attrs["internal"] = str(self.internal) | ||||||
|         return attrs |         return attrs | ||||||
| 
 | 
 | ||||||
|     def create_xml_element(self): |     def create_xml_element(self): | ||||||
| @ -948,196 +1221,7 @@ class QubesTemplateVm(QubesVm): | |||||||
|         attrs["rootcow_img"] = self.rootcow_img |         attrs["rootcow_img"] = self.rootcow_img | ||||||
|         return attrs |         return attrs | ||||||
| 
 | 
 | ||||||
| class QubesCowVm(QubesVm): | class QubesNetVm(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): |  | ||||||
|     """ |     """ | ||||||
|     A class that represents a NetVM. A child of QubesCowVM. |     A class that represents a NetVM. A child of QubesCowVM. | ||||||
|     """ |     """ | ||||||
| @ -1306,8 +1390,12 @@ class QubesProxyVm(QubesNetVm): | |||||||
|         qvm_collection.load() |         qvm_collection.load() | ||||||
|         qvm_collection.unlock_db() |         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: |         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() |             conf = vm.get_firewall_conf() | ||||||
| 
 | 
 | ||||||
|             xid = vm.get_xid() |             xid = vm.get_xid() | ||||||
| @ -1447,19 +1535,10 @@ class QubesDisposableVm(QubesVm): | |||||||
|     """ |     """ | ||||||
|     def __init__(self, **kwargs): |     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) |         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 |     @property | ||||||
|     def type(self): |     def type(self): | ||||||
| @ -1477,19 +1556,16 @@ class QubesDisposableVm(QubesVm): | |||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class QubesAppVm(QubesCowVm): | class QubesAppVm(QubesVm): | ||||||
|     """ |     """ | ||||||
|     A class that represents an AppVM. A child of QubesVm. |     A class that represents an AppVM. A child of QubesVm. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, **kwargs): |     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) |         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 |     @property | ||||||
|     def type(self): |     def type(self): | ||||||
| @ -1505,8 +1581,16 @@ class QubesAppVm(QubesCowVm): | |||||||
|             print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path) |             print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path) | ||||||
|         os.symlink (self.label.icon_path, self.icon_path) |         os.symlink (self.label.icon_path, self.icon_path) | ||||||
| 
 | 
 | ||||||
|  |         if not self.internal: | ||||||
|             self.create_appmenus (verbose) |             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): |     def create_appmenus(self, verbose): | ||||||
|         if self.template_vm is not None: |         if self.template_vm is not None: | ||||||
|             subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name]) |             subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name]) | ||||||
| @ -1514,88 +1598,6 @@ class QubesAppVm(QubesCowVm): | |||||||
|             # Only add apps to menu |             # Only add apps to menu | ||||||
|             subprocess.check_call ([qubes_appmenu_create_cmd, "none", self.name]) |             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): | class QubesVmCollection(dict): | ||||||
|     """ |     """ | ||||||
| @ -1896,8 +1898,8 @@ class QubesVmCollection(dict): | |||||||
|         kwargs = {} |         kwargs = {} | ||||||
|         common_attr_list = ("qid", "name", "dir_path", "conf_file", |         common_attr_list = ("qid", "name", "dir_path", "conf_file", | ||||||
|                 "private_img", "root_img", "template_qid", |                 "private_img", "root_img", "template_qid", | ||||||
|                 "installed_by_rpm", "updateable", |                 "installed_by_rpm", "updateable", "internal", | ||||||
|                 "uses_default_netvm", "label") |                 "uses_default_netvm", "label", "memory", "vcpus", "pcidevs") | ||||||
| 
 | 
 | ||||||
|         for attribute in common_attr_list: |         for attribute in common_attr_list: | ||||||
|             kwargs[attribute] = element.get(attribute) |             kwargs[attribute] = element.get(attribute) | ||||||
| @ -1909,6 +1911,9 @@ class QubesVmCollection(dict): | |||||||
|         if "installed_by_rpm" in kwargs: |         if "installed_by_rpm" in kwargs: | ||||||
|             kwargs["installed_by_rpm"] = True if kwargs["installed_by_rpm"] == "True" else False |             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 "template_qid" in kwargs: | ||||||
|             if kwargs["template_qid"] == "none" or kwargs["template_qid"] is None: |             if kwargs["template_qid"] == "none" or kwargs["template_qid"] is None: | ||||||
|                 kwargs.pop("template_qid") |                 kwargs.pop("template_qid") | ||||||
|  | |||||||
| @ -127,6 +127,9 @@ def main(): | |||||||
|     parser.add_option ("--skip-conflicting", action="store_true", dest="skip_conflicting", default=False, |     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") |                        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, |     parser.add_option ("--recreate-conf-files", action="store_true", dest="recreate_conf", default=False, | ||||||
|                        help="Recreate conf files after restore") |                        help="Recreate conf files after restore") | ||||||
| 
 | 
 | ||||||
| @ -242,6 +245,15 @@ def main(): | |||||||
| 
 | 
 | ||||||
|     print |     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: |     if there_are_conflicting_vms: | ||||||
|         print "*** There VMs with conflicting names on the host! ***" |         print "*** There VMs with conflicting names on the host! ***" | ||||||
|         if options.skip_conflicting: |         if options.skip_conflicting: | ||||||
|  | |||||||
| @ -63,6 +63,8 @@ def main(): | |||||||
|                         help="Initial memory size (in MB)") |                         help="Initial memory size (in MB)") | ||||||
|     parser.add_option ("-c", "--vcpus", dest="vcpus", default=None, |     parser.add_option ("-c", "--vcpus", dest="vcpus", default=None, | ||||||
|                         help="VCPUs count") |                         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) |     parser.add_option ("-q", "--quiet", action="store_false", dest="verbose", default=True) | ||||||
|     (options, args) = parser.parse_args () |     (options, args) = parser.parse_args () | ||||||
| @ -134,6 +136,9 @@ def main(): | |||||||
|     else: |     else: | ||||||
|         vm = qvm_collection.add_new_appvm(vmname, template_vm, label = label, updateable = options.standalone) |         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: |     if options.mem is not None: | ||||||
|         vm.memory = options.mem |         vm.memory = options.mem | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -124,6 +124,15 @@ def vm_run_cmd(vm, cmd, options): | |||||||
|                 if options.tray: |                 if options.tray: | ||||||
|                     tray_notify_error ("ERROR: Cannot start the GUI daemon for this VM!") |                     tray_notify_error ("ERROR: Cannot start the GUI daemon for this VM!") | ||||||
|                 exit (1) |                 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); |         actually_execute(str(xid), cmd, options); | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|  | |||||||
| @ -32,7 +32,7 @@ DVMTMPLDIR="/var/lib/qubes/appvms/$DVMTMPL" | |||||||
| if ! [ -d "$DVMTMPLDIR" ] ; then | if ! [ -d "$DVMTMPLDIR" ] ; then | ||||||
| 	# unfortunately, currently there are reliability issues with save of a domain | 	# unfortunately, currently there are reliability issues with save of a domain | ||||||
| 	# with multiple CPUs | 	# 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 | fi | ||||||
| if ! /usr/lib/qubes/qubes_prepare_saved_domain.sh \ | if ! /usr/lib/qubes/qubes_prepare_saved_domain.sh \ | ||||||
| 	"$DVMTMPL" "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $SCRIPTNAME ; then | 	"$DVMTMPL" "/var/lib/qubes/appvms/$DVMTMPL/dvm-savefile" $SCRIPTNAME ; then | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Joanna Rutkowska
						Joanna Rutkowska