settings.py 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  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. uses_default_kernel = False
  526. if new_kernel == "default":
  527. kernel = self.qvm_collection.get_default_kernel()
  528. uses_default_kernel = True
  529. elif new_kernel == "none":
  530. kernel = None
  531. else:
  532. kernel = new_kernel
  533. self.vm.kernel = kernel
  534. # Set self.vm.uses_default_kernel after self.vm.kernel to ensure that
  535. # the correct value persists after QubesVm.kernel resets self.vm.uses_default_kernel
  536. # to False.
  537. self.vm.uses_default_kernel = uses_default_kernel
  538. self.anything_changed = True
  539. except Exception as ex:
  540. msg.append(str(ex))
  541. if hasattr(self.vm, "drive") and self.drive_groupbox.isVisible():
  542. try:
  543. if not self.drive_groupbox.isChecked():
  544. if self.vm.drive != None:
  545. self.vm.drive = None
  546. self.anything_changed = True
  547. else:
  548. new_domain = str(self.drive_domain.currentText())
  549. if self.drive_domain.currentIndex() == self.drive_domain_idx:
  550. # strip "(current)"
  551. new_domain = new_domain.split(' ')[0]
  552. drive = "%s:%s:%s" % (
  553. str(self.drive_type.currentText()),
  554. new_domain,
  555. str(self.drive_path.text())
  556. )
  557. if self.vm.drive != drive:
  558. self.vm.drive = drive
  559. self.anything_changed = True
  560. except Exception as ex:
  561. msg.append(str(ex))
  562. #vm dispvm_netvm changed
  563. try:
  564. if self.dispvm_netvm.currentIndex() != self.dispvm_netvm_idx:
  565. new_dispvm_netvm_name = str(self.dispvm_netvm.currentText())
  566. new_dispvm_netvm_name = new_dispvm_netvm_name.split(' ')[0]
  567. uses_default_dispvm_netvm = False
  568. if new_dispvm_netvm_name == "default":
  569. uses_default_dispvm_netvm = True
  570. if new_dispvm_netvm_name == "none":
  571. dispvm_netvm = None
  572. else:
  573. dispvm_netvm = self.qvm_collection.get_vm_by_name(
  574. new_dispvm_netvm_name)
  575. assert (dispvm_netvm is None or (dispvm_netvm.qid in
  576. self.qvm_collection and dispvm_netvm.is_netvm()))
  577. if uses_default_dispvm_netvm:
  578. self.vm.uses_default_dispvm_netvm = True
  579. else:
  580. self.vm.uses_default_dispvm_netvm = False
  581. self.vm.dispvm_netvm = dispvm_netvm
  582. self.anything_changed = True
  583. except Exception as ex:
  584. msg.append(str(ex))
  585. return msg
  586. def drive_path_button_pressed(self):
  587. if str(self.drive_domain.currentText()) in ["dom0", "dom0 (current)"]:
  588. file_dialog = QFileDialog()
  589. file_dialog.setReadOnly(True)
  590. new_path = file_dialog.getOpenFileName(self, "Select drive image",
  591. str(self.drive_path.text()))
  592. else:
  593. drv_domain = str(self.drive_domain.currentText())
  594. if drv_domain.count("(current)") > 0:
  595. drv_domain = drv_domain.split(' ')[0]
  596. backend_vm = self.qvm_collection.get_vm_by_name(drv_domain)
  597. if backend_vm:
  598. new_path = get_path_for_vm(backend_vm, "qubes.SelectFile")
  599. if new_path:
  600. self.drive_path.setText(new_path)
  601. ######## devices tab
  602. def __init_devices_tab__(self):
  603. self.dev_list = MultiSelectWidget(self)
  604. self.dev_list.add_all_button.setVisible(False)
  605. self.devices_layout.addWidget(self.dev_list)
  606. devs = []
  607. lspci = subprocess.Popen(["/usr/sbin/lspci",], stdout = subprocess.PIPE)
  608. for dev in lspci.stdout:
  609. devs.append( (dev.rstrip(), dev.split(' ')[0]) )
  610. class DevListWidgetItem(QListWidgetItem):
  611. def __init__(self, name, slot, parent = None):
  612. super(DevListWidgetItem, self).__init__(name, parent)
  613. self.slot = slot
  614. self.Type
  615. for d in devs:
  616. if d[1] in self.vm.pcidevs:
  617. self.dev_list.selected_list.addItem( DevListWidgetItem(d[0], d[1]))
  618. else:
  619. self.dev_list.available_list.addItem( DevListWidgetItem(d[0], d[1]))
  620. if self.dev_list.selected_list.count() > 0 and self.include_in_balancing.isChecked():
  621. self.dmm_warning_adv.show()
  622. self.dmm_warning_dev.show()
  623. else:
  624. self.dmm_warning_adv.hide()
  625. self.dmm_warning_dev.hide()
  626. if self.vm.is_running():
  627. self.dev_list.setEnabled(False)
  628. self.turn_off_vm_to_modify_devs.setVisible(True)
  629. else:
  630. self.dev_list.setEnabled(True)
  631. self.turn_off_vm_to_modify_devs.setVisible(False)
  632. def __apply_devices_tab__(self):
  633. msg = []
  634. sth_changed = False
  635. added = []
  636. try:
  637. for i in range(self.dev_list.selected_list.count()):
  638. item = self.dev_list.selected_list.item(i)
  639. if item.slot not in self.vm.pcidevs:
  640. added.append(item)
  641. if self.dev_list.selected_list.count() - len(added) < len(self.vm.pcidevs): #sth removed
  642. sth_changed = True
  643. elif len(added) > 0:
  644. sth_changed = True
  645. if sth_changed == True:
  646. pcidevs = []
  647. for i in range(self.dev_list.selected_list.count()):
  648. slot = self.dev_list.selected_list.item(i).slot
  649. pcidevs.append(slot)
  650. self.vm.pcidevs = pcidevs
  651. self.anything_changed = True
  652. except Exception as ex:
  653. msg.append(str(ex))
  654. return msg
  655. def include_in_balancing_state_changed(self, state):
  656. for r in range (self.services_list.count()):
  657. item = self.services_list.item(r)
  658. if str(item.text()) == 'meminfo-writer':
  659. item.setCheckState(state)
  660. break
  661. if self.dev_list.selected_list.count() > 0:
  662. if state == QtCore.Qt.Checked:
  663. self.dmm_warning_adv.show()
  664. self.dmm_warning_dev.show()
  665. else:
  666. self.dmm_warning_adv.hide()
  667. self.dmm_warning_dev.hide()
  668. self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
  669. def devices_selection_changed(self):
  670. if self.include_in_balancing.isChecked():
  671. if self.dev_list.selected_list.count() > 0 :
  672. self.dmm_warning_adv.show()
  673. self.dmm_warning_dev.show()
  674. else:
  675. self.dmm_warning_adv.hide()
  676. self.dmm_warning_dev.hide()
  677. ######## services tab
  678. def __init_services_tab__(self):
  679. for srv in self.vm.services:
  680. item = QListWidgetItem(srv)
  681. if self.vm.services[srv] == True:
  682. item.setCheckState(QtCore.Qt.Checked)
  683. else:
  684. item.setCheckState(QtCore.Qt.Unchecked)
  685. self.services_list.addItem(item)
  686. self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked)
  687. self.new_srv_dict = copy(self.vm.services)
  688. def __add_service__(self):
  689. srv = str(self.service_line_edit.text()).strip()
  690. if srv != "":
  691. if srv in self.new_srv_dict:
  692. QMessageBox.information(None, "",
  693. self.tr("Service already on the list!"))
  694. else:
  695. item = QListWidgetItem(srv)
  696. item.setCheckState(QtCore.Qt.Checked)
  697. self.services_list.addItem(item)
  698. self.new_srv_dict[srv] = True
  699. def __remove_service__(self):
  700. item = self.services_list.currentItem()
  701. if not item:
  702. return
  703. if str(item.text()) == 'meminfo-writer':
  704. QMessageBox.information(None,
  705. self.tr("Service can not be removed"),
  706. self.tr("Service meminfo-writer can not be removed from the list."))
  707. return
  708. row = self.services_list.currentRow()
  709. item = self.services_list.takeItem(row)
  710. del self.new_srv_dict[str(item.text())]
  711. def services_item_clicked(self, item):
  712. if str(item.text()) == 'meminfo-writer':
  713. if item.checkState() == QtCore.Qt.Checked:
  714. if not self.include_in_balancing.isChecked():
  715. self.include_in_balancing.setChecked(True)
  716. elif item.checkState() == QtCore.Qt.Unchecked:
  717. if self.include_in_balancing.isChecked():
  718. self.include_in_balancing.setChecked(False)
  719. def __apply_services_tab__(self):
  720. msg = []
  721. try:
  722. for r in range (self.services_list.count()):
  723. item = self.services_list.item(r)
  724. self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked)
  725. balancing_was_checked = (self.vm.services['meminfo-writer']==True)
  726. balancing_is_checked = self.include_in_balancing.isChecked()
  727. meminfo_writer_checked = (self.new_srv_dict['meminfo-writer'] == True)
  728. if balancing_is_checked != meminfo_writer_checked:
  729. if balancing_is_checked != balancing_was_checked:
  730. self.new_srv_dict['meminfo-writer'] = balancing_is_checked
  731. if self.new_srv_dict != self.vm.services:
  732. self.vm.services = self.new_srv_dict
  733. self.anything_changed = True
  734. except Exception as ex:
  735. msg.append(str(ex))
  736. return msg
  737. ######### firewall tab related
  738. def set_fw_model(self, model):
  739. self.fw_model = model
  740. self.rulesTreeView.setModel(model)
  741. self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents)
  742. self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch)
  743. self.set_allow(model.allow)
  744. self.dnsCheckBox.setChecked(model.allowDns)
  745. self.icmpCheckBox.setChecked(model.allowIcmp)
  746. self.yumproxyCheckBox.setChecked(model.allowYumProxy)
  747. if model.tempFullAccessExpireTime:
  748. self.tempFullAccess.setChecked(True)
  749. self.tempFullAccessTime.setValue(
  750. (model.tempFullAccessExpireTime -
  751. int(datetime.datetime.now().strftime("%s")))/60)
  752. def set_allow(self, allow):
  753. self.policyAllowRadioButton.setChecked(allow)
  754. self.policyDenyRadioButton.setChecked(not allow)
  755. self.policy_changed(allow)
  756. def policy_changed(self, checked):
  757. self.tempFullAccessWidget.setEnabled(self.policyDenyRadioButton.isChecked())
  758. def new_rule_button_pressed(self):
  759. dialog = NewFwRuleDlg()
  760. self.run_rule_dialog(dialog)
  761. def edit_rule_button_pressed(self):
  762. dialog = NewFwRuleDlg()
  763. dialog.set_ok_enabled(True)
  764. selected = self.rulesTreeView.selectedIndexes()
  765. if len(selected) > 0:
  766. row = self.rulesTreeView.selectedIndexes().pop().row()
  767. address = self.fw_model.get_column_string(0, row).replace(' ', '')
  768. dialog.addressComboBox.setItemText(0, address)
  769. dialog.addressComboBox.setCurrentIndex(0)
  770. service = self.fw_model.get_column_string(1, row)
  771. if service == "any":
  772. service = ""
  773. dialog.serviceComboBox.setItemText(0, service)
  774. dialog.serviceComboBox.setCurrentIndex(0)
  775. protocol = self.fw_model.get_column_string(2, row)
  776. if protocol == "tcp":
  777. dialog.tcp_radio.setChecked(True)
  778. elif protocol == "udp":
  779. dialog.udp_radio.setChecked(True)
  780. else:
  781. dialog.any_radio.setChecked(True)
  782. self.run_rule_dialog(dialog, row)
  783. def delete_rule_button_pressed(self):
  784. for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]):
  785. self.fw_model.removeChild(i)
  786. def run_rule_dialog(self, dialog, row = None):
  787. if dialog.exec_():
  788. address = str(dialog.addressComboBox.currentText())
  789. service = str(dialog.serviceComboBox.currentText())
  790. port = None
  791. port2 = None
  792. unmask = address.split("/", 1)
  793. if len(unmask) == 2:
  794. address = unmask[0]
  795. netmask = int(unmask[1])
  796. else:
  797. netmask = 32
  798. if address == "*":
  799. address = "0.0.0.0"
  800. netmask = 0
  801. if dialog.any_radio.isChecked():
  802. protocol = "any"
  803. port = 0
  804. else:
  805. if dialog.tcp_radio.isChecked():
  806. protocol = "tcp"
  807. elif dialog.udp_radio.isChecked():
  808. protocol = "udp"
  809. else:
  810. protocol = "any"
  811. try:
  812. range = service.split("-", 1)
  813. if len(range) == 2:
  814. port = int(range[0])
  815. port2 = int(range[1])
  816. else:
  817. port = int(service)
  818. except (TypeError, ValueError) as ex:
  819. port = self.fw_model.get_service_port(service)
  820. if port is not None:
  821. if port2 is not None and port2 <= port:
  822. QMessageBox.warning(None, self.tr("Invalid service ports range"),
  823. unicode(self.tr("Port {0} is lower than port {1}.")).format(
  824. port2, port))
  825. else:
  826. item = {"address": address,
  827. "netmask": netmask,
  828. "portBegin": port,
  829. "portEnd": port2,
  830. "proto": protocol,
  831. }
  832. if row is not None:
  833. self.fw_model.setChild(row, item)
  834. else:
  835. self.fw_model.appendChild(item)
  836. else:
  837. QMessageBox.warning(None, self.tr("Invalid service name"),
  838. unicode(self.tr("Service '{0}' is unknown.")).format(service))
  839. # Bases on the original code by:
  840. # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
  841. def handle_exception( exc_type, exc_value, exc_traceback ):
  842. import sys
  843. import os.path
  844. import traceback
  845. filename, line, dummy, dummy = traceback.extract_tb( exc_traceback ).pop()
  846. filename = os.path.basename( filename )
  847. error = "%s: %s" % ( exc_type.__name__, exc_value )
  848. strace = ""
  849. stacktrace = traceback.extract_tb( exc_traceback )
  850. while len(stacktrace) > 0:
  851. (filename, line, func, txt) = stacktrace.pop()
  852. strace += "----\n"
  853. strace += "line: %s\n" %txt
  854. strace += "func: %s\n" %func
  855. strace += "line no.: %d\n" %line
  856. strace += "file: %s\n" %filename
  857. msg_box = QMessageBox()
  858. msg_box.setDetailedText(strace)
  859. msg_box.setIcon(QMessageBox.Critical)
  860. msg_box.setWindowTitle( "Houston, we have a problem...")
  861. msg_box.setText("Whoops. A critical error has occured. This is most likely a bug "
  862. "in Qubes Manager.<br><br>"
  863. "<b><i>%s</i></b>" % error +
  864. "<br/>at line <b>%d</b><br/>of file %s.<br/><br/>"
  865. % ( line, filename ))
  866. msg_box.exec_()
  867. def main():
  868. global qubes_host
  869. qubes_host = QubesHost()
  870. global app
  871. app = QApplication(sys.argv)
  872. app.setOrganizationName("The Qubes Project")
  873. app.setOrganizationDomain("http://qubes-os.org")
  874. app.setApplicationName("Qubes VM Settings")
  875. sys.excepthook = handle_exception
  876. qvm_collection = QubesVmCollection()
  877. qvm_collection.lock_db_for_reading()
  878. qvm_collection.load()
  879. qvm_collection.unlock_db()
  880. vm = None
  881. tab = "basic"
  882. if len(sys.argv) > 1:
  883. vm = qvm_collection.get_vm_by_name(sys.argv[1])
  884. if vm is None or vm.qid not in qvm_collection:
  885. QMessageBox.critical(None, "Qubes VM Settings Error",
  886. "A VM with the name '{0}' does not exist in the system.".format(sys.argv[1]))
  887. sys.exit(1)
  888. if len(sys.argv) > 2:
  889. tab_arg = sys.argv[2]
  890. if tab_arg in VMSettingsWindow.tabs_indices:
  891. tab = tab_arg
  892. else: QMessageBox.warning(None, "Qubes VM Settings Error",
  893. "There is no such tab as '{0}'. Opening default tab instead.".format(tab_arg))
  894. else:
  895. vms_list = [vm.name for vm in qvm_collection.values() if (vm.is_appvm() or vm.is_template())]
  896. vmname = QInputDialog.getItem(None, "Select VM", "Select VM:", vms_list, editable = False)
  897. if not vmname[1]:
  898. sys.exit(1)
  899. vm = qvm_collection.get_vm_by_name(vmname[0])
  900. global settings_window
  901. settings_window = VMSettingsWindow(vm, app, qvm_collection, tab)
  902. settings_window.show()
  903. app.exec_()
  904. app.exit()
  905. if __name__ == "__main__":
  906. main()
  907. # vim:sw=4:et: