settings.py 42 KB

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