qubes.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  1. #!/usr/bin/python2.6
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2010 Joanna Rutkowska <joanna@invisiblethingslab.com>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. #
  21. #
  22. import sys
  23. import os
  24. import os.path
  25. import subprocess
  26. import xml.etree.ElementTree
  27. import xml.parsers.expat
  28. import fcntl
  29. import re
  30. import shutil
  31. # Do not use XenAPI or create/read any VM files
  32. # This is for testing only!
  33. dry_run = False
  34. #dry_run = True
  35. if not dry_run:
  36. # Xen API
  37. import xmlrpclib
  38. from xen.xm import XenAPI
  39. from xen.xend import sxp
  40. if dry_run:
  41. qubes_base_dir = "."
  42. else:
  43. qubes_base_dir = "/var/lib/qubes"
  44. qubes_appvms_dir = qubes_base_dir + "/appvms"
  45. qubes_templates_dir = qubes_base_dir + "/vm-templates"
  46. qubes_servicevms_dir = qubes_base_dir + "/servicevms"
  47. qubes_store_filename = qubes_base_dir + "/qubes.xml"
  48. qubes_max_qid = 254*254
  49. qubes_max_netid = 254
  50. vm_default_netmask = "255.255.0.0"
  51. default_root_img = "root.img"
  52. default_rootcow_img = "root-cow.img"
  53. default_swapcow_img = "swap-cow.img"
  54. default_private_img = "private.img"
  55. default_appvms_conf_file = "appvm-template.conf"
  56. default_templatevm_conf_template = "templatevm.conf" # needed for TemplateVM cloning
  57. default_appmenus_templates_subdir = "apps.templates"
  58. default_kernels_subdir = "kernels"
  59. # do not allow to start a new AppVM if Dom0 mem was to be less than this
  60. dom0_min_memory = 700*1024*1024
  61. # We need this global reference, as each instance of QubesVM
  62. # must be able to ask Dom0 VM about how much memory it currently has...
  63. dom0_vm = None
  64. qubes_appmenu_create_cmd = "/usr/lib/qubes/create_apps_for_appvm.sh"
  65. qubes_appmenu_remove_cmd = "/usr/lib/qubes/remove_appvm_appmenus.sh"
  66. # TODO: we should detect the actual size of the AppVM's swap partition
  67. # rather than using this ugly hardcoded value, which was choosen here
  68. # as "should be good for everyone"
  69. swap_cow_sz = 1024*1024*1024
  70. VM_TEMPLATE = 'TempleteVM'
  71. VM_APPVM = 'AppVM'
  72. VM_NETVM = 'NetVM'
  73. def get_xend_session_old_api():
  74. from xen.xend import XendClient
  75. from xen.util.xmlrpcclient import ServerProxy
  76. xend_server = ServerProxy(XendClient.uri)
  77. return xend_server
  78. def get_xend_session_new_api():
  79. xend_socket_uri = "httpu:///var/run/xend/xen-api.sock"
  80. session = XenAPI.Session (xend_socket_uri)
  81. session.login_with_password ("", "")
  82. return session
  83. class QubesException (Exception) : pass
  84. class QubesVmLabel(object):
  85. def __init__(self, name, color = None, icon = None):
  86. self.name = name
  87. self.color = color if color is not None else name
  88. self.icon = icon if icon is not None else name
  89. self.icon_path = "/usr/share/qubes/icons/" + self.icon + ".png"
  90. # Globally defined lables
  91. QubesVmLabels = {
  92. "yellow" : QubesVmLabel ("yellow"),
  93. "orange" : QubesVmLabel ("orange"),
  94. "red" : QubesVmLabel ("red"),
  95. "purple" : QubesVmLabel ("purple"),
  96. "green" : QubesVmLabel ("green", color="0x5fa05e"),
  97. "blue" : QubesVmLabel ("blue"),
  98. "gray" : QubesVmLabel ("gray"),
  99. "black" : QubesVmLabel ("black"),
  100. }
  101. default_appvm_label = QubesVmLabels["red"]
  102. default_template_label = QubesVmLabels["gray"]
  103. default_servicevm_label = QubesVmLabels["red"]
  104. class QubesVm(object):
  105. """
  106. A representation of one Qubes VM
  107. Only persistent information are stored here, while all the runtime
  108. information, e.g. Xen dom id, etc, are to be retrieved via Xen API
  109. Note that qid is not the same as Xen's domid!
  110. """
  111. def __init__(self, qid, name, type,
  112. dir_path, conf_file = None,
  113. uses_default_netvm = True,
  114. netvm_vm = None,
  115. installed_by_rpm = False,
  116. updateable = False,
  117. label = None):
  118. assert qid < qubes_max_qid, "VM id out of bounds!"
  119. self.__qid = qid
  120. self.name = name
  121. dir_path = dir_path
  122. self.dir_path = dir_path
  123. conf_file = conf_file
  124. if self.dir_path is not None:
  125. if (conf_file is None):
  126. self.conf_file = dir_path + "/" + name + ".conf"
  127. else:
  128. if os.path.isabs(conf_file):
  129. self.conf_file = conf_file
  130. else:
  131. self.conf_file = dir_path + "/" + conf_file
  132. self.__type = type
  133. self.uses_default_netvm = uses_default_netvm
  134. self.netvm_vm = netvm_vm
  135. # We use it in remove from disk to avoid removing rpm files (for templates and netvms)
  136. self.installed_by_rpm = installed_by_rpm
  137. self.updateable = updateable
  138. self.label = label if label is not None else QubesVmLabels["red"]
  139. self.icon_path = self.dir_path + "/icon.png"
  140. @property
  141. def qid(self):
  142. return self.__qid
  143. @property
  144. def type(self):
  145. return self.__type
  146. @property
  147. def ip(self):
  148. if self.netvm_vm is not None:
  149. return self.netvm_vm.get_ip_for_vm(self.qid)
  150. else:
  151. return None
  152. @property
  153. def netmask(self):
  154. if self.netvm_vm is not None:
  155. return self.netvm_vm.netmask
  156. else:
  157. return None
  158. @property
  159. def gateway(self):
  160. if self.netvm_vm is not None:
  161. return self.netvm_vm.gateway
  162. else:
  163. return None
  164. def is_updateable(self):
  165. return self.updateable
  166. def is_networked(self):
  167. if self.is_netvm():
  168. return True
  169. if self.netvm_vm is not None:
  170. return True
  171. else:
  172. return False
  173. def set_nonupdateable(self):
  174. if not self.is_updateable():
  175. return
  176. assert not self.is_running()
  177. # We can always downgrade a VM to non-updateable...
  178. self.updateable = False
  179. def is_templete(self):
  180. if self.type == VM_TEMPLATE:
  181. return True
  182. else:
  183. return False
  184. def is_appvm(self):
  185. if self.type == VM_APPVM:
  186. return True
  187. else:
  188. return False
  189. def is_netvm(self):
  190. if self.type == VM_NETVM:
  191. return True
  192. else:
  193. return False
  194. def add_to_xen_storage(self):
  195. if dry_run:
  196. return
  197. retcode = subprocess.call (["/usr/sbin/xm", "new", "-q", self.conf_file])
  198. if retcode != 0:
  199. raise OSError ("Cannot add VM '{0}' to Xen Store!".format(self.name))
  200. return True
  201. def remove_from_xen_storage(self):
  202. if dry_run:
  203. return
  204. retcode = subprocess.call (["/usr/sbin/xm", "delete", self.name])
  205. if retcode != 0:
  206. raise OSError ("Cannot remove VM '{0}' from Xen Store!".format(self.name))
  207. self.in_xen_storage = False
  208. def update_xen_storage(self):
  209. self.remove_from_xen_storage()
  210. self.add_to_xen_storage()
  211. def get_xid(self):
  212. if dry_run:
  213. return 666
  214. session = get_xend_session_new_api()
  215. uuids = session.xenapi.VM.get_by_name_label (self.name)
  216. uuid = uuids[0]
  217. xid = int (session.xenapi.VM.get_domid (uuid))
  218. return xid
  219. def get_mem(self):
  220. if dry_run:
  221. return 666
  222. session = get_xend_session_new_api()
  223. uuids = session.xenapi.VM.get_by_name_label (self.name)
  224. uuid = uuids[0]
  225. metrics = session.xenapi.VM.get_metrics(uuid)
  226. mem = int (session.xenapi.VM_metrics.get_memory_actual (metrics))
  227. return mem
  228. def get_mem_static_max(self):
  229. if dry_run:
  230. return 666
  231. session = get_xend_session_new_api()
  232. uuids = session.xenapi.VM.get_by_name_label (self.name)
  233. uuid = uuids[0]
  234. mem = int(session.xenapi.VM.get_memory_static_max(uuid))
  235. return mem
  236. def get_cpu_total_load(self):
  237. if dry_run:
  238. import random
  239. return random.random() * 100
  240. session = get_xend_session_new_api()
  241. uuids = session.xenapi.VM.get_by_name_label (self.name)
  242. uuid = uuids[0]
  243. metrics = session.xenapi.VM.get_metrics(uuid)
  244. cpus_util = session.xenapi.VM_metrics.get_VCPUs_utilisation (metrics)
  245. if len (cpus_util) == 0:
  246. return 0
  247. cpu_total_load = 0.0
  248. for cpu in cpus_util:
  249. cpu_total_load += cpus_util[cpu]
  250. cpu_total_load /= len(cpus_util)
  251. p = 100*cpu_total_load
  252. if p > 100:
  253. p = 100
  254. return p
  255. def get_power_state(self):
  256. if dry_run:
  257. return "NA"
  258. session = get_xend_session_new_api()
  259. uuids = session.xenapi.VM.get_by_name_label (self.name)
  260. if len (uuids) == 0:
  261. return "NA"
  262. uuid = uuids[0]
  263. power_state = session.xenapi.VM.get_power_state (uuid)
  264. return power_state
  265. def is_running(self):
  266. if self.get_power_state() == "Running":
  267. return True
  268. else:
  269. return False
  270. def get_disk_usage(self, file_or_dir):
  271. if not os.path.exists(file_or_dir):
  272. return 0
  273. p = subprocess.Popen (["du", "-s", "--block-size=1", file_or_dir],
  274. stdout=subprocess.PIPE)
  275. result = p.communicate()
  276. m = re.match(r"^(\d+)\s.*", result[0])
  277. sz = int(m.group(1)) if m is not None else 0
  278. return sz
  279. def get_disk_utilization(self):
  280. return self.get_disk_usage(self.dir_path)
  281. def get_disk_utilization_private_img(self):
  282. return self.get_disk_usage(self.private_img)
  283. def get_private_img_sz(self):
  284. if not os.path.exists(self.private_img):
  285. return 0
  286. return os.path.getsize(self.private_img)
  287. def create_xenstore_entries(self, xid):
  288. if dry_run:
  289. return
  290. # Set Xen Store entires with VM networking info:
  291. retcode = subprocess.check_call ([
  292. "/usr/bin/xenstore-write",
  293. "/local/domain/{0}/qubes_vm_type".format(xid),
  294. self.type])
  295. if self.is_netvm():
  296. retcode = subprocess.check_call ([
  297. "/usr/bin/xenstore-write",
  298. "/local/domain/{0}/qubes_netvm_gateway".format(xid),
  299. self.gateway])
  300. retcode = subprocess.check_call ([
  301. "/usr/bin/xenstore-write",
  302. "/local/domain/{0}/qubes_netvm_netmask".format(xid),
  303. self.netmask])
  304. retcode = subprocess.check_call ([
  305. "/usr/bin/xenstore-write",
  306. "/local/domain/{0}/qubes_netvm_network".format(xid),
  307. self.network])
  308. elif self.netvm_vm is not None:
  309. retcode = subprocess.check_call ([
  310. "/usr/bin/xenstore-write",
  311. "/local/domain/{0}/qubes_ip".format(xid),
  312. self.ip])
  313. retcode = subprocess.check_call ([
  314. "/usr/bin/xenstore-write",
  315. "/local/domain/{0}/qubes_netmask".format(xid),
  316. self.netmask])
  317. retcode = subprocess.check_call ([
  318. "/usr/bin/xenstore-write",
  319. "/local/domain/{0}/qubes_gateway".format(xid),
  320. self.gateway])
  321. else:
  322. pass
  323. def start(self, debug_console = False, verbose = False):
  324. if dry_run:
  325. return
  326. if self.is_running():
  327. raise QubesException ("VM is already running!")
  328. if verbose:
  329. print "--> Rereading the VM's conf file ({0})...".format(self.conf_file)
  330. self.update_xen_storage()
  331. session = get_xend_session_new_api()
  332. uuids = session.xenapi.VM.get_by_name_label (self.name)
  333. uuid = uuids[0]
  334. if verbose:
  335. print "--> Loading the VM (type = {0})...".format(self.type)
  336. mem_required = self.get_mem_static_max()
  337. dom0_mem = dom0_vm.get_mem()
  338. dom0_mem_new = dom0_mem - mem_required
  339. if verbose:
  340. print "--> AppVM required mem : {0}".format(mem_required)
  341. print "--> Dom0 mem after launch : {0}".format(dom0_mem_new)
  342. if dom0_mem_new < dom0_min_memory:
  343. raise MemoryError ("ERROR: starting this VM would cause Dom0 memory to go below {0}B".format(dom0_min_memory))
  344. session.xenapi.VM.start (uuid, True) # Starting a VM paused
  345. xid = int (session.xenapi.VM.get_domid (uuid))
  346. if verbose:
  347. print "--> Setting Xen Store info for the VM..."
  348. self.create_xenstore_entries(xid)
  349. if not self.is_netvm() and self.netvm_vm is not None:
  350. assert self.netvm_vm is not None
  351. if verbose:
  352. print "--> Attaching to the network backend (netvm={0})...".format(self.netvm_vm.name)
  353. if self.netvm_vm.qid != 0:
  354. if not self.netvm_vm.is_running():
  355. print "ERROR: NetVM not running, please start it first"
  356. self.force_shutdown()
  357. raise QubesException ("NetVM not running")
  358. retcode = subprocess.call (["/usr/sbin/xm", "network-attach", self.name, "backend={0}".format(self.netvm_vm.name)])
  359. if retcode != 0:
  360. self.force_shutdown()
  361. raise OSError ("ERROR: Cannot attach to network backend!")
  362. else:
  363. retcode = subprocess.call (["/usr/sbin/xm", "network-attach", self.name, "backend=0"])
  364. if retcode != 0:
  365. self.force_shutdown()
  366. raise OSError ("ERROR: Cannot attach to network backend!")
  367. if verbose:
  368. print "--> Starting the VM..."
  369. session.xenapi.VM.unpause (uuid)
  370. # perhaps we should move it before unpause and fork?
  371. if debug_console:
  372. from xen.xm import console
  373. if verbose:
  374. print "--> Starting debug console..."
  375. console.execConsole (xid)
  376. return xid
  377. def force_shutdown(self):
  378. if dry_run:
  379. return
  380. session = get_xend_session_new_api()
  381. uuids = session.xenapi.VM.get_by_name_label (self.name)
  382. uuid = uuids[0]
  383. session.xenapi.VM.hard_shutdown (uuid)
  384. def remove_from_disk(self):
  385. if dry_run:
  386. return
  387. shutil.rmtree (self.dir_path)
  388. class QubesTemplateVm(QubesVm):
  389. """
  390. A class that represents an TemplateVM. A child of QubesVM.
  391. """
  392. def __init__(self, **kwargs):
  393. if "dir_path" not in kwargs or kwargs["dir_path"] is None:
  394. kwargs["dir_path"] = qubes_templates_dir + "/" + kwargs["name"]
  395. if "updateable" not in kwargs or kwargs["updateable"] is None :
  396. kwargs["updateable"] = True
  397. root_img = kwargs.pop("root_img") if "root_img" in kwargs else None
  398. private_img = kwargs.pop("private_img") if "private_img" in kwargs else None
  399. appvms_conf_file = kwargs.pop("appvms_conf_file") if "appvms_conf_file" in kwargs else None
  400. super(QubesTemplateVm, self).__init__(type=VM_TEMPLATE, label = default_template_label, **kwargs)
  401. dir_path = kwargs["dir_path"]
  402. # TempleteVM doesn't use root-cow image
  403. if root_img is not None and os.path.isabs(root_img):
  404. self.root_img = root_img
  405. else:
  406. self.root_img = dir_path + "/" + (
  407. root_img if root_img is not None else default_root_img)
  408. if private_img is not None and os.path.isabs(private_img):
  409. self.private_img = private_img
  410. else:
  411. self.private_img = dir_path + "/" + (
  412. private_img if private_img is not None else default_private_img)
  413. if appvms_conf_file is not None and os.path.isabs(appvms_conf_file):
  414. self.appvms_conf_file = appvms_conf_file
  415. else:
  416. self.appvms_conf_file = dir_path + "/" + (
  417. appvms_conf_file if appvms_conf_file is not None else default_appvms_conf_file)
  418. self.templatevm_conf_template = self.dir_path + "/" + default_templatevm_conf_template
  419. self.kernels_dir = self.dir_path + "/" + default_kernels_subdir
  420. self.appmenus_templates_dir = self.dir_path + "/" + default_appmenus_templates_subdir
  421. self.appvms = QubesVmCollection()
  422. def set_updateable(self):
  423. if self.is_updateable():
  424. return
  425. assert not self.is_running()
  426. # Make sure that all the AppVMs are non-updateable...
  427. for appvm in self.appvms.values():
  428. if appvm.is_updateable():
  429. raise QubesException("One of the AppVMs ('{0}')is also 'updateable'\
  430. -- cannot make the TempleteVM {'{1}'} 'nonupdatable'".\
  431. format (appvm.name, self.name))
  432. self.updateable = True
  433. def clone_disk_files(self, src_template_vm, verbose):
  434. if dry_run:
  435. return
  436. assert not src_template_vm.is_running(), "Attempt to clone a running Template VM!"
  437. if verbose:
  438. print "--> Creating directory: {0}".format(self.dir_path)
  439. os.mkdir (self.dir_path)
  440. if verbose:
  441. print "--> Copying the VM config file:\n{0} =*>\n{1}".\
  442. format(src_template_vm.templatevm_conf_template, self.conf_file)
  443. conf_templatevm_template = open (src_template_vm.templatevm_conf_template, "r")
  444. conf_file = open(self.conf_file, "w")
  445. rx_templatename = re.compile (r"%TEMPLATENAME%")
  446. for line in conf_templatevm_template:
  447. line = rx_templatename.sub (self.name, line)
  448. conf_file.write(line)
  449. conf_templatevm_template.close()
  450. conf_file.close()
  451. if verbose:
  452. print "--> Copying the VM config template :\n{0} ==>\n{1}".\
  453. format(src_template_vm.templatevm_conf_template, self.templatevm_conf_template)
  454. shutil.copy (src_template_vm.templatevm_conf_template, self.templatevm_conf_template)
  455. if verbose:
  456. print "--> Copying the VM config template :\n{0} ==>\n{1}".\
  457. format(src_template_vm.appvms_conf_file, self.appvms_conf_file)
  458. shutil.copy (src_template_vm.appvms_conf_file, self.appvms_conf_file)
  459. if verbose:
  460. print "--> Copying the template's private image:\n{0} ==>\n{1}".\
  461. format(src_template_vm.private_img, self.private_img)
  462. # We prefer to use Linux's cp, because it nicely handles sparse files
  463. retcode = subprocess.call (["cp", src_template_vm.private_img, self.private_img])
  464. retcode = 0
  465. if retcode != 0:
  466. raise IOError ("Error while copying {0} to {1}".\
  467. format(src_template_vm.private_img, self.private_img))
  468. if verbose:
  469. print "--> Copying the template's root image:\n{0} ==>\n{1}".\
  470. format(src_template_vm.root_img, self.root_img)
  471. # We prefer to use Linux's cp, because it nicely handles sparse files
  472. retcode = subprocess.call (["cp", src_template_vm.root_img, self.root_img])
  473. retcode = 0
  474. if retcode != 0:
  475. raise IOError ("Error while copying {0} to {1}".\
  476. format(src_template_vm.root_img, self.root_img))
  477. if verbose:
  478. print "--> Copying the template's kernel dir:\n{0} ==>\n{1}".\
  479. format(src_template_vm.kernels_dir, self.kernels_dir)
  480. shutil.copytree (src_template_vm.kernels_dir, self.kernels_dir)
  481. if verbose:
  482. print "--> Copying the template's appvm templates dir:\n{0} ==>\n{1}".\
  483. format(src_template_vm.appmenus_templates_dir, self.appmenus_templates_dir)
  484. shutil.copytree (src_template_vm.appmenus_templates_dir, self.appmenus_templates_dir)
  485. def get_disk_utilization_root_img(self):
  486. return self.get_disk_usage(self.root_img)
  487. def get_root_img_sz(self):
  488. if not os.path.exists(self.root_img):
  489. return 0
  490. return os.path.getsize(self.root_img)
  491. def verify_files(self):
  492. if dry_run:
  493. return
  494. if not os.path.exists (self.dir_path):
  495. raise QubesException (
  496. "VM directory doesn't exist: {0}".\
  497. format(self.dir_path))
  498. if not os.path.exists (self.conf_file):
  499. raise QubesException (
  500. "VM config file doesn't exist: {0}".\
  501. format(self.conf_file))
  502. if not os.path.exists (self.appvms_conf_file):
  503. raise QubesException (
  504. "Appvm template config file doesn't exist: {0}".\
  505. format(self.appvms_conf_file))
  506. if not os.path.exists (self.root_img):
  507. raise QubesException (
  508. "VM root image file doesn't exist: {0}".\
  509. format(self.root_img))
  510. if not os.path.exists (self.private_img):
  511. raise QubesException (
  512. "VM private image file doesn't exist: {0}".\
  513. format(self.private_img))
  514. if not os.path.exists (self.kernels_dir):
  515. raise QubesException (
  516. "VM's kernels directory does not exist: {0}".\
  517. format(self.kernels_dir))
  518. return True
  519. def start(self, debug_console = False, verbose = False):
  520. if dry_run:
  521. return
  522. if not self.is_updateable():
  523. raise QubesException ("Cannot start Template VM that is marked \"nonupdatable\"")
  524. # First ensure that none of our appvms is running:
  525. for appvm in self.appvms.values():
  526. if appvm.is_running():
  527. raise QubesException ("Cannot start TemplateVM when one of its AppVMs is running!")
  528. return super(QubesTemplateVm, self).start(debug_console=debug_console, verbose=verbose)
  529. def create_xml_element(self):
  530. element = xml.etree.ElementTree.Element(
  531. "QubesTemplateVm",
  532. qid=str(self.qid),
  533. name=self.name,
  534. dir_path=self.dir_path,
  535. conf_file=self.conf_file,
  536. appvms_conf_file=self.appvms_conf_file,
  537. root_img=self.root_img,
  538. private_img=self.private_img,
  539. uses_default_netvm=str(self.uses_default_netvm),
  540. netvm_qid=str(self.netvm_vm.qid) if self.netvm_vm is not None else "none",
  541. installed_by_rpm=str(self.installed_by_rpm),
  542. updateable=str(self.updateable),
  543. )
  544. return element
  545. class QubesServiceVm(QubesVm):
  546. """
  547. A class that represents a ServiceVM, e.g. NetVM. A child of QubesVM.
  548. """
  549. def __init__(self, **kwargs):
  550. if "dir_path" not in kwargs or kwargs["dir_path"] is None:
  551. kwargs["dir_path"] = qubes_servicevms_dir + "/" + kwargs["name"]
  552. root_img = kwargs.pop("root_img") if "root_img" in kwargs else None
  553. private_img = kwargs.pop("private_img") if "private_img" in kwargs else None
  554. kwargs["updateable"] = True
  555. super(QubesServiceVm, self).__init__(**kwargs)
  556. dir_path = kwargs["dir_path"]
  557. assert dir_path is not None
  558. if root_img is not None and os.path.isabs(root_img):
  559. self.root_img = root_img
  560. else:
  561. self.root_img = dir_path + "/" + (
  562. root_img if root_img is not None else default_root_img)
  563. if private_img is not None and os.path.isabs(private_img):
  564. self.private_img = private_img
  565. else:
  566. self.private_img = dir_path + "/" + (
  567. private_img if private_img is not None else default_private_img)
  568. def set_updateable(self):
  569. if self.is_updateable():
  570. return
  571. # ServiceVMs are standalone, we can always make it updateable
  572. # In fact there is no point in making it unpdatebale...
  573. # So, this is just for completncess
  574. assert not self.is_running()
  575. self.updateable = True
  576. def get_disk_utilization_root_img(self):
  577. return self.get_disk_usage(self.root_img)
  578. def get_root_img_sz(self):
  579. if not os.path.exists(self.root_img):
  580. return 0
  581. return os.path.getsize(self.root_img)
  582. def verify_files(self):
  583. if dry_run:
  584. return
  585. if not os.path.exists (self.dir_path):
  586. raise QubesException (
  587. "VM directory doesn't exist: {0}".\
  588. format(self.dir_path))
  589. if not os.path.exists (self.conf_file):
  590. raise QubesException (
  591. "VM config file doesn't exist: {0}".\
  592. format(self.conf_file))
  593. if not os.path.exists (self.root_img):
  594. raise QubesException (
  595. "VM root image file doesn't exist: {0}".\
  596. format(self.root_img))
  597. return True
  598. def create_xml_element(self):
  599. raise NotImplementedError
  600. class QubesNetVm(QubesServiceVm):
  601. """
  602. A class that represents a NetVM. A child of QubesServiceVM.
  603. """
  604. def __init__(self, **kwargs):
  605. netid = kwargs.pop("netid")
  606. self.netid = netid
  607. self.__network = "10.{0}.0.0".format(netid)
  608. self.netprefix = "10.{0}.".format(netid)
  609. self.__netmask = vm_default_netmask
  610. self.__gateway = self.netprefix + "0.1"
  611. if "label" not in kwargs or kwargs["label"] is None:
  612. kwargs["label"] = default_servicevm_label
  613. super(QubesNetVm, self).__init__(type=VM_NETVM, installed_by_rpm=True, **kwargs)
  614. @property
  615. def gateway(self):
  616. return self.__gateway
  617. @property
  618. def netmask(self):
  619. return self.__netmask
  620. @property
  621. def network(self):
  622. return self.__network
  623. def get_ip_for_vm(self, qid):
  624. hi = qid / 253
  625. lo = qid % 253 + 2
  626. assert hi >= 0 and hi <= 254 and lo >= 2 and lo <= 254, "Wrong IP address for VM"
  627. return self.netprefix + "{0}.{1}".format(hi,lo)
  628. def create_xml_element(self):
  629. element = xml.etree.ElementTree.Element(
  630. "QubesNetVm",
  631. qid=str(self.qid),
  632. netid=str(self.netid),
  633. name=self.name,
  634. dir_path=self.dir_path,
  635. conf_file=self.conf_file,
  636. root_img=self.root_img,
  637. private_img=self.private_img,
  638. installed_by_rpm=str(self.installed_by_rpm),
  639. )
  640. return element
  641. class QubesDom0NetVm(QubesNetVm):
  642. def __init__(self):
  643. super(QubesDom0NetVm, self).__init__(qid=0, name="dom0", netid=0,
  644. dir_path=None, root_img = None,
  645. private_img = None,
  646. label = default_template_label)
  647. def is_running(self):
  648. return True
  649. def get_cpu_total_load(self):
  650. if dry_run:
  651. import random
  652. return random.random() * 100
  653. session = get_xend_session_new_api()
  654. hosts = session.xenapi.host.get_all()
  655. cpus = session.xenapi.host.get_host_CPUs(hosts[0])
  656. cpu_total_load = 0.0
  657. for cpu in cpus:
  658. cpu_total_load += session.xenapi.host_cpu.get_utilisation(cpu)
  659. cpu_total_load /= len(cpus)
  660. p = 100*cpu_total_load
  661. if p > 100:
  662. p = 100
  663. return p
  664. def get_mem(self):
  665. # Unfortunately XenAPI provides only info about total memory, not the one actually usable by Dom0...
  666. #session = get_xend_session_new_api()
  667. #hosts = session.xenapi.host.get_all()
  668. #metrics = session.xenapi.host.get_metrics(hosts[0])
  669. #memory_total = int(session.xenapi.metrics.get_memory_total(metrics))
  670. # ... so we must read /proc/meminfo, just like free command does
  671. f = open ("/proc/meminfo")
  672. for line in f:
  673. match = re.match(r"^MemTotal\:\s*(\d+) kB", line)
  674. if match is not None:
  675. break
  676. f.close()
  677. assert match is not None
  678. return int(match.group(1))*1024
  679. def get_xid(self):
  680. return 0
  681. def get_power_state(self):
  682. return "Running"
  683. def get_disk_usage(self, file_or_dir):
  684. return 0
  685. def get_disk_utilization(self):
  686. return 0
  687. def get_disk_utilization_private_img(self):
  688. return 0
  689. def get_private_img_sz(self):
  690. return 0
  691. def start(self, debug_console = False, verbose = False):
  692. raise QubesException ("Cannot start Dom0 fake domain!")
  693. def create_xml_element(self):
  694. return None
  695. def verify_files(self):
  696. return True
  697. class QubesAppVm(QubesVm):
  698. """
  699. A class that represents an AppVM. A child of QubesVM.
  700. """
  701. def __init__(self, **kwargs):
  702. if "dir_path" not in kwargs or kwargs["dir_path"] is None:
  703. kwargs["dir_path"] = qubes_appvms_dir + "/" + kwargs["name"]
  704. if "updateable" not in kwargs or kwargs["updateable"] is None:
  705. kwargs["updateable"] = False
  706. private_img = kwargs.pop("private_img")
  707. template_vm = kwargs.pop("template_vm")
  708. super(QubesAppVm, self).__init__(type=VM_APPVM, **kwargs)
  709. qid = kwargs["qid"]
  710. dir_path = kwargs["dir_path"]
  711. assert template_vm is not None, "Missing template_vm for AppVM!"
  712. if not template_vm.is_templete():
  713. print "ERROR: template_qid={0} doesn't point to a valid TempleteVM".\
  714. format(new_vm.template_vm.qid)
  715. return False
  716. self.template_vm = template_vm
  717. template_vm.appvms[qid] = self
  718. # AppVM doesn't have its own root_img, it uses the one provided by the TemplateVM
  719. if private_img is not None and os.path.isabs(private_img):
  720. self.private_img = private_img
  721. else:
  722. self.private_img = dir_path + "/" + (
  723. private_img if private_img is not None else default_private_img)
  724. self.rootcow_img = dir_path + "/" + default_rootcow_img
  725. self.swapcow_img = dir_path + "/" + default_swapcow_img
  726. def set_updateable(self):
  727. if self.is_updateable():
  728. return
  729. assert not self.is_running()
  730. # Check if the TemaplteVM is *non* updatable...
  731. if not self.template_vm.is_updateable():
  732. self.updateable = True
  733. self.reset_cow_storage()
  734. else:
  735. # Temaplate VM is Updatable itself --> can't make the AppVM updateable too
  736. # as this would cause COW-backed storage incoherency
  737. raise QubesException ("TemaplteVM is updateable: cannot make the AppVM '{0}' updateable".format(self.name))
  738. def create_on_disk(self, verbose):
  739. if dry_run:
  740. return
  741. if verbose:
  742. print "--> Creating directory: {0}".format(self.dir_path)
  743. os.mkdir (self.dir_path)
  744. if verbose:
  745. print "--> Creating the VM config file: {0}".format(self.conf_file)
  746. conf_template = open (self.template_vm.appvms_conf_file, "r")
  747. conf_appvm = open(self.conf_file, "w")
  748. rx_vmname = re.compile (r"%VMNAME%")
  749. rx_vmdir = re.compile (r"%VMDIR%")
  750. rx_template = re.compile (r"%TEMPLATEDIR%")
  751. for line in conf_template:
  752. line = rx_vmname.sub (self.name, line)
  753. line = rx_vmdir.sub (self.dir_path, line)
  754. line = rx_template.sub (self.template_vm.dir_path, line)
  755. conf_appvm.write(line)
  756. conf_template.close()
  757. conf_appvm.close()
  758. template_priv = self.template_vm.private_img
  759. if verbose:
  760. print "--> Copying the template's private image: {0}".\
  761. format(template_priv)
  762. # We prefer to use Linux's cp, because it nicely handles sparse files
  763. retcode = subprocess.call (["cp", template_priv, self.private_img])
  764. if retcode != 0:
  765. raise IOError ("Error while copying {0} to {1}".\
  766. format(template_priv, self.private_img))
  767. if verbose:
  768. print "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
  769. os.symlink (self.label.icon_path, self.icon_path)
  770. subprocess.check_call ([qubes_appmenu_create_cmd, self.template_vm.appmenus_templates_dir, self.name])
  771. def get_disk_utilization_root_img(self):
  772. return 0
  773. def get_root_img_sz(self):
  774. return 0
  775. def verify_files(self):
  776. if dry_run:
  777. return
  778. if not os.path.exists (self.dir_path):
  779. raise QubesException (
  780. "VM directory doesn't exist: {0}".\
  781. format(self.dir_path))
  782. if not os.path.exists (self.conf_file):
  783. raise QubesException (
  784. "VM config file doesn't exist: {0}".\
  785. format(self.conf_file))
  786. if not os.path.exists (self.private_img):
  787. raise QubesException (
  788. "VM private image file doesn't exist: {0}".\
  789. format(self.private_img))
  790. return True
  791. def create_xml_element(self):
  792. element = xml.etree.ElementTree.Element(
  793. "QubesAppVm",
  794. qid=str(self.qid),
  795. name=self.name,
  796. dir_path=self.dir_path,
  797. conf_file=self.conf_file,
  798. template_qid=str(self.template_vm.qid),
  799. uses_default_netvm=str(self.uses_default_netvm),
  800. netvm_qid=str(self.netvm_vm.qid) if self.netvm_vm is not None else "none",
  801. private_img=self.private_img,
  802. installed_by_rpm=str(self.installed_by_rpm),
  803. updateable=str(self.updateable),
  804. label=self.label.name)
  805. return element
  806. def start(self, debug_console = False, verbose = False):
  807. if dry_run:
  808. return
  809. if self.is_running():
  810. raise QubesException("VM is already running!")
  811. # First ensure that our template is *not* running:
  812. if self.template_vm.is_running():
  813. raise QubesException ("Cannot start AppVM when its template is running!")
  814. if not self.is_updateable():
  815. self.reset_cow_storage()
  816. return super(QubesAppVm, self).start(debug_console=debug_console, verbose=verbose)
  817. def reset_cow_storage (self):
  818. print "--> Resetting the COW storage: {0}...".format (self.rootcow_img)
  819. if dry_run:
  820. return
  821. # this is probbaly not needed, as open (..., "w") should remove the previous file
  822. if os.path.exists (self.rootcow_img):
  823. os.remove (self.rootcow_img)
  824. f_cow = open (self.rootcow_img, "w")
  825. f_root = open (self.template_vm.root_img, "r")
  826. f_root.seek(0, os.SEEK_END)
  827. f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
  828. f_cow.close ()
  829. f_root.close()
  830. print "--> Resetting the COW storage: {0}...".format (self.swapcow_img)
  831. if os.path.exists (self.swapcow_img):
  832. os.remove (self.swapcow_img)
  833. f_swap_cow = open (self.swapcow_img, "w")
  834. f_swap_cow.truncate (swap_cow_sz)
  835. f_swap_cow.close()
  836. def remove_from_disk(self):
  837. if dry_run:
  838. return
  839. subprocess.check_call ([qubes_appmenu_remove_cmd, self.name])
  840. shutil.rmtree (self.dir_path)
  841. class QubesVmCollection(dict):
  842. """
  843. A collection of Qubes VMs indexed by Qubes id (qid)
  844. """
  845. def __init__(self):
  846. super(QubesVmCollection, self).__init__()
  847. self.default_netvm_qid = None
  848. self.default_template_qid = None
  849. def values(self):
  850. for qid in self.keys():
  851. yield self[qid]
  852. def items(self):
  853. for qid in self.keys():
  854. yield (qid, self[qid])
  855. def __iter__(self):
  856. for qid in sorted(super(QubesVmCollection, self).keys()):
  857. yield qid
  858. keys = __iter__
  859. def __setitem__(self, key, value):
  860. if key not in self:
  861. return super(QubesVmCollection, self).__setitem__(key, value)
  862. else:
  863. assert False, "Attempt to add VM with qid that already exists in the collection!"
  864. def add_new_appvm(self, name, template_vm,
  865. dir_path = None, conf_file = None,
  866. private_img = None,
  867. label = None):
  868. qid = self.get_new_unused_qid()
  869. vm = QubesAppVm (qid=qid, name=name, template_vm=template_vm,
  870. dir_path=dir_path, conf_file=conf_file,
  871. private_img=private_img,
  872. netvm_vm = self.get_default_netvm_vm(),
  873. label=label)
  874. if not self.verify_new_vm (vm):
  875. assert False, "Wrong VM description!"
  876. self[vm.qid]=vm
  877. return vm
  878. def add_new_templatevm(self, name,
  879. dir_path = None, conf_file = None,
  880. root_img = None, private_img = None,
  881. installed_by_rpm = True):
  882. qid = self.get_new_unused_qid()
  883. vm = QubesTemplateVm (qid=qid, name=name,
  884. dir_path=dir_path, conf_file=conf_file,
  885. root_img=root_img, private_img=private_img,
  886. installed_by_rpm=installed_by_rpm,
  887. netvm_vm = self.get_default_netvm_vm())
  888. if not self.verify_new_vm (vm):
  889. assert False, "Wrong VM description!"
  890. self[vm.qid]=vm
  891. if self.default_template_qid is None:
  892. self.set_default_template_vm(vm)
  893. return vm
  894. def clone_templatevm(self, src_template_vm, name, dir_path = None, verbose = False):
  895. assert not src_template_vm.is_running(), "Attempt to clone a running Template VM!"
  896. vm = self.add_new_templatevm (name=name, dir_path=dir_path, installed_by_rpm = False)
  897. return vm
  898. def add_new_netvm(self, name,
  899. dir_path = None, conf_file = None,
  900. root_img = None):
  901. qid = self.get_new_unused_qid()
  902. netid = self.get_new_unused_netid()
  903. vm = QubesNetVm (qid=qid, name=name,
  904. netid=netid,
  905. dir_path=dir_path, conf_file=conf_file,
  906. root_img=root_img)
  907. if not self.verify_new_vm (vm):
  908. assert False, "Wrong VM description!"
  909. self[vm.qid]=vm
  910. if self.default_netvm_qid is None:
  911. self.set_default_netvm_vm(vm)
  912. return vm
  913. def set_default_template_vm(self, vm):
  914. assert vm.is_templete(), "VM {0} is not a TempleteVM!".format(vm.name)
  915. self.default_template_qid = vm.qid
  916. def get_default_template_vm(self):
  917. if self.default_template_qid is None:
  918. return None
  919. else:
  920. return self[self.default_template_qid]
  921. def set_default_netvm_vm(self, vm):
  922. assert vm.is_netvm(), "VM {0} is not a NetVM!".format(vm.name)
  923. self.default_netvm_qid = vm.qid
  924. def get_default_netvm_vm(self):
  925. if self.default_netvm_qid is None:
  926. return None
  927. else:
  928. return self[self.default_netvm_qid]
  929. def get_vm_by_name(self, name):
  930. for vm in self.values():
  931. if (vm.name == name):
  932. return vm
  933. return None
  934. def get_qid_by_name(self, name):
  935. vm = self.get_vm_by_name(name)
  936. return vm.qid if vm is not None else None
  937. def get_vms_based_on(self, template_qid):
  938. vms = set([vm for vm in self.values()
  939. if (vm.is_appvm() and vm.template_vm.qid == template_qid)])
  940. return vms
  941. def verify_new_vm(self, new_vm):
  942. # Verify that qid is unique
  943. for vm in self.values():
  944. if vm.qid == new_vm.qid:
  945. print "ERROR: The qid={0} is already used by VM '{1}'!".\
  946. format(vm.qid, vm.name)
  947. return False
  948. # Verify that name is unique
  949. for vm in self.values():
  950. if vm.name == new_vm.name:
  951. print "ERROR: The name={0} is already used by other VM with qid='{1}'!".\
  952. format(vm.name, vm.qid)
  953. return False
  954. return True
  955. def get_new_unused_qid(self):
  956. used_ids = set([vm.qid for vm in self.values()])
  957. for id in range (1, qubes_max_qid):
  958. if id not in used_ids:
  959. return id
  960. raise LookupError ("Cannot find unused qid!")
  961. def get_new_unused_netid(self):
  962. used_ids = set([vm.netid for vm in self.values() if vm.is_netvm()])
  963. for id in range (1, qubes_max_netid):
  964. if id not in used_ids:
  965. return id
  966. raise LookupError ("Cannot find unused netid!")
  967. def check_if_storage_exists(self):
  968. try:
  969. f = open (qubes_store_filename, 'r')
  970. except IOError:
  971. return False
  972. f.close()
  973. return True
  974. def create_empty_storage(self):
  975. self.qubes_store_file = open (qubes_store_filename, 'w')
  976. self.clear()
  977. self.save()
  978. def lock_db_for_reading(self):
  979. self.qubes_store_file = open (qubes_store_filename, 'r')
  980. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_SH)
  981. def lock_db_for_writing(self):
  982. self.qubes_store_file = open (qubes_store_filename, 'r+')
  983. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_EX)
  984. def unlock_db(self):
  985. fcntl.lockf (self.qubes_store_file, fcntl.LOCK_UN)
  986. self.qubes_store_file.close()
  987. def save(self):
  988. root = xml.etree.ElementTree.Element(
  989. "QubesVmCollection",
  990. default_template=str(self.default_template_qid) \
  991. if self.default_template_qid is not None else "None",
  992. default_netvm=str(self.default_netvm_qid) \
  993. if self.default_netvm_qid is not None else "None"
  994. )
  995. for vm in self.values():
  996. element = vm.create_xml_element()
  997. if element is not None:
  998. root.append(element)
  999. tree = xml.etree.ElementTree.ElementTree(root)
  1000. try:
  1001. # We need to manually truncate the file, as we open the
  1002. # file as "r+" in the lock_db_for_writing() function
  1003. self.qubes_store_file.seek (0, os.SEEK_SET)
  1004. self.qubes_store_file.truncate()
  1005. tree.write(self.qubes_store_file, "UTF-8")
  1006. except EnvironmentError as err:
  1007. print("{0}: import error: {1}".format(
  1008. os.path.basename(sys.argv[0]), err))
  1009. return False
  1010. return True
  1011. def load(self):
  1012. self.clear()
  1013. dom0vm = QubesDom0NetVm ()
  1014. self[dom0vm.qid] = dom0vm
  1015. self.default_netvm_qid = 0
  1016. global dom0_vm
  1017. dom0_vm = dom0vm
  1018. try:
  1019. tree = xml.etree.ElementTree.parse(self.qubes_store_file)
  1020. except (EnvironmentError,
  1021. xml.parsers.expat.ExpatError) as err:
  1022. print("{0}: import error: {1}".format(
  1023. os.path.basename(sys.argv[0]), err))
  1024. return False
  1025. element = tree.getroot()
  1026. default_template = element.get("default_template")
  1027. self.default_template_qid = int(default_template) \
  1028. if default_template != "None" else None
  1029. default_netvm = element.get("default_netvm")
  1030. if default_netvm is not None:
  1031. self.default_netvm_qid = int(default_netvm) \
  1032. if default_netvm != "None" else None
  1033. #assert self.default_netvm_qid is not None
  1034. # Read in the NetVMs first, because a reference to NetVM
  1035. # is needed to create all other VMs
  1036. for element in tree.findall("QubesNetVm"):
  1037. try:
  1038. kwargs = {}
  1039. attr_list = ("qid", "netid", "name", "dir_path", "conf_file",
  1040. "private_img", "root_img",
  1041. )
  1042. for attribute in attr_list:
  1043. kwargs[attribute] = element.get(attribute)
  1044. kwargs["qid"] = int(kwargs["qid"])
  1045. kwargs["netid"] = int(kwargs["netid"])
  1046. vm = QubesNetVm(**kwargs)
  1047. self[vm.qid] = vm
  1048. except (ValueError, LookupError) as err:
  1049. print("{0}: import error (QubesNetVM) {1}".format(
  1050. os.path.basename(sys.argv[0]), err))
  1051. return False
  1052. self.default_template_qid
  1053. # Then, read in the TemplateVMs, because a reference to templete VM
  1054. # is needed to create each AppVM
  1055. for element in tree.findall("QubesTemplateVm"):
  1056. try:
  1057. kwargs = {}
  1058. attr_list = ("qid", "name", "dir_path", "conf_file",
  1059. "appvms_conf_file", "private_img", "root_img",
  1060. "installed_by_rpm", "updateable",
  1061. "uses_default_netvm", "netvm_qid")
  1062. for attribute in attr_list:
  1063. kwargs[attribute] = element.get(attribute)
  1064. kwargs["qid"] = int(kwargs["qid"])
  1065. kwargs["installed_by_rpm"] = True if kwargs["installed_by_rpm"] == "True" else False
  1066. if kwargs["updateable"] is not None:
  1067. kwargs["updateable"] = True if kwargs["updateable"] == "True" else False
  1068. if "uses_default_netvm" not in kwargs:
  1069. kwargs["uses_default_netvm"] = True
  1070. else:
  1071. kwargs["uses_default_netvm"] = True if kwargs["uses_default_netvm"] == "True" else False
  1072. if kwargs["uses_default_netvm"] is True:
  1073. netvm_vm = self.get_default_netvm_vm()
  1074. kwargs.pop("netvm_qid")
  1075. else:
  1076. if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None:
  1077. netvm_vm = None
  1078. kwargs.pop("netvm_qid")
  1079. else:
  1080. netvm_qid = int(kwargs.pop("netvm_qid"))
  1081. if netvm_qid not in self:
  1082. netvm_vm = None
  1083. else:
  1084. netvm_vm = self[netvm_qid]
  1085. kwargs["netvm_vm"] = netvm_vm
  1086. vm = QubesTemplateVm(**kwargs)
  1087. self[vm.qid] = vm
  1088. except (ValueError, LookupError) as err:
  1089. print("{0}: import error (QubesTemplateVm): {1}".format(
  1090. os.path.basename(sys.argv[0]), err))
  1091. return False
  1092. # Finally, read in the AppVMs
  1093. for element in tree.findall("QubesAppVm"):
  1094. try:
  1095. kwargs = {}
  1096. attr_list = ("qid", "name", "dir_path", "conf_file",
  1097. "private_img", "template_qid",
  1098. "updateable", "label", "netvm_qid",
  1099. "uses_default_netvm")
  1100. for attribute in attr_list:
  1101. kwargs[attribute] = element.get(attribute)
  1102. kwargs["qid"] = int(kwargs["qid"])
  1103. kwargs["template_qid"] = int(kwargs["template_qid"])
  1104. if kwargs["updateable"] is not None:
  1105. kwargs["updateable"] = True if kwargs["updateable"] == "True" else False
  1106. template_vm = self[kwargs.pop("template_qid")]
  1107. if template_vm is None:
  1108. print "ERROR: AppVM '{0}' uses unkown template qid='{1}'!".\
  1109. format(kwargs["name"], kwargs["template_qid"])
  1110. kwargs["template_vm"] = template_vm
  1111. if "uses_default_netvm" not in kwargs:
  1112. kwargs["uses_default_netvm"] = True
  1113. else:
  1114. kwargs["uses_default_netvm"] = True if kwargs["uses_default_netvm"] == "True" else False
  1115. if kwargs["uses_default_netvm"] is True:
  1116. netvm_vm = self.get_default_netvm_vm()
  1117. kwargs.pop("netvm_qid")
  1118. else:
  1119. if kwargs["netvm_qid"] == "none" or kwargs["netvm_qid"] is None:
  1120. netvm_vm = None
  1121. kwargs.pop("netvm_qid")
  1122. else:
  1123. netvm_qid = int(kwargs.pop("netvm_qid"))
  1124. if netvm_qid not in self:
  1125. netvm_vm = None
  1126. else:
  1127. netvm_vm = self[netvm_qid]
  1128. kwargs["netvm_vm"] = netvm_vm
  1129. if kwargs["label"] is not None:
  1130. if kwargs["label"] not in QubesVmLabels:
  1131. print "ERROR: incorrect label for VM '{0}'".format(kwargs["name"])
  1132. kwargs.pop ("label")
  1133. else:
  1134. kwargs["label"] = QubesVmLabels[kwargs["label"]]
  1135. vm = QubesAppVm(**kwargs)
  1136. self[vm.qid] = vm
  1137. except (ValueError, LookupError) as err:
  1138. print("{0}: import error (QubesAppVm): {1}".format(
  1139. os.path.basename(sys.argv[0]), err))
  1140. return False
  1141. return True
  1142. class QubesDaemonPidfile(object):
  1143. def __init__(self, name):
  1144. self.name = name
  1145. self.path = "/var/run/qubes/" + name + ".pid"
  1146. def create_pidfile(self):
  1147. f = open (self.path, 'w')
  1148. f.write(str(os.getpid()))
  1149. f.close()
  1150. def pidfile_exists(self):
  1151. return os.path.exists(self.path)
  1152. def read_pid(self):
  1153. f = open (self.path)
  1154. pid = f.read ().strip()
  1155. f.close()
  1156. return int(pid)
  1157. def pidfile_is_stale(self):
  1158. if not self.pidfile_exists():
  1159. return False
  1160. # check if the pid file is valid...
  1161. proc_path = "/proc/" + str(self.read_pid()) + "/cmdline"
  1162. if not os.path.exists (proc_path):
  1163. print "Path {0} doesn't exist, assuming stale pidfile.".format(proc_path)
  1164. return True
  1165. f = open (proc_path)
  1166. cmdline = f.read ()
  1167. f.close()
  1168. # The following doesn't work with python -- one would have to get argv[1] and compare it with self.name...
  1169. # if not cmdline.strip().endswith(self.name):
  1170. # print "{0} = {1} doesn't seem to point to our process ({2}), assuming stale pidile.".format(proc_path, cmdline, self.name)
  1171. # return True
  1172. return False # It's a good pidfile
  1173. def remove_pidfile(self):
  1174. os.remove (self.path)
  1175. def __enter__ (self):
  1176. # assumes the pidfile doesn't exist -- you should ensure it before opening the context
  1177. self.create_pidfile()
  1178. def __exit__ (self):
  1179. self.remove_pidfile()