settings.py 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. #!/usr/bin/python2
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
  6. # Copyright (C) 2012 Marek Marczykowski <marmarek@mimuw.edu.pl>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # as published by the Free Software Foundation; either version 2
  11. # of the License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. #
  22. #
  23. from qubes.qubes import QubesVmCollection
  24. from qubes.qubes import QubesVmLabels
  25. from qubes.qubes import QubesHost
  26. from qubes.qubes import system_path
  27. import subprocess
  28. from copy import copy
  29. from ui_settingsdlg import *
  30. from appmenu_select import *
  31. from firewall import *
  32. from backup_utils import get_path_for_vm
  33. class VMSettingsWindow(Ui_SettingsDialog, QDialog):
  34. tabs_indices = {"basic": 0,
  35. "advanced": 1,
  36. "firewall": 2,
  37. "devices": 3,
  38. "applications": 4,
  39. "services": 5,}
  40. def __init__(self, vm, app, qvm_collection, init_page="basic", parent=None):
  41. super(VMSettingsWindow, self).__init__(parent)
  42. self.app = app
  43. self.qvm_collection = qvm_collection
  44. self.vm = vm
  45. if self.vm.template:
  46. self.source_vm = self.vm.template
  47. else:
  48. self.source_vm = self.vm
  49. self.setupUi(self)
  50. self.setWindowTitle("Settings: %s" % self.vm.name)
  51. if init_page in self.tabs_indices:
  52. idx = self.tabs_indices[init_page]
  53. assert (idx in range(self.tabWidget.count()))
  54. self.tabWidget.setCurrentIndex(idx)
  55. self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
  56. self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
  57. self.tabWidget.currentChanged.connect(self.current_tab_changed)
  58. self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], vm.is_networked() and not (vm.is_netvm() and not vm.is_proxyvm()))
  59. ###### basic tab
  60. self.__init_basic_tab__()
  61. ###### advanced tab
  62. self.__init_advanced_tab__()
  63. self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed)
  64. self.connect(self.init_mem, SIGNAL("editingFinished()"), self.check_mem_changes)
  65. self.connect(self.max_mem_size, SIGNAL("editingFinished()"), self.check_mem_changes)
  66. self.drive_path_button.clicked.connect(self.drive_path_button_pressed)
  67. ###### firewall tab
  68. if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]):
  69. model = QubesFirewallRulesModel()
  70. model.set_vm(vm)
  71. self.set_fw_model(model)
  72. self.newRuleButton.clicked.connect(self.new_rule_button_pressed)
  73. self.editRuleButton.clicked.connect(self.edit_rule_button_pressed)
  74. self.deleteRuleButton.clicked.connect(self.delete_rule_button_pressed)
  75. self.policyDenyRadioButton.clicked.connect(self.policy_changed)
  76. self.policyAllowRadioButton.clicked.connect(self.policy_changed)
  77. ####### devices tab
  78. self.__init_devices_tab__()
  79. self.connect(self.dev_list, SIGNAL("selected_changed()"), self.devices_selection_changed)
  80. ####### services tab
  81. self.__init_services_tab__()
  82. self.add_srv_button.clicked.connect(self.__add_service__)
  83. self.remove_srv_button.clicked.connect(self.__remove_service__)
  84. ####### apps tab
  85. if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
  86. self.app_list = MultiSelectWidget(self)
  87. self.apps_layout.addWidget(self.app_list)
  88. self.AppListManager = AppmenuSelectManager(self.vm, self.app_list)
  89. def reject(self):
  90. self.done(0)
  91. #needed not to close the dialog before applying changes
  92. def accept(self):
  93. pass
  94. def save_and_apply(self):
  95. thread_monitor = ThreadMonitor()
  96. thread = threading.Thread (target=self.__save_changes__, args=(thread_monitor,))
  97. thread.daemon = True
  98. thread.start()
  99. progress = QProgressDialog ("Applying settings to <b>{0}</b>...".format(self.vm.name), "", 0, 0)
  100. progress.setCancelButton(None)
  101. progress.setModal(True)
  102. progress.show()
  103. while not thread_monitor.is_finished():
  104. self.app.processEvents()
  105. time.sleep (0.1)
  106. progress.hide()
  107. if not thread_monitor.success:
  108. QMessageBox.warning (None, "Error while changing settings for {0}!".format(self.vm.name),
  109. "ERROR: {0}".format(thread_monitor.error_msg))
  110. self.done(0)
  111. def __save_changes__(self, thread_monitor):
  112. self.qvm_collection.lock_db_for_writing()
  113. self.anything_changed = False
  114. ret = []
  115. try:
  116. ret_tmp = self.__apply_basic_tab__()
  117. if len(ret_tmp) > 0:
  118. ret += ["Basic tab:"] + ret_tmp
  119. ret_tmp = self.__apply_advanced_tab__()
  120. if len(ret_tmp) > 0:
  121. ret += ["Advanced tab:"] + ret_tmp
  122. ret_tmp = self.__apply_devices_tab__()
  123. if len(ret_tmp) > 0:
  124. ret += ["Devices tab:"] + ret_tmp
  125. ret_tmp = self.__apply_services_tab__()
  126. if len(ret_tmp) > 0:
  127. ret += ["Sevices tab:"] + ret_tmp
  128. except Exception as ex:
  129. ret.append('Error while saving changes: ' + str(ex))
  130. if self.anything_changed == True:
  131. self.qvm_collection.save()
  132. self.qvm_collection.unlock_db()
  133. try:
  134. if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]):
  135. self.fw_model.apply_rules(self.policyAllowRadioButton.isChecked(),
  136. self.dnsCheckBox.isChecked(),
  137. self.icmpCheckBox.isChecked(),
  138. self.yumproxyCheckBox.isChecked(),
  139. self.tempFullAccess.isChecked(),
  140. self.tempFullAccessTime.value())
  141. except Exception as ex:
  142. ret += ["Firewall tab:", str(ex)]
  143. try:
  144. if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
  145. self.AppListManager.save_appmenu_select_changes()
  146. except Exception as ex:
  147. ret += ["Applications tab:", str(ex)]
  148. if len(ret) > 0 :
  149. thread_monitor.set_error_msg('\n'.join(ret))
  150. thread_monitor.set_finished()
  151. def current_tab_changed(self, idx):
  152. if idx == self.tabs_indices["firewall"]:
  153. if self.vm.netvm is not None and not self.vm.netvm.is_proxyvm():
  154. QMessageBox.warning (None, "VM configuration problem!", "The '{0}' AppVM is not network connected to a FirewallVM!<p>".format(self.vm.name) +\
  155. "You may edit the '{0}' VM firewall rules, but these will not take any effect until you connect it to a working Firewall VM.".format(self.vm.name))
  156. ######### basic tab
  157. def __init_basic_tab__(self):
  158. self.vmname.setText(self.vm.name)
  159. self.vmname.setValidator(QRegExpValidator(QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None))
  160. self.vmname.setEnabled(not self.vm.is_running())
  161. #self.qvm_collection.lock_db_for_reading()
  162. #self.qvm_collection.load()
  163. #self.qvm_collection.unlock_db()
  164. if self.vm.qid == 0:
  165. self.vmlabel.setVisible(False)
  166. else:
  167. self.vmlabel.setVisible(True)
  168. self.label_list = QubesVmLabels.values()
  169. self.label_list.sort(key=lambda l: l.index)
  170. self.label_idx = 0
  171. for (i, label) in enumerate(self.label_list):
  172. if label == self.vm.label:
  173. self.label_idx = i
  174. self.vmlabel.insertItem(i, label.name)
  175. self.vmlabel.setItemIcon (i, QIcon(label.icon_path))
  176. self.vmlabel.setCurrentIndex(self.label_idx)
  177. self.vmlabel.setEnabled(not self.vm.is_running())
  178. if not self.vm.is_template() and self.vm.template is not None:
  179. template_vm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_template()]
  180. self.template_idx = -1
  181. i = 0
  182. for vm in template_vm_list:
  183. if not self.vm.is_template_compatible(vm):
  184. continue
  185. text = vm.name
  186. if vm is self.qvm_collection.get_default_template():
  187. text += " (default)"
  188. if vm.qid == self.vm.template.qid:
  189. self.template_idx = i
  190. text += " (current)"
  191. self.template_name.insertItem(i, text)
  192. i += 1
  193. self.template_name.setCurrentIndex(self.template_idx)
  194. self.template_name.setEnabled(not self.vm.is_running())
  195. else:
  196. self.template_name.setEnabled(False)
  197. self.template_idx = -1
  198. if (not self.vm.is_netvm() or self.vm.is_proxyvm()):
  199. netvm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_netvm() and vm.qid != 0]
  200. self.netvm_idx = -1
  201. default_netvm = self.qvm_collection.get_default_netvm()
  202. if default_netvm is not None:
  203. text = "default (%s)" % default_netvm.name
  204. if self.vm.uses_default_netvm:
  205. text += " (current)"
  206. self.netvm_idx = 0
  207. self.netVM.insertItem(0, text)
  208. for (i, vm) in enumerate(netvm_list):
  209. text = vm.name
  210. if self.vm.netvm is not None and vm.qid == self.vm.netvm.qid and not self.vm.uses_default_netvm:
  211. self.netvm_idx = i+1
  212. text += " (current)"
  213. self.netVM.insertItem(i+1, text)
  214. none_text = "none"
  215. if self.vm.netvm is None:
  216. none_text += " (current)"
  217. self.netvm_idx = len(netvm_list)+1
  218. self.netVM.insertItem(len(netvm_list)+1, none_text)
  219. self.netVM.setCurrentIndex(self.netvm_idx)
  220. else:
  221. self.netVM.setEnabled(False)
  222. self.netvm_idx = -1
  223. self.include_in_backups.setChecked(self.vm.include_in_backups)
  224. if hasattr(self.vm, 'debug'):
  225. self.run_in_debug_mode.setVisible(True)
  226. self.run_in_debug_mode.setChecked(self.vm.debug)
  227. else:
  228. self.run_in_debug_mode.setVisible(False)
  229. if hasattr(self.vm, 'autostart'):
  230. self.autostart_vm.setVisible(True)
  231. self.autostart_vm.setChecked(self.vm.autostart)
  232. else:
  233. self.autostart_vm.setVisible(False)
  234. if hasattr(self.vm, 'seamless_gui_mode'):
  235. self.seamless_gui.setVisible(True)
  236. self.seamless_gui.setChecked(self.vm.seamless_gui_mode)
  237. else:
  238. self.seamless_gui.setVisible(False)
  239. #type
  240. self.type_label.setText(self.vm.type)
  241. #installed by rpm
  242. text = "Yes" if self.vm.installed_by_rpm == True else "No"
  243. self.rpm_label.setText(text)
  244. #networking info
  245. if self.vm.is_networked():
  246. self.networking_groupbox.setEnabled(True)
  247. self.ip_label.setText(self.vm.ip if self.vm.ip is not None else "none")
  248. self.netmask_label.setText(self.vm.netmask if self.vm.netmask is not None else "none")
  249. self.gateway_label.setText(self.vm.netvm.gateway if self.vm.netvm is not None else "none")
  250. else:
  251. self.networking_groupbox.setEnabled(False)
  252. #max priv storage
  253. self.priv_img_size = self.vm.get_private_img_sz()/1024/1024
  254. self.max_priv_storage.setMinimum(self.priv_img_size)
  255. self.max_priv_storage.setValue(self.priv_img_size)
  256. self.root_img_size = self.vm.get_root_img_sz()/1024/1024
  257. self.root_resize.setValue(self.root_img_size)
  258. self.root_resize.setMinimum(self.root_img_size)
  259. self.root_resize.setEnabled(hasattr(self.vm, 'resize_root_img') and
  260. not self.vm.template)
  261. self.root_resize_label.setEnabled(self.root_resize.isEnabled())
  262. def __apply_basic_tab__(self):
  263. msg = []
  264. # vmname changed
  265. vmname = str(self.vmname.text())
  266. if self.vm.name != vmname:
  267. if self.vm.is_running():
  268. msg.append("Can't change name of a running VM.")
  269. elif self.qvm_collection.get_vm_by_name(vmname) is not None:
  270. msg.append("Can't change VM name - a VM named <b>{0}</b> already exists in the system!".format(vmname))
  271. else:
  272. oldname = self.vm.name
  273. try:
  274. self.vm.set_name(vmname)
  275. self.anything_changed = True
  276. except Exception as ex:
  277. msg.append(str(ex))
  278. #vm label changed
  279. try:
  280. if self.vmlabel.isVisible():
  281. if self.vmlabel.currentIndex() != self.label_idx:
  282. label = self.label_list[self.vmlabel.currentIndex()]
  283. self.vm.label = label
  284. self.anything_changed = True
  285. except Exception as ex:
  286. msg.append(str(ex))
  287. #vm template changed
  288. try:
  289. if self.template_name.currentIndex() != self.template_idx:
  290. new_template_name = str(self.template_name.currentText())
  291. new_template_name = new_template_name.split(' ')[0]
  292. template_vm = self.qvm_collection.get_vm_by_name(new_template_name)
  293. assert (template_vm is not None and template_vm.qid in self.qvm_collection)
  294. assert template_vm.is_template()
  295. self.vm.template = template_vm
  296. self.anything_changed = True
  297. except Exception as ex:
  298. msg.append(str(ex))
  299. #vm netvm changed
  300. try:
  301. if self.netVM.currentIndex() != self.netvm_idx:
  302. new_netvm_name = str(self.netVM.currentText())
  303. new_netvm_name = new_netvm_name.split(' ')[0]
  304. uses_default_netvm = False
  305. if new_netvm_name == "default":
  306. new_netvm_name = self.qvm_collection.get_default_netvm().name
  307. uses_default_netvm = True
  308. if new_netvm_name == "none":
  309. netvm = None
  310. else:
  311. netvm = self.qvm_collection.get_vm_by_name(new_netvm_name)
  312. assert (netvm is None or (netvm is not None and netvm.qid in self.qvm_collection and netvm.is_netvm()))
  313. self.vm.netvm = netvm
  314. self.vm.uses_default_netvm = uses_default_netvm
  315. self.anything_changed = True
  316. except Exception as ex:
  317. msg.append(str(ex))
  318. #include in backups
  319. try:
  320. if self.vm.include_in_backups != self.include_in_backups.isChecked():
  321. self.vm.include_in_backups = self.include_in_backups.isChecked()
  322. self.anything_changed = True
  323. except Exception as ex:
  324. msg.append(str(ex))
  325. #run_in_debug_mode
  326. try:
  327. if self.run_in_debug_mode.isVisible():
  328. if self.vm.debug != self.run_in_debug_mode.isChecked():
  329. self.vm.debug = self.run_in_debug_mode.isChecked()
  330. self.anything_changed = True
  331. except Exception as ex:
  332. msg.append(str(ex))
  333. #autostart_vm
  334. try:
  335. if self.autostart_vm.isVisible():
  336. if self.vm.autostart != self.autostart_vm.isChecked():
  337. self.vm.autostart = self.autostart_vm.isChecked()
  338. self.anything_changed = True
  339. except Exception as ex:
  340. msg.append(str(ex))
  341. #seamless_gui
  342. try:
  343. if self.seamless_gui.isVisible():
  344. if self.vm.seamless_gui_mode != self.seamless_gui.isChecked():
  345. self.vm.seamless_gui_mode = self.seamless_gui.isChecked()
  346. self.anything_changed = True
  347. except Exception as ex:
  348. msg.append(str(ex))
  349. #max priv storage
  350. priv_size = self.max_priv_storage.value()
  351. if self.priv_img_size != priv_size:
  352. try:
  353. self.vm.resize_private_img(priv_size*1024*1024)
  354. self.anything_changed = True
  355. except Exception as ex:
  356. msg.append(str(ex))
  357. #max sys storage
  358. sys_size = self.root_resize.value()
  359. if self.root_img_size != sys_size:
  360. try:
  361. self.vm.resize_root_img(sys_size*1024*1024)
  362. self.anything_changed = True
  363. except Exception as ex:
  364. msg.append(str(ex))
  365. return msg
  366. def check_mem_changes(self):
  367. if self.max_mem_size.value() < self.init_mem.value():
  368. QMessageBox.warning(None, "Warning!", "Max memory can not be less than initial memory.<br>Setting max memory to equal initial memory.")
  369. self.max_mem_size.setValue(self.init_mem.value())
  370. # Linux specific limit: init memory must not be below max_mem_size/10.79 in order to allow scaling up to max_mem_size (or else "add_memory() failed: -17" problem)
  371. if self.init_mem.value() * 10 < self.max_mem_size.value():
  372. QMessageBox.warning(None, "Warning!", "Initial memory can not be less than one tenth Max memory.<br>Setting initial memory to the minimum allowed value.")
  373. self.init_mem.setValue(self.max_mem_size.value() / 10)
  374. ######### advanced tab
  375. def __init_advanced_tab__(self):
  376. #mem/cpu
  377. qubes_memory = QubesHost().memory_total/1024
  378. self.init_mem.setValue(int(self.vm.memory))
  379. self.init_mem.setMaximum(qubes_memory)
  380. self.max_mem_size.setValue(int(self.vm.maxmem))
  381. self.max_mem_size.setMaximum(qubes_memory)
  382. self.vcpus.setMinimum(1)
  383. self.vcpus.setMaximum(QubesHost().no_cpus)
  384. self.vcpus.setValue(int(self.vm.vcpus))
  385. self.include_in_balancing.setEnabled(True)
  386. self.include_in_balancing.setChecked(self.vm.services['meminfo-writer']==True)
  387. if self.vm.type == "HVM":
  388. self.include_in_balancing.setChecked(False)
  389. self.include_in_balancing.setEnabled(False)
  390. self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
  391. #paths
  392. self.dir_path.setText(self.vm.dir_path)
  393. self.config_path.setText(self.vm.conf_file)
  394. if self.vm.template is not None:
  395. self.root_img_path.setText(self.vm.template.root_img)
  396. elif self.vm.root_img is not None:
  397. self.root_img_path.setText(self.vm.root_img)
  398. else:
  399. self.root_img_path.setText("n/a")
  400. if self.vm.volatile_img is not None:
  401. self.volatile_img_path.setText(self.vm.volatile_img)
  402. else:
  403. self.volatile_img_path.setText('n/a')
  404. self.private_img_path.setText(self.vm.private_img)
  405. #kernel
  406. #in case VM is HVM
  407. if not hasattr(self.vm, "kernel"):
  408. self.kernel_groupbox.setVisible(False)
  409. else:
  410. self.kernel_groupbox.setVisible(True)
  411. # construct available kernels list
  412. text = "default (" + self.qvm_collection.get_default_kernel() +")"
  413. kernel_list = [text]
  414. for k in os.listdir(system_path["qubes_kernels_base_dir"]):
  415. kernel_list.append(k)
  416. kernel_list.append("none")
  417. self.kernel_idx = 0
  418. # put available kernels to a combobox
  419. for (i, k) in enumerate(kernel_list):
  420. text = k
  421. # and mark the current choice
  422. if (text.startswith("default") and self.vm.uses_default_kernel) or ( self.vm.kernel == k and not self.vm.uses_default_kernel) or (k=="none" and self.vm.kernel==None):
  423. text += " (current)"
  424. self.kernel_idx = i
  425. self.kernel.insertItem(i,text)
  426. self.kernel.setCurrentIndex(self.kernel_idx)
  427. #kernel opts
  428. if self.vm.uses_default_kernelopts:
  429. self.kernel_opts.setText(self.vm.kernelopts + " (default)")
  430. else:
  431. self.kernel_opts.setText(self.vm.kernelopts)
  432. if not hasattr(self.vm, "drive"):
  433. self.drive_groupbox.setVisible(False)
  434. else:
  435. self.drive_groupbox.setVisible(True)
  436. self.drive_groupbox.setChecked(self.vm.drive is not None)
  437. self.drive_running_warning.setVisible(self.vm.is_running())
  438. self.drive_type.addItems(["hd", "cdrom"])
  439. self.drive_type.setCurrentIndex(0)
  440. vm_list = [vm for vm in self.qvm_collection.values() if not vm
  441. .internal and vm.qid != self.vm.qid]
  442. # default to dom0 (in case of nonexisting vm already set...)
  443. self.drive_domain_idx = 0
  444. for (i, vm) in enumerate(sorted(vm_list, key=lambda v: v.name)):
  445. if vm.qid == 0:
  446. self.drive_domain_idx = i
  447. self.drive_domain.insertItem(i, vm.name)
  448. if self.vm.drive is not None:
  449. (drv_type, drv_domain, drv_path) = self.vm.drive.split(":")
  450. if drv_type == "cdrom":
  451. self.drive_type.setCurrentIndex(1)
  452. else:
  453. self.drive_type.setCurrentIndex(0)
  454. for i in xrange(self.drive_domain.count()):
  455. if drv_domain == self.drive_domain.itemText(i):
  456. self.drive_domain_idx = i
  457. self.drive_path.setText(drv_path)
  458. self.drive_domain.setCurrentIndex(self.drive_domain_idx)
  459. if not hasattr(self.vm, "dispvm_netvm"):
  460. self.other_groupbox.setVisible(False)
  461. else:
  462. self.other_groupbox.setVisible(True)
  463. netvm_list = [vm for vm in self.qvm_collection.values() if not vm.internal and vm.is_netvm() and vm.qid != 0]
  464. self.dispvm_netvm_idx = -1
  465. text = "default (same as VM own NetVM)"
  466. if self.vm.uses_default_dispvm_netvm:
  467. text += " (current)"
  468. self.dispvm_netvm_idx = 0
  469. self.dispvm_netvm.insertItem(0, text)
  470. for (i, vm) in enumerate(netvm_list):
  471. text = vm.name
  472. if self.vm.dispvm_netvm is not None and vm.qid == \
  473. self.vm.dispvm_netvm.qid and not \
  474. self.vm.uses_default_dispvm_netvm:
  475. self.dispvm_netvm_idx = i+1
  476. text += " (current)"
  477. self.dispvm_netvm.insertItem(i+1, text)
  478. none_text = "none"
  479. if self.vm.dispvm_netvm is None:
  480. none_text += " (current)"
  481. self.dispvm_netvm_idx = len(netvm_list)+1
  482. self.dispvm_netvm.insertItem(len(netvm_list)+1, none_text)
  483. self.dispvm_netvm.setCurrentIndex(self.dispvm_netvm_idx)
  484. def __apply_advanced_tab__(self):
  485. msg = []
  486. #mem/cpu
  487. try:
  488. if self.init_mem.value() != int(self.vm.memory):
  489. self.vm.memory = self.init_mem.value()
  490. self.anything_changed = True
  491. if self.max_mem_size.value() != int(self.vm.maxmem):
  492. self.vm.maxmem = self.max_mem_size.value()
  493. self.anything_changed = True
  494. if self.vcpus.value() != int(self.vm.vcpus):
  495. self.vm.vcpus = self.vcpus.value()
  496. self.anything_changed = True
  497. except Exception as ex:
  498. msg.append(str(ex))
  499. #include_in_memory_balancing applied in services tab
  500. #in case VM is not Linux
  501. if hasattr(self.vm, "kernel") and self.kernel_groupbox.isVisible():
  502. try:
  503. if self.kernel.currentIndex() != self.kernel_idx:
  504. new_kernel = str(self.kernel.currentText())
  505. new_kernel = new_kernel.split(' ')[0]
  506. if new_kernel == "default":
  507. kernel = self.qvm_collection.get_default_kernel()
  508. self.vm.uses_default_kernel = True
  509. elif new_kernel == "none":
  510. kernel = None
  511. self.vm.uses_default_kernel = False
  512. else:
  513. kernel = new_kernel
  514. self.vm.uses_default_kernel = False
  515. self.vm.kernel = kernel
  516. self.anything_changed = True
  517. except Exception as ex:
  518. msg.append(str(ex))
  519. if hasattr(self.vm, "drive") and self.drive_groupbox.isVisible():
  520. try:
  521. if not self.drive_groupbox.isChecked():
  522. if self.vm.drive != None:
  523. self.vm.drive = None
  524. self.anything_changed = True
  525. else:
  526. new_domain = str(self.drive_domain.currentText())
  527. if self.drive_domain.currentIndex() == self.drive_domain_idx:
  528. # strip "(current)"
  529. new_domain = new_domain.split(' ')[0]
  530. drive = "%s:%s:%s" % (
  531. str(self.drive_type.currentText()),
  532. new_domain,
  533. str(self.drive_path.text())
  534. )
  535. if self.vm.drive != drive:
  536. self.vm.drive = drive
  537. self.anything_changed = True
  538. except Exception as ex:
  539. msg.append(str(ex))
  540. #vm dispvm_netvm changed
  541. try:
  542. print self.dispvm_netvm.currentIndex()
  543. if self.dispvm_netvm.currentIndex() != self.dispvm_netvm_idx:
  544. new_dispvm_netvm_name = str(self.dispvm_netvm.currentText())
  545. new_dispvm_netvm_name = new_dispvm_netvm_name.split(' ')[0]
  546. uses_default_dispvm_netvm = False
  547. if new_dispvm_netvm_name == "default":
  548. uses_default_dispvm_netvm = True
  549. if new_dispvm_netvm_name == "none":
  550. dispvm_netvm = None
  551. else:
  552. dispvm_netvm = self.qvm_collection.get_vm_by_name(
  553. new_dispvm_netvm_name)
  554. assert (dispvm_netvm is None or (dispvm_netvm.qid in
  555. self.qvm_collection and dispvm_netvm.is_netvm()))
  556. if uses_default_dispvm_netvm:
  557. self.vm.uses_default_dispvm_netvm = True
  558. else:
  559. self.vm.uses_default_dispvm_netvm = False
  560. self.vm.dispvm_netvm = dispvm_netvm
  561. self.anything_changed = True
  562. except Exception as ex:
  563. msg.append(str(ex))
  564. return msg
  565. def drive_path_button_pressed(self):
  566. if str(self.drive_domain.currentText()) in ["dom0", "dom0 (current)"]:
  567. file_dialog = QFileDialog()
  568. file_dialog.setReadOnly(True)
  569. new_path = file_dialog.getOpenFileName(self, "Select drive image",
  570. str(self.drive_path.text()))
  571. else:
  572. drv_domain = str(self.drive_domain.currentText())
  573. if drv_domain.count("(current)") > 0:
  574. drv_domain = drv_domain.split(' ')[0]
  575. backend_vm = self.qvm_collection.get_vm_by_name(drv_domain)
  576. if backend_vm:
  577. new_path = get_path_for_vm(backend_vm, "qubes.SelectFile")
  578. if new_path:
  579. self.drive_path.setText(new_path)
  580. ######## devices tab
  581. def __init_devices_tab__(self):
  582. self.dev_list = MultiSelectWidget(self)
  583. self.dev_list.add_all_button.setVisible(False)
  584. self.devices_layout.addWidget(self.dev_list)
  585. devs = []
  586. lspci = subprocess.Popen(["/usr/sbin/lspci",], stdout = subprocess.PIPE)
  587. for dev in lspci.stdout:
  588. devs.append( (dev.rstrip(), dev.split(' ')[0]) )
  589. class DevListWidgetItem(QListWidgetItem):
  590. def __init__(self, name, slot, parent = None):
  591. super(DevListWidgetItem, self).__init__(name, parent)
  592. self.slot = slot
  593. self.Type
  594. for d in devs:
  595. if d[1] in self.vm.pcidevs:
  596. self.dev_list.selected_list.addItem( DevListWidgetItem(d[0], d[1]))
  597. else:
  598. self.dev_list.available_list.addItem( DevListWidgetItem(d[0], d[1]))
  599. if self.dev_list.selected_list.count() > 0 and self.include_in_balancing.isChecked():
  600. self.dmm_warning_adv.show()
  601. self.dmm_warning_dev.show()
  602. else:
  603. self.dmm_warning_adv.hide()
  604. self.dmm_warning_dev.hide()
  605. if self.vm.is_running():
  606. self.dev_list.setEnabled(False)
  607. self.turn_off_vm_to_modify_devs.setVisible(True)
  608. else:
  609. self.dev_list.setEnabled(True)
  610. self.turn_off_vm_to_modify_devs.setVisible(False)
  611. def __apply_devices_tab__(self):
  612. msg = []
  613. sth_changed = False
  614. added = []
  615. try:
  616. for i in range(self.dev_list.selected_list.count()):
  617. item = self.dev_list.selected_list.item(i)
  618. if item.slot not in self.vm.pcidevs:
  619. added.append(item)
  620. if self.dev_list.selected_list.count() - len(added) < len(self.vm.pcidevs): #sth removed
  621. sth_changed = True
  622. elif len(added) > 0:
  623. sth_changed = True
  624. if sth_changed == True:
  625. pcidevs = []
  626. for i in range(self.dev_list.selected_list.count()):
  627. slot = self.dev_list.selected_list.item(i).slot
  628. pcidevs.append(slot)
  629. self.vm.pcidevs = pcidevs
  630. self.anything_changed = True
  631. except Exception as ex:
  632. msg.append(str(ex))
  633. return msg
  634. def include_in_balancing_state_changed(self, state):
  635. for r in range (self.services_list.count()):
  636. item = self.services_list.item(r)
  637. if str(item.text()) == 'meminfo-writer':
  638. item.setCheckState(state)
  639. break
  640. if self.dev_list.selected_list.count() > 0:
  641. if state == QtCore.Qt.Checked:
  642. self.dmm_warning_adv.show()
  643. self.dmm_warning_dev.show()
  644. else:
  645. self.dmm_warning_adv.hide()
  646. self.dmm_warning_dev.hide()
  647. self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
  648. def devices_selection_changed(self):
  649. if self.include_in_balancing.isChecked():
  650. if self.dev_list.selected_list.count() > 0 :
  651. self.dmm_warning_adv.show()
  652. self.dmm_warning_dev.show()
  653. else:
  654. self.dmm_warning_adv.hide()
  655. self.dmm_warning_dev.hide()
  656. ######## services tab
  657. def __init_services_tab__(self):
  658. for srv in self.vm.services:
  659. item = QListWidgetItem(srv)
  660. if self.vm.services[srv] == True:
  661. item.setCheckState(QtCore.Qt.Checked)
  662. else:
  663. item.setCheckState(QtCore.Qt.Unchecked)
  664. self.services_list.addItem(item)
  665. self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked)
  666. self.new_srv_dict = copy(self.vm.services)
  667. def __add_service__(self):
  668. srv = str(self.service_line_edit.text()).strip()
  669. if srv != "":
  670. if srv in self.new_srv_dict:
  671. QMessageBox.information(None, "", "Service already on the list!")
  672. else:
  673. item = QListWidgetItem(srv)
  674. item.setCheckState(QtCore.Qt.Checked)
  675. self.services_list.addItem(item)
  676. self.new_srv_dict[srv] = True
  677. def __remove_service__(self):
  678. item = self.services_list.currentItem()
  679. if not item:
  680. return
  681. if str(item.text()) == 'meminfo-writer':
  682. QMessageBox.information(None, "Service can not be removed", "Service meminfo-writer can not be removed from the list.")
  683. return
  684. row = self.services_list.currentRow()
  685. item = self.services_list.takeItem(row)
  686. del self.new_srv_dict[str(item.text())]
  687. def services_item_clicked(self, item):
  688. if str(item.text()) == 'meminfo-writer':
  689. if item.checkState() == QtCore.Qt.Checked:
  690. if not self.include_in_balancing.isChecked():
  691. self.include_in_balancing.setChecked(True)
  692. elif item.checkState() == QtCore.Qt.Unchecked:
  693. if self.include_in_balancing.isChecked():
  694. self.include_in_balancing.setChecked(False)
  695. def __apply_services_tab__(self):
  696. msg = []
  697. try:
  698. for r in range (self.services_list.count()):
  699. item = self.services_list.item(r)
  700. self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked)
  701. balancing_was_checked = (self.vm.services['meminfo-writer']==True)
  702. balancing_is_checked = self.include_in_balancing.isChecked()
  703. meminfo_writer_checked = (self.new_srv_dict['meminfo-writer'] == True)
  704. if balancing_is_checked != meminfo_writer_checked:
  705. if balancing_is_checked != balancing_was_checked:
  706. self.new_srv_dict['meminfo-writer'] = balancing_is_checked
  707. if self.new_srv_dict != self.vm.services:
  708. self.vm.services = self.new_srv_dict
  709. self.anything_changed = True
  710. except Exception as ex:
  711. msg.append(str(ex))
  712. return msg
  713. ######### firewall tab related
  714. def set_fw_model(self, model):
  715. self.fw_model = model
  716. self.rulesTreeView.setModel(model)
  717. self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents)
  718. self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch)
  719. self.set_allow(model.allow)
  720. self.dnsCheckBox.setChecked(model.allowDns)
  721. self.icmpCheckBox.setChecked(model.allowIcmp)
  722. self.yumproxyCheckBox.setChecked(model.allowYumProxy)
  723. if model.tempFullAccessExpireTime:
  724. self.tempFullAccess.setChecked(True)
  725. self.tempFullAccessTime.setValue(
  726. (model.tempFullAccessExpireTime -
  727. int(datetime.datetime.now().strftime("%s")))/60)
  728. def set_allow(self, allow):
  729. self.policyAllowRadioButton.setChecked(allow)
  730. self.policyDenyRadioButton.setChecked(not allow)
  731. self.policy_changed(allow)
  732. def policy_changed(self, checked):
  733. self.tempFullAccessWidget.setEnabled(self.policyDenyRadioButton.isChecked())
  734. def new_rule_button_pressed(self):
  735. dialog = NewFwRuleDlg()
  736. self.run_rule_dialog(dialog)
  737. def edit_rule_button_pressed(self):
  738. dialog = NewFwRuleDlg()
  739. dialog.set_ok_enabled(True)
  740. selected = self.rulesTreeView.selectedIndexes()
  741. if len(selected) > 0:
  742. row = self.rulesTreeView.selectedIndexes().pop().row()
  743. address = self.fw_model.get_column_string(0, row).replace(' ', '')
  744. dialog.addressComboBox.setItemText(0, address)
  745. dialog.addressComboBox.setCurrentIndex(0)
  746. service = self.fw_model.get_column_string(1, row)
  747. if service == "any":
  748. service = ""
  749. dialog.serviceComboBox.setItemText(0, service)
  750. dialog.serviceComboBox.setCurrentIndex(0)
  751. protocol = self.fw_model.get_column_string(2, row)
  752. if protocol == "tcp":
  753. dialog.tcp_radio.setChecked(True)
  754. elif protocol == "udp":
  755. dialog.udp_radio.setChecked(True)
  756. else:
  757. dialog.any_radio.setChecked(True)
  758. self.run_rule_dialog(dialog, row)
  759. def delete_rule_button_pressed(self):
  760. for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]):
  761. self.fw_model.removeChild(i)
  762. def run_rule_dialog(self, dialog, row = None):
  763. if dialog.exec_():
  764. address = str(dialog.addressComboBox.currentText())
  765. service = str(dialog.serviceComboBox.currentText())
  766. port = None
  767. port2 = None
  768. unmask = address.split("/", 1)
  769. if len(unmask) == 2:
  770. address = unmask[0]
  771. netmask = int(unmask[1])
  772. else:
  773. netmask = 32
  774. if address == "*":
  775. address = "0.0.0.0"
  776. netmask = 0
  777. if dialog.any_radio.isChecked():
  778. protocol = "any"
  779. port = 0
  780. else:
  781. if dialog.tcp_radio.isChecked():
  782. protocol = "tcp"
  783. elif dialog.udp_radio.isChecked():
  784. protocol = "udp"
  785. else:
  786. protocol = "any"
  787. try:
  788. range = service.split("-", 1)
  789. if len(range) == 2:
  790. port = int(range[0])
  791. port2 = int(range[1])
  792. else:
  793. port = int(service)
  794. except (TypeError, ValueError) as ex:
  795. port = self.fw_model.get_service_port(service)
  796. if port is not None:
  797. if port2 is not None and port2 <= port:
  798. QMessageBox.warning(None, "Invalid service ports range",
  799. "Port {0} is lower than port {1}.".format(port2, port))
  800. else:
  801. item = {"address": address,
  802. "netmask": netmask,
  803. "portBegin": port,
  804. "portEnd": port2,
  805. "proto": protocol,
  806. }
  807. if row is not None:
  808. self.fw_model.setChild(row, item)
  809. else:
  810. self.fw_model.appendChild(item)
  811. else:
  812. QMessageBox.warning(None, "Invalid service name",
  813. "Service '{0}' is unknown.".format(service))
  814. # Bases on the original code by:
  815. # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
  816. def handle_exception( exc_type, exc_value, exc_traceback ):
  817. import sys
  818. import os.path
  819. import traceback
  820. filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
  821. filename = os.path.basename( filename )
  822. error = "%s: %s" % ( exc_type.__name__, exc_value )
  823. strace = ""
  824. stacktrace = traceback.extract_tb( exc_traceback )
  825. while len(stacktrace) > 0:
  826. (filename, line, func, txt) = stacktrace.pop()
  827. strace += "----\n"
  828. strace += "line: %s\n" %txt
  829. strace += "func: %s\n" %func
  830. strace += "line no.: %d\n" %line
  831. strace += "file: %s\n" %filename
  832. msg_box = QMessageBox()
  833. msg_box.setDetailedText(strace)
  834. msg_box.setIcon(QMessageBox.Critical)
  835. msg_box.setWindowTitle( "Houston, we have a problem...")
  836. msg_box.setText("Whoops. A critical error has occured. This is most likely a bug "
  837. "in Qubes Manager.<br><br>"
  838. "<b><i>%s</i></b>" % error +
  839. "<br/>at line <b>%d</b><br/>of file %s.<br/><br/>"
  840. % ( line, filename ))
  841. msg_box.exec_()
  842. def main():
  843. global qubes_host
  844. qubes_host = QubesHost()
  845. global app
  846. app = QApplication(sys.argv)
  847. app.setOrganizationName("The Qubes Project")
  848. app.setOrganizationDomain("http://qubes-os.org")
  849. app.setApplicationName("Qubes VM Settings")
  850. sys.excepthook = handle_exception
  851. qvm_collection = QubesVmCollection()
  852. qvm_collection.lock_db_for_reading()
  853. qvm_collection.load()
  854. qvm_collection.unlock_db()
  855. vm = None
  856. tab = "basic"
  857. if len(sys.argv) > 1:
  858. vm = qvm_collection.get_vm_by_name(sys.argv[1])
  859. if vm is None or vm.qid not in qvm_collection:
  860. QMessageBox.critical(None, "Qubes VM Settings Error",
  861. "A VM with the name '{0}' does not exist in the system.".format(sys.argv[1]))
  862. sys.exit(1)
  863. if len(sys.argv) > 2:
  864. tab_arg = sys.argv[2]
  865. print tab_arg
  866. if tab_arg in VMSettingsWindow.tabs_indices:
  867. tab = tab_arg
  868. else: QMessageBox.warning(None, "Qubes VM Settings Error",
  869. "There is no such tab as '{0}'. Opening default tab instead.".format(tab_arg))
  870. else:
  871. vms_list = [vm.name for vm in qvm_collection.values() if (vm.is_appvm() or vm.is_template())]
  872. vmname = QInputDialog.getItem(None, "Select VM", "Select VM:", vms_list, editable = False)
  873. if not vmname[1]:
  874. sys.exit(1)
  875. vm = qvm_collection.get_vm_by_name(vmname[0])
  876. global settings_window
  877. settings_window = VMSettingsWindow(vm, app, qvm_collection, tab)
  878. settings_window.show()
  879. app.exec_()
  880. app.exit()
  881. if __name__ == "__main__":
  882. main()
  883. # vim:sw=4:et: