12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610 |
- #!/usr/bin/python2
- #
- # The Qubes OS Project, http://www.qubes-os.org
- #
- # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
- # Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- #
- #
- import datetime
- import fcntl
- import lxml.etree
- import os
- import os.path
- import re
- import shutil
- import subprocess
- import sys
- import time
- import uuid
- import xml.parsers.expat
- import xen.lowlevel.xc
- from qubes.qubes import xs,dry_run,xc,xl_ctx
- from qubes.qubes import register_qubes_vm_class
- from qubes.qubes import QubesVmCollection,QubesException,QubesHost,QubesVmLabels
- from qubes.qubes import defaults,system_path,vm_files,qubes_max_qid
- from qubes.qmemman_client import QMemmanClient
- class QubesVm(object):
- """
- A representation of one Qubes VM
- Only persistent information are stored here, while all the runtime
- information, e.g. Xen dom id, etc, are to be retrieved via Xen API
- Note that qid is not the same as Xen's domid!
- """
- # In which order load this VM type from qubes.xml
- load_order = 100
- # hooks for plugins (modules) which want to influence existing classes,
- # without introducing new ones
- hooks_clone_disk_files = []
- hooks_create_on_disk = []
- hooks_create_xenstore_entries = []
- hooks_get_attrs_config = []
- hooks_get_clone_attrs = []
- hooks_get_config_params = []
- hooks_init = []
- hooks_label_setter = []
- hooks_netvm_setter = []
- hooks_post_rename = []
- hooks_pre_rename = []
- hooks_remove_from_disk = []
- hooks_start = []
- hooks_verify_files = []
- def get_attrs_config(self):
- """ Object attributes for serialization/deserialization
- inner dict keys:
- - order: initialization order (to keep dependency intact)
- attrs without order will be evaluated at the end
- - default: default value used when attr not given to object constructor
- - attr: set value to this attribute instead of parameter name
- - eval: assign result of this expression instead of value directly;
- local variable 'value' contains attribute value (or default if it was not given)
- - save: use evaluation result as value for XML serialization; only attrs with 'save' key will be saved in XML
- - save_skip: if present and evaluates to true, attr will be omitted in XML
- - save_attr: save to this XML attribute instead of parameter name
- """
- attrs = {
- # __qid cannot be accessed by setattr, so must be set manually in __init__
- "qid": { "attr": "_qid", "order": 0 },
- "name": { "order": 1 },
- "dir_path": { "default": None, "order": 2 },
- "conf_file": { "eval": 'self.absolute_path(value, self.name + ".conf")', 'order': 3 },
- ### order >= 10: have base attrs set
- "root_img": { "eval": 'self.absolute_path(value, vm_files["root_img"])', 'order': 10 },
- "private_img": { "eval": 'self.absolute_path(value, vm_files["private_img"])', 'order': 10 },
- "volatile_img": { "eval": 'self.absolute_path(value, vm_files["volatile_img"])', 'order': 10 },
- "firewall_conf": { "eval": 'self.absolute_path(value, vm_files["firewall_conf"])', 'order': 10 },
- "installed_by_rpm": { "default": False, 'order': 10 },
- "template": { "default": None, 'order': 10 },
- ### order >= 20: have template set
- "uses_default_netvm": { "default": True, 'order': 20 },
- "netvm": { "default": None, "attr": "_netvm", 'order': 20 },
- "label": { "attr": "_label", "default": defaults["appvm_label"], 'order': 20,
- 'xml_deserialize': lambda _x: QubesVmLabels[_x] },
- "memory": { "default": defaults["memory"], 'order': 20 },
- "maxmem": { "default": None, 'order': 25 },
- "pcidevs": { "default": '[]', 'order': 25, "eval": \
- '[] if value in ["none", None] else eval(value) if value.find("[") >= 0 else eval("[" + value + "]")' },
- # Internal VM (not shown in qubes-manager, doesn't create appmenus entries
- "internal": { "default": False },
- "vcpus": { "default": None },
- "uses_default_kernel": { "default": True, 'order': 30 },
- "uses_default_kernelopts": { "default": True, 'order': 30 },
- "kernel": { "default": None, 'order': 31,
- 'eval': 'collection.get_default_kernel() if self.uses_default_kernel else value' },
- "kernelopts": { "default": "", 'order': 31, "eval": \
- 'value if not self.uses_default_kernelopts else defaults["kernelopts_pcidevs"] if len(self.pcidevs) > 0 else defaults["kernelopts"]' },
- "mac": { "attr": "_mac", "default": None },
- "include_in_backups": { "default": True },
- "services": { "default": {}, "eval": "eval(str(value))" },
- "debug": { "default": False },
- "default_user": { "default": "user" },
- "qrexec_timeout": { "default": 60 },
- ##### Internal attributes - will be overriden in __init__ regardless of args
- "config_file_template": { "eval": 'system_path["config_template_pv"]' },
- "icon_path": { "eval": 'os.path.join(self.dir_path, "icon.png") if self.dir_path is not None else None' },
- # used to suppress side effects of clone_attrs
- "_do_not_reset_firewall": { "eval": 'False' },
- "kernels_dir": { 'eval': 'os.path.join(system_path["qubes_kernels_base_dir"], self.kernel) if self.kernel is not None else ' + \
- # for backward compatibility (or another rare case): kernel=None -> kernel in VM dir
- 'os.path.join(self.dir_path, vm_files["kernels_subdir"])' },
- "_start_guid_first": { 'eval': 'False' },
- }
- ### Mark attrs for XML inclusion
- # Simple string attrs
- for prop in ['qid', 'name', 'dir_path', 'memory', 'maxmem', 'pcidevs', 'vcpus', 'internal',\
- 'uses_default_kernel', 'kernel', 'uses_default_kernelopts',\
- 'kernelopts', 'services', 'installed_by_rpm',\
- 'uses_default_netvm', 'include_in_backups', 'debug',\
- 'default_user', 'qrexec_timeout' ]:
- attrs[prop]['save'] = 'str(self.%s)' % prop
- # Simple paths
- for prop in ['conf_file', 'root_img', 'volatile_img', 'private_img']:
- attrs[prop]['save'] = 'self.relative_path(self.%s)' % prop
- attrs[prop]['save_skip'] = 'self.%s is None' % prop
- attrs['mac']['save'] = 'str(self._mac)'
- attrs['mac']['save_skip'] = 'self._mac is None'
- attrs['netvm']['save'] = 'str(self.netvm.qid) if self.netvm is not None else "none"'
- attrs['netvm']['save_attr'] = "netvm_qid"
- attrs['template']['save'] = 'str(self.template.qid) if self.template else "none"'
- attrs['template']['save_attr'] = "template_qid"
- attrs['label']['save'] = 'self.label.name'
- # fire hooks
- for hook in self.hooks_get_attrs_config:
- attrs = hook(self, attrs)
- return attrs
- def __basic_parse_xml_attr(self, value):
- if value is None:
- return None
- if value.lower() == "none":
- return None
- if value.lower() == "true":
- return True
- if value.lower() == "false":
- return False
- if value.isdigit():
- return int(value)
- return value
- def __init__(self, **kwargs):
- collection = None
- if 'collection' in kwargs:
- collection = kwargs['collection']
- else:
- raise ValueError("No collection given to QubesVM constructor")
- # Special case for template b/c it is given in "template_qid" property
- if "xml_element" in kwargs and kwargs["xml_element"].get("template_qid"):
- template_qid = kwargs["xml_element"].get("template_qid")
- if template_qid.lower() != "none":
- if int(template_qid) in collection:
- kwargs["template"] = collection[int(template_qid)]
- else:
- raise ValueError("Unknown template with QID %s" % template_qid)
- attrs = self.get_attrs_config()
- for attr_name in sorted(attrs, key=lambda _x: attrs[_x]['order'] if 'order' in attrs[_x] else 1000):
- attr_config = attrs[attr_name]
- attr = attr_name
- if 'attr' in attr_config:
- attr = attr_config['attr']
- value = None
- if attr_name in kwargs:
- value = kwargs[attr_name]
- elif 'xml_element' in kwargs and kwargs['xml_element'].get(attr_name) is not None:
- if 'xml_deserialize' in attr_config and callable(attr_config['xml_deserialize']):
- value = attr_config['xml_deserialize'](kwargs['xml_element'].get(attr_name))
- else:
- value = self.__basic_parse_xml_attr(kwargs['xml_element'].get(attr_name))
- else:
- if 'default' in attr_config:
- value = attr_config['default']
- if 'eval' in attr_config:
- setattr(self, attr, eval(attr_config['eval']))
- else:
- #print "setting %s to %s" % (attr, value)
- setattr(self, attr, value)
- #Init private attrs
- self.__qid = self._qid
- assert self.__qid < qubes_max_qid, "VM id out of bounds!"
- assert self.name is not None
- if not self.verify_name(self.name):
- raise QubesException("Invalid characters in VM name")
- if self.netvm is not None:
- self.netvm.connected_vms[self.qid] = self
- # Not in generic way to not create QubesHost() to frequently
- if self.maxmem is None:
- qubes_host = QubesHost()
- total_mem_mb = qubes_host.memory_total/1024
- self.maxmem = total_mem_mb/2
- # By default allow use all VCPUs
- if self.vcpus is None:
- qubes_host = QubesHost()
- self.vcpus = qubes_host.no_cpus
- # Always set if meminfo-writer should be active or not
- if 'meminfo-writer' not in self.services:
- self.services['meminfo-writer'] = not (len(self.pcidevs) > 0)
- # Additionally force meminfo-writer disabled when VM have PCI devices
- if len(self.pcidevs) > 0:
- self.services['meminfo-writer'] = False
- # Some additional checks for template based VM
- if self.template is not None:
- if not self.template.is_template():
- print >> sys.stderr, "ERROR: template_qid={0} doesn't point to a valid TemplateVM".\
- format(self.template.qid)
- return False
- self.template.appvms[self.qid] = self
- else:
- assert self.root_img is not None, "Missing root_img for standalone VM!"
- self.xid = -1
- self.xid = self.get_xid()
- # fire hooks
- for hook in self.hooks_init:
- hook(self)
- def absolute_path(self, arg, default):
- if arg is not None and os.path.isabs(arg):
- return arg
- else:
- return os.path.join(self.dir_path, (arg if arg is not None else default))
- def relative_path(self, arg):
- return arg.replace(self.dir_path + '/', '')
- @property
- def qid(self):
- return self.__qid
- @property
- def label(self):
- return self._label
- @label.setter
- def label(self, new_label):
- self._label = new_label
- if self.icon_path:
- try:
- os.remove(self.icon_path)
- except:
- pass
- os.symlink (new_label.icon_path, self.icon_path)
- subprocess.call(['sudo', 'xdg-icon-resource', 'forceupdate'])
- # fire hooks
- for hook in self.hooks_label_setter:
- hook(self, new_label)
- @property
- def netvm(self):
- return self._netvm
- # Don't know how properly call setter from base class, so workaround it...
- @netvm.setter
- def netvm(self, new_netvm):
- self._set_netvm(new_netvm)
- # fire hooks
- for hook in self.hooks_netvm_setter:
- hook(self, new_netvm)
- def _set_netvm(self, new_netvm):
- if self.is_running() and new_netvm is not None and not new_netvm.is_running():
- raise QubesException("Cannot dynamically attach to stopped NetVM")
- if self.netvm is not None:
- self.netvm.connected_vms.pop(self.qid)
- if self.is_running():
- subprocess.call(["xl", "network-detach", self.name, "0"], stderr=subprocess.PIPE)
- if hasattr(self.netvm, 'post_vm_net_detach'):
- self.netvm.post_vm_net_detach(self)
- if new_netvm is None:
- if not self._do_not_reset_firewall:
- # Set also firewall to block all traffic as discussed in #370
- if os.path.exists(self.firewall_conf):
- shutil.copy(self.firewall_conf, os.path.join(system_path["qubes_base_dir"],
- "backup", "%s-firewall-%s.xml" % (self.name,
- time.strftime('%Y-%m-%d-%H:%M:%S'))))
- self.write_firewall_conf({'allow': False, 'allowDns': False,
- 'allowIcmp': False, 'allowYumProxy': False, 'rules': []})
- else:
- new_netvm.connected_vms[self.qid]=self
- self._netvm = new_netvm
- if new_netvm is None:
- return
- if self.is_running():
- # refresh IP, DNS etc
- self.create_xenstore_entries()
- self.attach_network()
- if hasattr(self.netvm, 'post_vm_net_attach'):
- self.netvm.post_vm_net_attach(self)
- @property
- def ip(self):
- if self.netvm is not None:
- return self.netvm.get_ip_for_vm(self.qid)
- else:
- return None
- @property
- def netmask(self):
- if self.netvm is not None:
- return self.netvm.netmask
- else:
- return None
- @property
- def gateway(self):
- # This is gateway IP for _other_ VMs, so make sense only in NetVMs
- return None
- @property
- def secondary_dns(self):
- if self.netvm is not None:
- return self.netvm.secondary_dns
- else:
- return None
- @property
- def vif(self):
- if self.xid < 0:
- return None
- if self.netvm is None:
- return None
- return "vif{0}.+".format(self.xid)
- @property
- def mac(self):
- if self._mac is not None:
- return self._mac
- else:
- return "00:16:3E:5E:6C:{qid:02X}".format(qid=self.qid)
- @mac.setter
- def mac(self, new_mac):
- self._mac = new_mac
- @property
- def updateable(self):
- return self.template is None
- # Leaved for compatibility
- def is_updateable(self):
- return self.updateable
- def is_networked(self):
- if self.is_netvm():
- return True
- if self.netvm is not None:
- return True
- else:
- return False
- def verify_name(self, name):
- return re.match(r"^[a-zA-Z][a-zA-Z0-9_-]*$", name) is not None
- def pre_rename(self, new_name):
- # fire hooks
- for hook in self.hooks_pre_rename:
- hook(self, new_name)
- def set_name(self, name):
- if self.is_running():
- raise QubesException("Cannot change name of running VM!")
- if not self.verify_name(name):
- raise QubesException("Invalid characters in VM name")
- self.pre_rename(name)
- new_conf = os.path.join(self.dir_path, name + '.conf')
- if os.path.exists(self.conf_file):
- os.rename(self.conf_file, new_conf)
- old_dirpath = self.dir_path
- new_dirpath = os.path.join(os.path.dirname(self.dir_path), name)
- os.rename(old_dirpath, new_dirpath)
- self.dir_path = new_dirpath
- old_name = self.name
- self.name = name
- if self.private_img is not None:
- self.private_img = self.private_img.replace(old_dirpath, new_dirpath)
- if self.root_img is not None:
- self.root_img = self.root_img.replace(old_dirpath, new_dirpath)
- if self.volatile_img is not None:
- self.volatile_img = self.volatile_img.replace(old_dirpath, new_dirpath)
- if self.conf_file is not None:
- self.conf_file = new_conf.replace(old_dirpath, new_dirpath)
- if self.icon_path is not None:
- self.icon_path = self.icon_path.replace(old_dirpath, new_dirpath)
- if hasattr(self, 'kernels_dir') and self.kernels_dir is not None:
- self.kernels_dir = self.kernels_dir.replace(old_dirpath, new_dirpath)
- self.post_rename(old_name)
- def post_rename(self, old_name):
- # fire hooks
- for hook in self.hooks_post_rename:
- hook(self, old_name)
- def is_template(self):
- return False
- def is_appvm(self):
- return False
- def is_netvm(self):
- return False
- def is_proxyvm(self):
- return False
- def is_disposablevm(self):
- return False
- def get_xl_dominfo(self):
- if dry_run:
- return
- domains = xl_ctx.list_domains()
- for dominfo in domains:
- domname = xl_ctx.domid_to_name(dominfo.domid)
- if domname == self.name:
- return dominfo
- return None
- def get_xc_dominfo(self):
- if dry_run:
- return
- start_xid = self.xid
- if start_xid < 0:
- start_xid = 0
- try:
- domains = xc.domain_getinfo(start_xid, qubes_max_qid)
- except xen.lowlevel.xc.Error:
- return None
- for dominfo in domains:
- domname = xl_ctx.domid_to_name(dominfo['domid'])
- if domname == self.name:
- return dominfo
- return None
- def get_xid(self):
- if dry_run:
- return 666
- dominfo = self.get_xc_dominfo()
- if dominfo:
- self.xid = dominfo['domid']
- return self.xid
- else:
- return -1
- def get_uuid(self):
- dominfo = self.get_xl_dominfo()
- if dominfo:
- vmuuid = uuid.UUID(''.join('%02x' % b for b in dominfo.uuid))
- return vmuuid
- else:
- return None
- def get_mem(self):
- if dry_run:
- return 666
- dominfo = self.get_xc_dominfo()
- if dominfo:
- return dominfo['mem_kb']
- else:
- return 0
- def get_mem_static_max(self):
- if dry_run:
- return 666
- dominfo = self.get_xc_dominfo()
- if dominfo:
- return dominfo['maxmem_kb']
- else:
- return 0
- def get_per_cpu_time(self):
- if dry_run:
- import random
- return random.random() * 100
- dominfo = self.get_xc_dominfo()
- if dominfo:
- return dominfo['cpu_time']/dominfo['online_vcpus']
- else:
- return 0
- def get_disk_utilization_root_img(self):
- if not os.path.exists(self.root_img):
- return 0
- return self.get_disk_usage(self.root_img)
- def get_root_img_sz(self):
- if not os.path.exists(self.root_img):
- return 0
- return os.path.getsize(self.root_img)
- def get_power_state(self):
- if dry_run:
- return "NA"
- dominfo = self.get_xc_dominfo()
- if dominfo:
- if dominfo['paused']:
- return "Paused"
- elif dominfo['crashed']:
- return "Crashed"
- elif dominfo['shutdown']:
- if dominfo['shutdown_reason'] == 2:
- return "Suspended"
- else:
- return "Halting"
- elif dominfo['dying']:
- return "Dying"
- else:
- if not self.is_fully_usable():
- return "Transient"
- else:
- return "Running"
- else:
- return 'Halted'
- return "NA"
- def is_guid_running(self):
- xid = self.get_xid()
- if xid < 0:
- return False
- if not os.path.exists('/var/run/qubes/guid-running.%d' % xid):
- return False
- return True
- def is_fully_usable(self):
- # Running gui-daemon implies also VM running
- if not self.is_guid_running():
- return False
- # currently qrexec daemon doesn't cleanup socket in /var/run/qubes, so
- # it can be left from some other VM
- return True
- def is_running(self):
- # in terms of Xen and internal logic - starting VM is running
- if self.get_power_state() in ["Running", "Transient", "Halting"]:
- return True
- else:
- return False
- def is_paused(self):
- if self.get_power_state() == "Paused":
- return True
- else:
- return False
- def get_start_time(self):
- if not self.is_running():
- return None
- dominfo = self.get_xl_dominfo()
- uuid = self.get_uuid()
- start_time = xs.read('', "/vm/%s/start_time" % str(uuid))
- if start_time != '':
- return datetime.datetime.fromtimestamp(float(start_time))
- else:
- return None
- def is_outdated(self):
- # Makes sense only on VM based on template
- if self.template is None:
- return False
- if not self.is_running():
- return False
- rootimg_inode = os.stat(self.template.root_img)
- try:
- rootcow_inode = os.stat(self.template.rootcow_img)
- except OSError:
- # The only case when rootcow_img doesn't exists is in the middle of
- # commit_changes, so VM is outdated right now
- return True
- current_dmdev = "/dev/mapper/snapshot-{0:x}:{1}-{2:x}:{3}".format(
- rootimg_inode[2], rootimg_inode[1],
- rootcow_inode[2], rootcow_inode[1])
- # 51712 (0xCA00) is xvda
- # backend node name not available through xenapi :(
- used_dmdev = xs.read('', "/local/domain/0/backend/vbd/{0}/51712/node".format(self.get_xid()))
- return used_dmdev != current_dmdev
- def get_disk_usage(self, file_or_dir):
- if not os.path.exists(file_or_dir):
- return 0
- p = subprocess.Popen (["du", "-s", "--block-size=1", file_or_dir],
- stdout=subprocess.PIPE)
- result = p.communicate()
- m = re.match(r"^(\d+)\s.*", result[0])
- sz = int(m.group(1)) if m is not None else 0
- return sz
- def get_disk_utilization(self):
- return self.get_disk_usage(self.dir_path)
- def get_disk_utilization_private_img(self):
- return self.get_disk_usage(self.private_img)
- def get_private_img_sz(self):
- if not os.path.exists(self.private_img):
- return 0
- return os.path.getsize(self.private_img)
- def resize_private_img(self, size):
- assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
- f_private = open (self.private_img, "a+b")
- f_private.truncate (size)
- f_private.close ()
- retcode = 0
- if self.is_running():
- # find loop device
- p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
- stdout=subprocess.PIPE)
- result = p.communicate()
- m = re.match(r"^(/dev/loop\d+):\s", result[0])
- if m is None:
- raise QubesException("ERROR: Cannot find loop device!")
- loop_dev = m.group(1)
- # resize loop device
- subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
- retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
- "head /dev/xvdb > /dev/null; sleep 0.2; done; resize2fs /dev/xvdb", user="root", wait=True)
- else:
- retcode = subprocess.check_call(["sudo", "resize2fs", "-f", self.private_img])
- if retcode != 0:
- raise QubesException("resize2fs failed")
- # FIXME: should be outside of QubesVM?
- def get_timezone(self):
- # fc18
- if os.path.islink('/etc/localtime'):
- return '/'.join(os.readlink('/etc/localtime').split('/')[-2:])
- # <=fc17
- elif os.path.exists('/etc/sysconfig/clock'):
- clock_config = open('/etc/sysconfig/clock', "r")
- clock_config_lines = clock_config.readlines()
- clock_config.close()
- zone_re = re.compile(r'^ZONE="(.*)"')
- for line in clock_config_lines:
- line_match = zone_re.match(line)
- if line_match:
- return line_match.group(1)
- return None
- def cleanup_vifs(self):
- """
- Xend does not remove vif when backend domain is down, so we must do it
- manually
- """
- if not self.is_running():
- return
- dev_basepath = '/local/domain/%d/device/vif' % self.xid
- for dev in xs.ls('', dev_basepath):
- # check if backend domain is alive
- backend_xid = int(xs.read('', '%s/%s/backend-id' % (dev_basepath, dev)))
- if xl_ctx.domid_to_name(backend_xid) is not None:
- # check if device is still active
- if xs.read('', '%s/%s/state' % (dev_basepath, dev)) == '4':
- continue
- # remove dead device
- xs.rm('', '%s/%s' % (dev_basepath, dev))
- def create_xenstore_entries(self, xid = None):
- if dry_run:
- return
- if xid is None:
- xid = self.xid
- domain_path = xs.get_domain_path(xid)
- # Set Xen Store entires with VM networking info:
- xs.write('', "{0}/qubes-vm-type".format(domain_path),
- self.type)
- xs.write('', "{0}/qubes-vm-updateable".format(domain_path),
- str(self.updateable))
- if self.is_netvm():
- xs.write('',
- "{0}/qubes-netvm-gateway".format(domain_path),
- self.gateway)
- xs.write('',
- "{0}/qubes-netvm-secondary-dns".format(domain_path),
- self.secondary_dns)
- xs.write('',
- "{0}/qubes-netvm-netmask".format(domain_path),
- self.netmask)
- xs.write('',
- "{0}/qubes-netvm-network".format(domain_path),
- self.network)
- if self.netvm is not None:
- xs.write('', "{0}/qubes-ip".format(domain_path), self.ip)
- xs.write('', "{0}/qubes-netmask".format(domain_path),
- self.netvm.netmask)
- xs.write('', "{0}/qubes-gateway".format(domain_path),
- self.netvm.gateway)
- xs.write('',
- "{0}/qubes-secondary-dns".format(domain_path),
- self.netvm.secondary_dns)
- tzname = self.get_timezone()
- if tzname:
- xs.write('',
- "{0}/qubes-timezone".format(domain_path),
- tzname)
- for srv in self.services.keys():
- # convert True/False to "1"/"0"
- xs.write('', "{0}/qubes-service/{1}".format(domain_path, srv),
- str(int(self.services[srv])))
- xs.write('',
- "{0}/qubes-block-devices".format(domain_path),
- '')
- xs.write('',
- "{0}/qubes-usb-devices".format(domain_path),
- '')
- xs.write('', "{0}/qubes-debug-mode".format(domain_path),
- str(int(self.debug)))
- # Fix permissions
- xs.set_permissions('', '{0}/device'.format(domain_path),
- [{ 'dom': xid }])
- xs.set_permissions('', '{0}/memory'.format(domain_path),
- [{ 'dom': xid }])
- xs.set_permissions('', '{0}/qubes-block-devices'.format(domain_path),
- [{ 'dom': xid }])
- xs.set_permissions('', '{0}/qubes-usb-devices'.format(domain_path),
- [{ 'dom': xid }])
- # fire hooks
- for hook in self.hooks_create_xenstore_entries:
- hook(self, xid=xid)
- def get_rootdev(self, source_template=None):
- if self.template:
- return "'script:snapshot:{dir}/root.img:{dir}/root-cow.img,xvda,r',".format(dir=self.template.dir_path)
- else:
- return "'script:file:{dir}/root.img,xvda,w',".format(dir=self.dir_path)
- def get_config_params(self, source_template=None):
- args = {}
- args['name'] = self.name
- if hasattr(self, 'kernels_dir'):
- args['kerneldir'] = self.kernels_dir
- args['vmdir'] = self.dir_path
- args['pcidev'] = str(self.pcidevs).strip('[]')
- args['mem'] = str(self.memory)
- if self.maxmem < self.memory:
- args['mem'] = str(self.maxmem)
- args['maxmem'] = str(self.maxmem)
- if 'meminfo-writer' in self.services and not self.services['meminfo-writer']:
- # If dynamic memory management disabled, set maxmem=mem
- args['maxmem'] = args['mem']
- args['vcpus'] = str(self.vcpus)
- if self.netvm is not None:
- args['ip'] = self.ip
- args['mac'] = self.mac
- args['gateway'] = self.netvm.gateway
- args['dns1'] = self.netvm.gateway
- args['dns2'] = self.secondary_dns
- args['netmask'] = self.netmask
- args['netdev'] = "'mac={mac},script=/etc/xen/scripts/vif-route-qubes,ip={ip}".format(ip=self.ip, mac=self.mac)
- if self.netvm.qid != 0:
- args['netdev'] += ",backend={0}".format(self.netvm.name)
- args['netdev'] += "'"
- args['disable_network'] = '';
- else:
- args['ip'] = ''
- args['mac'] = ''
- args['gateway'] = ''
- args['dns1'] = ''
- args['dns2'] = ''
- args['netmask'] = ''
- args['netdev'] = ''
- args['disable_network'] = '#';
- args['rootdev'] = self.get_rootdev(source_template=source_template)
- args['privatedev'] = "'script:file:{dir}/private.img,xvdb,w',".format(dir=self.dir_path)
- args['volatiledev'] = "'script:file:{dir}/volatile.img,xvdc,w',".format(dir=self.dir_path)
- if hasattr(self, 'kernel'):
- modulesmode='r'
- if self.kernel is None:
- modulesmode='w'
- args['otherdevs'] = "'script:file:{dir}/modules.img,xvdd,{mode}',".format(dir=self.kernels_dir, mode=modulesmode)
- if hasattr(self, 'kernelopts'):
- args['kernelopts'] = self.kernelopts
- if self.debug:
- print >> sys.stderr, "--> Debug mode: adding 'earlyprintk=xen' to kernel opts"
- args['kernelopts'] += ' earlyprintk=xen'
- # fire hooks
- for hook in self.hooks_get_config_params:
- args = hook(self, args)
- return args
- @property
- def uses_custom_config(self):
- return self.conf_file != self.absolute_path(self.name + ".conf", None)
- def create_config_file(self, file_path = None, source_template = None, prepare_dvm = False):
- if file_path is None:
- file_path = self.conf_file
- if self.uses_custom_config:
- return
- if source_template is None:
- source_template = self.template
- f_conf_template = open(self.config_file_template, 'r')
- conf_template = f_conf_template.read()
- f_conf_template.close()
- template_params = self.get_config_params(source_template)
- if prepare_dvm:
- template_params['name'] = '%NAME%'
- template_params['privatedev'] = ''
- template_params['netdev'] = re.sub(r"ip=[0-9.]*", "ip=%IP%", template_params['netdev'])
- conf_appvm = open(file_path, "w")
- conf_appvm.write(conf_template.format(**template_params))
- conf_appvm.close()
- def create_on_disk(self, verbose, source_template = None):
- if source_template is None:
- source_template = self.template
- assert source_template is not None
- if dry_run:
- return
- if verbose:
- print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
- os.mkdir (self.dir_path)
- if verbose:
- print >> sys.stderr, "--> Creating the VM config file: {0}".format(self.conf_file)
- self.create_config_file(source_template = source_template)
- template_priv = source_template.private_img
- if verbose:
- print >> sys.stderr, "--> 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.updateable:
- template_root = source_template.root_img
- if verbose:
- print >> sys.stderr, "--> 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 = source_template.kernels_dir
- if verbose:
- print >> sys.stderr, "--> Copying the kernel (set kernel \"none\" to use it): {0}".\
- format(kernels_dir)
- os.mkdir (self.dir_path + '/kernels')
- for f in ("vmlinuz", "initramfs", "modules.img"):
- shutil.copy(os.path.join(kernels_dir, f),
- os.path.join(self.dir_path, vm_files["kernels_subdir"], f))
- # Create volatile.img
- self.reset_volatile_storage(source_template = source_template, verbose=verbose)
- if verbose:
- print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
- os.symlink (self.label.icon_path, self.icon_path)
- # fire hooks
- for hook in self.hooks_create_on_disk:
- hook(self, verbose, source_template=source_template)
- def get_clone_attrs(self):
- attrs = ['kernel', 'uses_default_kernel', 'netvm', 'uses_default_netvm', \
- 'memory', 'maxmem', 'kernelopts', 'uses_default_kernelopts', 'services', 'vcpus', \
- '_mac', 'pcidevs', 'include_in_backups', '_label']
- # fire hooks
- for hook in self.hooks_get_clone_attrs:
- attrs = hook(self, attrs)
- return attrs
- def clone_attrs(self, src_vm):
- self._do_not_reset_firewall = True
- for prop in self.get_clone_attrs():
- setattr(self, prop, getattr(src_vm, prop))
- self._do_not_reset_firewall = False
- def clone_disk_files(self, src_vm, verbose):
- if dry_run:
- return
- if src_vm.is_running():
- raise QubesException("Attempt to clone a running VM!")
- if verbose:
- print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
- os.mkdir (self.dir_path)
- if src_vm.private_img is not None and self.private_img is not None:
- if verbose:
- print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
- format(src_vm.private_img, self.private_img)
- # We prefer to use Linux's cp, because it nicely handles sparse files
- retcode = subprocess.call (["cp", src_vm.private_img, self.private_img])
- if retcode != 0:
- raise IOError ("Error while copying {0} to {1}".\
- format(src_vm.private_img, self.private_img))
- if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
- if verbose:
- print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
- format(src_vm.root_img, self.root_img)
- # We prefer to use Linux's cp, because it nicely handles sparse files
- retcode = subprocess.call (["cp", src_vm.root_img, self.root_img])
- if retcode != 0:
- raise IOError ("Error while copying {0} to {1}".\
- format(src_vm.root_img, self.root_img))
- if src_vm.icon_path is not None and self.icon_path is not None:
- if os.path.exists (src_vm.dir_path):
- if os.path.islink(src_vm.icon_path):
- icon_path = os.readlink(src_vm.icon_path)
- if verbose:
- print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, icon_path)
- os.symlink (icon_path, self.icon_path)
- else:
- if verbose:
- print >> sys.stderr, "--> Copying icon: {0} -> {1}".format(src_vm.icon_path, self.icon_path)
- shutil.copy(src_vm.icon_path, self.icon_path)
- # fire hooks
- for hook in self.hooks_clone_disk_files:
- hook(self, src_vm, verbose)
- 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 self.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))
- if not os.path.exists (os.path.join(self.kernels_dir, 'vmlinuz')):
- raise QubesException (
- "VM kernel does not exists: {0}".\
- format(os.path.join(self.kernels_dir, 'vmlinuz')))
- if not os.path.exists (os.path.join(self.kernels_dir, 'initramfs')):
- raise QubesException (
- "VM initramfs does not exists: {0}".\
- format(os.path.join(self.kernels_dir, 'initramfs')))
- if not os.path.exists (os.path.join(self.kernels_dir, 'modules.img')):
- raise QubesException (
- "VM kernel modules image does not exists: {0}".\
- format(os.path.join(self.kernels_dir, 'modules.img')))
- # fire hooks
- for hook in self.hooks_verify_files:
- hook(self)
- return True
- def reset_volatile_storage(self, source_template = None, verbose = False):
- assert not self.is_running(), "Attempt to clean volatile image of running VM!"
- if source_template is None:
- source_template = self.template
- # Only makes sense on template based VM
- if source_template is None:
- # For StandaloneVM create it only if not already exists (eg after backup-restore)
- if not os.path.exists(self.volatile_img):
- if verbose:
- print >> sys.stderr, "--> Creating volatile image: {0}...".format (self.volatile_img)
- f_root = open (self.root_img, "r")
- f_root.seek(0, os.SEEK_END)
- root_size = f_root.tell()
- f_root.close()
- subprocess.check_call([system_path["prepare_volatile_img_cmd"], self.volatile_img, str(root_size / 1024 / 1024)])
- return
- if verbose:
- print >> sys.stderr, "--> 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", source_template.clean_volatile_img, "-C", self.dir_path])
- if retcode != 0:
- raise IOError ("Error while unpacking {0} to {1}".\
- format(source_template.clean_volatile_img, self.volatile_img))
- def remove_from_disk(self):
- if dry_run:
- return
- # fire hooks
- for hook in self.hooks_remove_from_disk:
- hook(self)
- shutil.rmtree (self.dir_path)
- def write_firewall_conf(self, conf):
- defaults = self.get_firewall_conf()
- for item in defaults.keys():
- if item not in conf:
- conf[item] = defaults[item]
- root = lxml.etree.Element(
- "QubesFirwallRules",
- policy = "allow" if conf["allow"] else "deny",
- dns = "allow" if conf["allowDns"] else "deny",
- icmp = "allow" if conf["allowIcmp"] else "deny",
- yumProxy = "allow" if conf["allowYumProxy"] else "deny"
- )
- for rule in conf["rules"]:
- # For backward compatibility
- if "proto" not in rule:
- if rule["portBegin"] is not None and rule["portBegin"] > 0:
- rule["proto"] = "tcp"
- else:
- rule["proto"] = "any"
- element = lxml.etree.Element(
- "rule",
- address=rule["address"],
- proto=str(rule["proto"]),
- )
- if rule["netmask"] is not None and rule["netmask"] != 32:
- element.set("netmask", str(rule["netmask"]))
- if rule["portBegin"] is not None and rule["portBegin"] > 0:
- element.set("port", str(rule["portBegin"]))
- if rule["portEnd"] is not None and rule["portEnd"] > 0:
- element.set("toport", str(rule["portEnd"]))
- root.append(element)
- tree = lxml.etree.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, encoding="UTF-8", pretty_print=True)
- fcntl.lockf(f, fcntl.LOCK_UN)
- f.close()
- except EnvironmentError as err:
- print >> sys.stderr, "{0}: save error: {1}".format(
- os.path.basename(sys.argv[0]), err)
- return False
- # Automatically enable/disable 'yum-proxy-setup' service based on allowYumProxy
- if conf['allowYumProxy']:
- self.services['yum-proxy-setup'] = True
- else:
- if self.services.has_key('yum-proxy-setup'):
- self.services.pop('yum-proxy-setup')
- return True
- def has_firewall(self):
- return os.path.exists (self.firewall_conf)
- def get_firewall_defaults(self):
- return { "rules": list(), "allow": True, "allowDns": True, "allowIcmp": True, "allowYumProxy": False }
- def get_firewall_conf(self):
- conf = self.get_firewall_defaults()
- try:
- tree = lxml.etree.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")
- conf["allowYumProxy"] = (root.get("yumProxy") == "allow")
- for element in root:
- rule = {}
- attr_list = ("address", "netmask", "proto", "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
- if rule["port"] is not None:
- rule["portBegin"] = int(rule["port"])
- else:
- # backward compatibility
- rule["portBegin"] = 0
- # For backward compatibility
- if rule["proto"] is None:
- if rule["portBegin"] > 0:
- rule["proto"] = "tcp"
- else:
- rule["proto"] = "any"
- 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 pci_add(self, pci):
- if not os.path.exists('/sys/bus/pci/devices/0000:%s' % pci):
- raise QubesException("Invalid PCI device: %s" % pci)
- if self.pcidevs.count(pci):
- # already added
- return
- self.pcidevs.append(pci)
- if self.is_running():
- try:
- subprocess.check_call(['sudo', system_path["qubes_pciback_cmd"], pci])
- subprocess.check_call(['sudo', 'xl', 'pci-attach', str(self.xid), pci])
- except Exception as e:
- print >>sys.stderr, "Failed to attach PCI device on the fly " \
- "(%s), changes will be seen after VM restart" % str(e)
- def pci_remove(self, pci):
- if not self.pcidevs.count(pci):
- # not attached
- return
- self.pcidevs.remove(pci)
- if self.is_running():
- p = subprocess.Popen(['xl', 'pci-list', str(self.xid)],
- stdout=subprocess.PIPE)
- result = p.communicate()
- m = re.search(r"^(\d+.\d+)\s+0000:%s$" % pci, result[0], flags=re.MULTILINE)
- if not m:
- print >>sys.stderr, "Device %s already detached" % pci
- return
- vmdev = m.group(1)
- try:
- self.run("QUBESRPC qubes.DetachPciDevice dom0", user="root",
- localcmd="echo 00:%s" % vmdev, wait=True)
- subprocess.check_call(['sudo', 'xl', 'pci-detach', str(self.xid), pci])
- except Exception as e:
- print >>sys.stderr, "Failed to detach PCI device on the fly " \
- "(%s), changes will be seen after VM restart" % str(e)
- def run(self, command, user = None, verbose = True, autostart = False, notify_function = None, passio = False, passio_popen = False, passio_stderr=False, ignore_stderr=False, localcmd = None, wait = False, gui = True):
- """command should be in form 'cmdline'
- When passio_popen=True, popen object with stdout connected to pipe.
- When additionally passio_stderr=True, stderr also is connected to pipe.
- When ignore_stderr=True, stderr is connected to /dev/null.
- """
- if user is None:
- user = self.default_user
- null = None
- if not self.is_running() and not self.is_paused():
- if not autostart:
- raise QubesException("VM not running")
- try:
- if notify_function is not None:
- notify_function ("info", "Starting the '{0}' VM...".format(self.name))
- elif verbose:
- print >> sys.stderr, "Starting the VM '{0}'...".format(self.name)
- xid = self.start(verbose=verbose, start_guid = gui, notify_function=notify_function)
- except (IOError, OSError, QubesException) as err:
- raise QubesException("Error while starting the '{0}' VM: {1}".format(self.name, err))
- except (MemoryError) as err:
- raise QubesException("Not enough memory to start '{0}' VM! Close one or more running VMs and try again.".format(self.name))
- xid = self.get_xid()
- if gui and os.getenv("DISPLAY") is not None and not self.is_guid_running():
- self.start_guid(verbose = verbose, notify_function = notify_function)
- args = [system_path["qrexec_client_path"], "-d", str(xid), "%s:%s" % (user, command)]
- if localcmd is not None:
- args += [ "-l", localcmd]
- if passio:
- os.execv(system_path["qrexec_client_path"], args)
- exit(1)
- call_kwargs = {}
- if ignore_stderr:
- null = open("/dev/null", "w")
- call_kwargs['stderr'] = null
- if passio_popen:
- popen_kwargs={'stdout': subprocess.PIPE}
- popen_kwargs['stdin'] = subprocess.PIPE
- if passio_stderr:
- popen_kwargs['stderr'] = subprocess.PIPE
- else:
- popen_kwargs['stderr'] = call_kwargs.get('stderr', None)
- p = subprocess.Popen (args, **popen_kwargs)
- if null:
- null.close()
- return p
- if not wait:
- args += ["-e"]
- retcode = subprocess.call(args, **call_kwargs)
- if null:
- null.close()
- return retcode
- def attach_network(self, verbose = False, wait = True, netvm = None):
- if dry_run:
- return
- if not self.is_running():
- raise QubesException ("VM not running!")
- if netvm is None:
- netvm = self.netvm
- if netvm is None:
- raise QubesException ("NetVM not set!")
- if netvm.qid != 0:
- if not netvm.is_running():
- if verbose:
- print >> sys.stderr, "--> Starting NetVM {0}...".format(netvm.name)
- netvm.start()
- xs_path = '/local/domain/%d/device/vif/0/state' % (self.xid)
- if xs.read('', xs_path) is not None:
- # TODO: check its state and backend state (this can be stale vif after NetVM restart)
- if verbose:
- print >> sys.stderr, "NOTICE: Network already attached"
- return
- xm_cmdline = ["/usr/sbin/xl", "network-attach", str(self.xid), "script=/etc/xen/scripts/vif-route-qubes", "ip="+self.ip, "backend="+netvm.name ]
- retcode = subprocess.call (xm_cmdline)
- if retcode != 0:
- print >> sys.stderr, ("WARNING: Cannot attach to network to '{0}'!".format(self.name))
- if wait:
- tries = 0
- while xs.read('', xs_path) != '4':
- tries += 1
- if tries > 50:
- raise QubesException ("Network attach timed out!")
- time.sleep(0.2)
- def wait_for_session(self, notify_function = None):
- #self.run('echo $$ >> /tmp/qubes-session-waiter; [ ! -f /tmp/qubes-session-env ] && exec sleep 365d', ignore_stderr=True, gui=False, wait=True)
- # Note : User root is redefined to SYSTEM in the Windows agent code
- p = self.run('QUBESRPC qubes.WaitForSession none', user="root", passio_popen=True, gui=False, wait=True)
- p.communicate(input=self.default_user)
- def start_guid(self, verbose = True, notify_function = None):
- if verbose:
- print >> sys.stderr, "--> Starting Qubes GUId..."
- xid = self.get_xid()
- guid_cmd = [system_path["qubes_guid_path"], "-d", str(xid), "-c", self.label.color, "-i", self.label.icon_path, "-l", str(self.label.index)]
- if self.debug:
- guid_cmd += ['-v', '-v']
- retcode = subprocess.call (guid_cmd)
- if (retcode != 0) :
- raise QubesException("Cannot start qubes-guid!")
- if verbose:
- print >> sys.stderr, "--> Sending monitor layout..."
- try:
- subprocess.call([system_path["monitor_layout_notify_cmd"], self.name])
- except Exception as e:
- print >>sys.stderr, "ERROR: %s" % e
- if verbose:
- print >> sys.stderr, "--> Waiting for qubes-session..."
- self.wait_for_session(notify_function)
- def start_qrexec_daemon(self, verbose = False, notify_function = None):
- if verbose:
- print >> sys.stderr, "--> Starting the qrexec daemon..."
- xid = self.get_xid()
- qrexec_env = os.environ
- qrexec_env['QREXEC_STARTUP_TIMEOUT'] = str(self.qrexec_timeout)
- retcode = subprocess.call ([system_path["qrexec_daemon_path"], str(xid), self.name, self.default_user], env=qrexec_env)
- if (retcode != 0) :
- self.force_shutdown(xid=xid)
- raise OSError ("ERROR: Cannot execute qrexec-daemon!")
- def start(self, debug_console = False, verbose = False, preparing_dvm = False, start_guid = True, notify_function = None):
- if dry_run:
- return
- # Intentionally not used is_running(): eliminate also "Paused", "Crashed", "Halting"
- if self.get_power_state() != "Halted":
- raise QubesException ("VM is already running!")
- self.verify_files()
- if self.netvm is not None:
- if self.netvm.qid != 0:
- if not self.netvm.is_running():
- if verbose:
- print >> sys.stderr, "--> Starting NetVM {0}...".format(self.netvm.name)
- self.netvm.start(verbose = verbose, start_guid = start_guid, notify_function = notify_function)
- self.reset_volatile_storage(verbose=verbose)
- if verbose:
- print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
- # refresh config file
- self.create_config_file()
- mem_required = int(self.memory) * 1024 * 1024
- qmemman_client = QMemmanClient()
- try:
- got_memory = qmemman_client.request_memory(mem_required)
- except IOError as e:
- raise IOError("ERROR: Failed to connect to qmemman: %s" % str(e))
- if not got_memory:
- qmemman_client.close()
- raise MemoryError ("ERROR: insufficient memory to start VM '%s'" % self.name)
- # Bind pci devices to pciback driver
- for pci in self.pcidevs:
- try:
- subprocess.check_call(['sudo', system_path["qubes_pciback_cmd"], pci])
- except subprocess.CalledProcessError:
- raise QubesException("Failed to prepare PCI device %s" % pci)
- xl_cmdline = ['sudo', '/usr/sbin/xl', 'create', self.conf_file, '-q', '-p']
- try:
- subprocess.check_call(xl_cmdline)
- except:
- raise QubesException("Failed to load VM config")
- xid = self.get_xid()
- self.xid = xid
- if preparing_dvm:
- self.services['qubes-dvm'] = True
- if verbose:
- print >> sys.stderr, "--> Setting Xen Store info for the VM..."
- self.create_xenstore_entries(xid)
- qvm_collection = QubesVmCollection()
- qvm_collection.lock_db_for_reading()
- qvm_collection.load()
- qvm_collection.unlock_db()
- if verbose:
- print >> sys.stderr, "--> Updating firewall rules..."
- for vm in qvm_collection.values():
- if vm.is_proxyvm() and vm.is_running():
- vm.write_iptables_xenstore_entry()
- # fire hooks
- for hook in self.hooks_start:
- hook(self, verbose = verbose, preparing_dvm = preparing_dvm,
- start_guid = start_guid, notify_function = notify_function)
- if verbose:
- print >> sys.stderr, "--> Starting the VM..."
- xc.domain_unpause(xid)
- # close() is not really needed, because the descriptor is close-on-exec
- # anyway, the reason to postpone close() is that possibly xl is not done
- # constructing the domain after its main process exits
- # so we close() when we know the domain is up
- # the successful unpause is some indicator of it
- qmemman_client.close()
- if self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'):
- self.start_guid(verbose=verbose,notify_function=notify_function)
- if not preparing_dvm:
- self.start_qrexec_daemon(verbose=verbose,notify_function=notify_function)
- if not self._start_guid_first and start_guid and not preparing_dvm and os.path.exists('/var/run/shm.id'):
- self.start_guid(verbose=verbose,notify_function=notify_function)
- if preparing_dvm:
- if verbose:
- print >> sys.stderr, "--> Preparing config template for DispVM"
- self.create_config_file(file_path = self.dir_path + '/dvm.conf', prepare_dvm = True)
- # perhaps we should move it before unpause and fork?
- # FIXME: this uses obsolete xm api
- if debug_console:
- from xen.xm import console
- if verbose:
- print >> sys.stderr, "--> Starting debug console..."
- console.execConsole (xid)
- return xid
- def shutdown(self, force=False, xid = None):
- if dry_run:
- return
- if not self.is_running():
- raise QubesException ("VM already stopped!")
- subprocess.call (['/usr/sbin/xl', 'shutdown', str(xid) if xid is not None else self.name])
- #xc.domain_destroy(self.get_xid())
- def force_shutdown(self, xid = None):
- if dry_run:
- return
- if not self.is_running() and not self.is_paused():
- raise QubesException ("VM already stopped!")
- subprocess.call (['/usr/sbin/xl', 'destroy', str(xid) if xid is not None else self.name])
- def suspend(self):
- if dry_run:
- return
- if not self.is_running() and not self.is_paused():
- raise QubesException ("VM already stopped!")
- if len (self.pcidevs) > 0:
- xs_path = '/local/domain/%d/control/shutdown' % self.get_xid()
- xs.write('', xs_path, 'suspend')
- tries = 0
- while self.get_power_state() != "Suspended":
- tries += 1
- if tries > 15:
- # fallback to pause
- print >>sys.stderr, "Failed to suspend domain %s, falling back to pause method" % self.name
- self.pause()
- break
- time.sleep(0.2)
- else:
- self.pause()
- def resume(self):
- if dry_run:
- return
- xc_info = self.get_xc_dominfo()
- if not xc_info:
- raise QubesException ("VM isn't started (cannot get xc_dominfo)!")
- if xc_info['shutdown_reason'] == 2:
- # suspended
- xc.domain_resume(xc_info['domid'], 1)
- else:
- self.unpause()
- def pause(self):
- if dry_run:
- return
- xc.domain_pause(self.get_xid())
- def unpause(self):
- if dry_run:
- return
- xc.domain_unpause(self.get_xid())
- def get_xml_attrs(self):
- attrs = {}
- attrs_config = self.get_attrs_config()
- for attr in attrs_config:
- attr_config = attrs_config[attr]
- if 'save' in attr_config:
- if 'save_skip' in attr_config and eval(attr_config['save_skip']):
- continue
- if 'save_attr' in attr_config:
- attrs[attr_config['save_attr']] = eval(attr_config['save'])
- else:
- attrs[attr] = eval(attr_config['save'])
- return attrs
- def create_xml_element(self):
- # Compatibility hack (Qubes*VM in type vs Qubes*Vm in XML)...
- rx_type = re.compile (r"VM")
- attrs = self.get_xml_attrs()
- element = lxml.etree.Element(
- "Qubes" + rx_type.sub("Vm", self.type),
- **attrs)
- return element
- register_qubes_vm_class(QubesVm)
|