diff --git a/qubesmanager/qube_manager.py b/qubesmanager/qube_manager.py
index afc59ce..17e9e62 100644
--- a/qubesmanager/qube_manager.py
+++ b/qubesmanager/qube_manager.py
@@ -50,6 +50,7 @@ from . import global_settings
from . import restore
from . import backup
from . import log_dialog
+from . import utils as manager_utils
class SearchBox(QtGui.QLineEdit):
@@ -413,7 +414,11 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
def check_updates(self):
for vm in self.qubes_app.domains:
if vm.klass in {'TemplateVM', 'StandaloneVM'}:
- self.vms_in_table[vm.qid].update()
+ try:
+ self.vms_in_table[vm.qid].update()
+ except exc.QubesException:
+ # the VM might have vanished in the meantime
+ pass
def on_domain_added(self, _, domain):
#needs to clear cache
@@ -686,26 +691,20 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
vm = self.get_selected_vm()
- dependencies = utils.vm_usage(self.qubes_app, vm)
+ dependencies = utils.vm_dependencies(self.qubes_app, vm)
if dependencies:
- list_text = "
"
- for (holder, prop) in dependencies:
- if holder is None:
- list_text += "- Global property {}
".format(prop)
- else:
- list_text += "- {} for qube {}
".format(
- prop, holder.name)
- list_text += "
"
+ list_text = "
" + \
+ manager_utils.format_dependencies_list(dependencies) + \
+ "
"
info_dialog = QtGui.QMessageBox(self)
info_dialog.setWindowTitle(self.tr("Warning!"))
info_dialog.setText(
self.tr("This qube cannot be removed. It is used as:"
- "
" + list_text + "If you want to "
- " remove this qube, you should remove or change "
- " settings of each qube or setting that uses"
- " it."))
+ "
{} If you want to remove this qube, "
+ "you should remove or change settings of each qube "
+ "or setting that uses it.".format(list_text)))
info_dialog.setModal(False)
info_dialog.show()
self.qt_app.processEvents()
@@ -1001,8 +1000,24 @@ class VmManagerWindow(ui_qubemanager.Ui_VmManagerWindow, QtGui.QMainWindow):
settings_window = settings.VMSettingsWindow(
vm, self.qt_app, "basic")
settings_window.exec_()
- self.vms_in_table[vm.qid].update()
+ vm_deleted = False
+
+ try:
+ # the VM might not exist after running Settings - it might
+ # have been cloned or removed
+ self.vms_in_table[vm.qid].update()
+ except exc.QubesException:
+ # TODO: this will be replaced by proper signal handling once
+ # settings are migrated to AdminAPI
+ vm_deleted = True
+
+ if vm_deleted:
+ for row in self.vms_in_table:
+ try:
+ self.vms_in_table[row].update()
+ except exc.QubesException:
+ pass
# noinspection PyArgumentList
@QtCore.pyqtSlot(name='on_action_appmenus_triggered')
diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py
index adcf21c..8c1d705 100755
--- a/qubesmanager/settings.py
+++ b/qubesmanager/settings.py
@@ -34,6 +34,7 @@ import traceback
import sys
from qubesadmin.tools import QubesArgumentParser
from qubesadmin import devices
+from qubesadmin import utils as admin_utils
import qubesadmin.exc
from . import utils
@@ -481,10 +482,35 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
return False
return True
- def _rename_vm(self, t_monitor, name):
+ def _rename_vm(self, t_monitor, name, dependencies):
try:
- self.vm.app.clone_vm(self.vm, name)
- del self.vm.app.domains[self.vm.name]
+ new_vm = self.vm.app.clone_vm(self.vm, name)
+
+ failed_props = []
+
+ for (holder, prop) in dependencies:
+ try:
+ if holder is None:
+ setattr(self.vm.app, prop, new_vm)
+ else:
+ setattr(holder, prop, new_vm)
+ except qubesadmin.exc.QubesException as qex:
+ failed_props += (holder, prop)
+
+ if not failed_props:
+ del self.vm.app.domains[self.vm.name]
+ else:
+ list_text = utils.format_dependencies_list(failed_props)
+
+ QtGui.QMessageBox.warning(
+ self,
+ self.tr("Warning: rename partially unsuccessful"),
+ self.tr("Some properties could not be changed to the new "
+ "name. The system has now both {} and {} qubes. "
+ "To resolve this, please check and change the "
+ "following properties and remove the qube {} "
+ "manually.
".format(
+ self.vm.name, name, self.vm.name) + list_text))
except qubesadmin.exc.QubesException as qex:
t_monitor.set_error_msg(str(qex))
@@ -494,13 +520,31 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
t_monitor.set_finished()
def rename_vm(self):
+
+ dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
+
+ running_dependencies = [vm.name for (vm, prop) in dependencies
+ if vm and prop == 'template'
+ and vm.is_running()]
+
+ if running_dependencies:
+ QtGui.QMessageBox.warning(
+ self,
+ self.tr("Qube cannot be renamed!"),
+ self.tr(
+ "The following qubes using this qube as a template are "
+ "running:
{}.
In order to rename this qube, you "
+ "must first shut them down.".format(
+ ", ".join(running_dependencies))))
+ return
+
new_vm_name, ok = QtGui.QInputDialog.getText(
self,
self.tr('Rename qube'),
self.tr('New name: (WARNING: all other changes will be discarded)'))
if ok:
- if self._run_in_thread(self._rename_vm, new_vm_name):
+ if self._run_in_thread(self._rename_vm, new_vm_name, dependencies):
self.done(0)
def _remove_vm(self, t_monitor):
@@ -516,6 +560,21 @@ class VMSettingsWindow(ui_settingsdlg.Ui_SettingsDialog, QtGui.QDialog):
def remove_vm(self):
+ dependencies = admin_utils.vm_dependencies(self.vm.app, self.vm)
+
+ if dependencies:
+ list_text = utils.format_dependencies_list(dependencies)
+ QtGui.QMessageBox.warning(
+ self,
+ self.tr("Qube cannot be removed!"),
+ self.tr("This qube cannot be removed. It is used as:"
+ "
{} If you want to remove this qube, "
+ "you should remove or change settings of each qube "
+ "or setting that uses it.".format(list_text)))
+
+ return
+
+
answer, ok = QtGui.QInputDialog.getText(
self,
self.tr('Delete qube'),
diff --git a/qubesmanager/utils.py b/qubesmanager/utils.py
index 01913b8..80d9b40 100644
--- a/qubesmanager/utils.py
+++ b/qubesmanager/utils.py
@@ -172,3 +172,18 @@ def get_path_from_vm(vm, service_name):
assert '\0' not in untrusted_path
return untrusted_path.strip()
raise ValueError('Unexpected characters in path.')
+
+
+def format_dependencies_list(dependencies):
+ """Given a list of tuples representing properties, formats them in
+ a readable list."""
+
+ list_text = ""
+ for (holder, prop) in dependencies:
+ if holder is None:
+ list_text += "- Global property {}
".format(prop)
+ else:
+ list_text += "- {} for qube {}
".format(
+ prop, holder.name)
+
+ return list_text