settings.py 42 KB

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