diff --git a/backupdlg.ui b/backupdlg.ui index f26c0af..f782343 100644 --- a/backupdlg.ui +++ b/backupdlg.ui @@ -22,20 +22,128 @@ - - - - 9 - 50 - false - false - false - - - - Select VMs to backup: - - + + + + + + 0 + 0 + + + + Shutdown all running selected VMs + + + + :/shutdownvm.png:/shutdownvm.png + + + + + + + + 0 + 0 + + + + Refresh running states. + + + + + + + + 0 + 0 + + + + + 75 + true + true + + + + color:rgb(255, 0, 0) + + + Some of the selected VMs are running (red). Running VMs can not be backuped! + + + true + + + + + + + + 9 + 50 + false + false + false + + + + Select VMs to backup: + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Total size: + + + + + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + @@ -187,6 +295,8 @@ p, li { white-space: pre-wrap; } - + + + diff --git a/globalsettingsdlg.ui b/globalsettingsdlg.ui index 3c195a7..c555ea1 100644 --- a/globalsettingsdlg.ui +++ b/globalsettingsdlg.ui @@ -90,7 +90,7 @@ - false + true Minimal VM's memory: @@ -100,36 +100,36 @@ - false + true - MB + MiB 999999999 - 10 + 50 - false + true - dom0 memory margin: + dom0 memory boost: - + - false + true - MB + MiB 999999999 diff --git a/icons/apps.png b/icons/apps.png new file mode 100644 index 0000000..24e6ec9 Binary files /dev/null and b/icons/apps.png differ diff --git a/icons/backup.png b/icons/backup.png new file mode 100644 index 0000000..f560da6 Binary files /dev/null and b/icons/backup.png differ diff --git a/icons/firewall.png b/icons/firewall.png deleted file mode 100644 index dd93c58..0000000 Binary files a/icons/firewall.png and /dev/null differ diff --git a/icons/global-settings.png b/icons/global-settings.png new file mode 100644 index 0000000..b837c7a Binary files /dev/null and b/icons/global-settings.png differ diff --git a/icons/restore.png b/icons/restore.png new file mode 100644 index 0000000..38e7198 Binary files /dev/null and b/icons/restore.png differ diff --git a/icons/running.png b/icons/running.png new file mode 100644 index 0000000..2a4f8d9 Binary files /dev/null and b/icons/running.png differ diff --git a/icons/settings.png b/icons/settings.png new file mode 100644 index 0000000..e579edb Binary files /dev/null and b/icons/settings.png differ diff --git a/icons/show-all-running.png b/icons/show-all-running.png new file mode 100644 index 0000000..d03c60c Binary files /dev/null and b/icons/show-all-running.png differ diff --git a/icons/wall.png b/icons/wall.png new file mode 100644 index 0000000..de94384 Binary files /dev/null and b/icons/wall.png differ diff --git a/mainwindow.ui b/mainwindow.ui index 29b0e96..491cab8 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -273,6 +273,10 @@ + + + + @@ -353,7 +357,7 @@ - :/appsprefs.png:/appsprefs.png + :/apps.png:/apps.png Select VM applications @@ -386,8 +390,8 @@ - :/showallvms.png - :/showallvms.png:/showallvms.png + :/show-all-running.png + :/showallvms.png:/show-all-running.png Show/Hide inactive VMs @@ -399,7 +403,7 @@ - :/redfirewall.png:/redfirewall.png + :/firewall.png:/firewall.png Edit VM Firewall rules @@ -502,7 +506,7 @@ - :/root.png:/root.png + :/settings.png:/settings.png Settings @@ -512,16 +516,28 @@ + + + :/restore.png:/restore.png + Restore VMs from backup + + + :/backup.png:/backup.png + Backup VMs + + + :/global-settings.png:/global-settings.png + Global settings diff --git a/qubesmanager/backup.py b/qubesmanager/backup.py index 47efca4..4887aa1 100644 --- a/qubesmanager/backup.py +++ b/qubesmanager/backup.py @@ -23,6 +23,7 @@ import sys import os +import signal from PyQt4.QtCore import * from PyQt4.QtGui import * @@ -48,18 +49,20 @@ from ui_backupdlg import * from multiselectwidget import * from backup_utils import * +import grp,pwd class BackupVMsWindow(Ui_Backup, QWizard): __pyqtSignals__ = ("backup_progress(int)",) - def __init__(self, app, qvm_collection, blk_manager, parent=None): + def __init__(self, app, qvm_collection, blk_manager, shutdown_vm_func, parent=None): super(BackupVMsWindow, self).__init__(parent) self.app = app self.qvm_collection = qvm_collection self.blk_manager = blk_manager + self.shutdown_vm_func = shutdown_vm_func self.dev_mount_path = None self.backup_dir = None @@ -75,12 +78,18 @@ class BackupVMsWindow(Ui_Backup, QWizard): self.setupUi(self) + self.show_running_vms_warning(False) self.dir_line_edit.setReadOnly(True) self.select_vms_widget = MultiSelectWidget(self) self.verticalLayout.insertWidget(1, self.select_vms_widget) self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed) + self.connect(self.select_vms_widget, SIGNAL("selected_changed()"), self.check_running) + self.connect(self.select_vms_widget, SIGNAL("items_removed(list)"), self.vms_removed) + self.connect(self.select_vms_widget, SIGNAL("items_added(list)"), self.vms_added) + self.refresh_button.clicked.connect(self.check_running) + self.shutdown_running_vms_button.clicked.connect(self.shutdown_all_running_selected) self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated) self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue) @@ -89,30 +98,126 @@ class BackupVMsWindow(Ui_Backup, QWizard): #FIXME #this causes to run isComplete() twice, I don't know why self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()")) - + + self.total_size = 0 self.__fill_vms_list__() fill_devs_list(self) + def show_running_vms_warning(self, show): + self.running_vms_warning.setVisible(show) + self.shutdown_running_vms_button.setVisible(show) + self.refresh_button.setVisible(show) + + class VmListItem(QListWidgetItem): + def __init__(self, vm): + self.vm = vm + if vm.qid == 0: + local_user = grp.getgrnam('qubes').gr_mem[0] + home_dir = pwd.getpwnam(local_user).pw_dir + self.size = qubesutils.get_disk_usage(home_dir) + else: + self.size = self.get_vm_size(vm) + super(BackupVMsWindow.VmListItem, self).__init__(vm.name+ " (" + qubesutils.size_to_human(self.size) + ")") + + def get_vm_size(self, vm): + size = 0 + if vm.private_img is not None: + size += vm.get_disk_usage (vm.private_img) + + if vm.updateable: + size += vm.get_disk_usage(vm.root_img) + + return size + + def __fill_vms_list__(self): for vm in self.qvm_collection.values(): - if vm.is_running() and vm.qid != 0: - self.excluded.append(vm.name) - continue - if vm.is_appvm() and vm.internal: - self.excluded.append(vm.name) continue - if vm.is_template() and vm.installed_by_rpm: - self.excluded.append(vm.name) continue + item = BackupVMsWindow.VmListItem(vm) if vm.include_in_backups == True: - self.select_vms_widget.selected_list.addItem(vm.name) + self.select_vms_widget.selected_list.addItem(item) + self.total_size += item.size else: - self.select_vms_widget.available_list.addItem(vm.name) + self.select_vms_widget.available_list.addItem(item) + self.check_running() + self.total_size_label.setText(qubesutils.size_to_human(self.total_size)) + + def vms_added(self, items): + for i in items: + self.total_size += i.size + self.total_size_label.setText(qubesutils.size_to_human(self.total_size)) + + def vms_removed(self, items): + for i in items: + self.total_size -= i.size + self.total_size_label.setText(qubesutils.size_to_human(self.total_size)) + + def check_running(self): + some_selected_vms_running = False + for i in range(self.select_vms_widget.selected_list.count()): + item = self.select_vms_widget.selected_list.item(i) + if item.vm.is_running() and item.vm.qid != 0: + item.setForeground(QBrush(QColor(255, 0, 0))) + some_selected_vms_running = True + else: + item.setForeground(QBrush(QColor(0, 0, 0))) + + self.show_running_vms_warning(some_selected_vms_running) + + for i in range(self.select_vms_widget.available_list.count()): + item = self.select_vms_widget.available_list.item(i) + if item.vm.is_running() and item.vm.qid != 0: + item.setForeground(QBrush(QColor(255, 0, 0))) + else: + item.setForeground(QBrush(QColor(0, 0, 0))) + + return some_selected_vms_running + + def shutdown_all_running_selected(self): + names = [] + vms = [] + for i in range(self.select_vms_widget.selected_list.count()): + item = self.select_vms_widget.selected_list.item(i) + if item.vm.is_running() and item.vm.qid != 0: + names.append(item.vm.name) + vms.append(item.vm) + + if len(vms) == 0: + return; + + reply = QMessageBox.question(None, "VM Shutdown Confirmation", + "Are you sure you want to power down the following VMs: {0}?
" + "This will shutdown all the running applications within them.".format(', '.join(names)), + QMessageBox.Yes | QMessageBox.Cancel) + + self.app.processEvents() + + if reply == QMessageBox.Yes: + for vm in vms: + self.shutdown_vm_func(vm) + + progress = QProgressDialog ("Shutting down VMs {0}...".format(', '.join(names)), "", 0, 0) + progress.setModal(True) + progress.show() + + wait_time = 15.0 + wait_for = wait_time + while self.check_running() and wait_for > 0: + self.app.processEvents() + time.sleep (0.1) + wait_for -= 0.1 + + progress.hide() + + if self.check_running(): + QMessageBox.information(None, "Strange", "Could not power down the VMs in {0} seconds...".format(wait_time)) + + - def dev_combobox_activated(self, idx): dev_combobox_activated(self, idx) @@ -123,17 +228,30 @@ class BackupVMsWindow(Ui_Backup, QWizard): def validateCurrentPage(self): if self.currentPage() is self.select_vms_page: + for i in range(self.select_vms_widget.selected_list.count()): + if self.check_running() == True: + QMessageBox.information(None, "Wait!", "Some selected VMs are running. Running VMs can not be backuped. Please shut them down or remove them from the list.") + return False + + del self.excluded[:] for i in range(self.select_vms_widget.available_list.count()): - vmname = self.select_vms_widget.available_list.item(i).text() + vmname = self.select_vms_widget.available_list.item(i).vm.name self.excluded.append(vmname) + return True def gather_output(self, s): self.func_output.append(s) def update_progress_bar(self, value): - self.emit(SIGNAL("backup_progress(int)"), value) + if value == 100: + self.emit(SIGNAL("backup_progress(int)"), value) + def check_backup_progress(self, initial_usage, total_backup_size): + du = qubesutils.get_disk_usage(self.backup_dir) + done = du - initial_usage + percent = int((float(done)/total_backup_size)*100) + return percent def __do_backup__(self, thread_monitor): msg = [] @@ -152,23 +270,34 @@ class BackupVMsWindow(Ui_Backup, QWizard): def current_page_changed(self, id): if self.currentPage() is self.confirm_page: del self.func_output[:] - self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output) + try: + self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output) + except Exception as ex: + QMessageBox.critical(None, "Error while prepering backup.", "ERROR: {0}".format(ex)) self.textEdit.setReadOnly(True) self.textEdit.setFontFamily("Monospace") self.textEdit.setText("\n".join(self.func_output)) elif self.currentPage() is self.commit_page: - self.button(self.CancelButton).setDisabled(True) self.button(self.FinishButton).setDisabled(True) + self.button(self.CancelButton).setDisabled(True) self.thread_monitor = ThreadMonitor() + initial_usage = qubesutils.get_disk_usage(self.backup_dir) thread = threading.Thread (target= self.__do_backup__ , args=(self.thread_monitor,)) thread.daemon = True thread.start() + self.button(self.CancelButton).setDisabled(False) + counter = 0 while not self.thread_monitor.is_finished(): self.app.processEvents() time.sleep (0.1) + counter += 1 + if counter == 20: + progress = self.check_backup_progress(initial_usage, self.total_size) + self.progress_bar.setValue(progress) + counter = 0 if not self.thread_monitor.success: QMessageBox.warning (None, "Backup error!", "ERROR: {1}".format(self.vm.name, self.thread_monitor.error_msg)) @@ -179,6 +308,25 @@ class BackupVMsWindow(Ui_Backup, QWizard): def reject(self): + #cancell clicked while the backup is in progress. + #calling kill on cp. + if self.currentPage() is self.commit_page: + manager_pid = os.getpid() + cp_pid_cmd = ["ps" ,"--ppid", str(manager_pid)] + pid = None + + while not self.thread_monitor.is_finished(): + cp_pid = subprocess.Popen(cp_pid_cmd, stdout = subprocess.PIPE) + output = cp_pid.stdout.read().split("\n") + + for l in output: + if l.endswith("cp"): + pid = l.split(" ")[1] + break + if pid != None: + os.kill(int(pid), signal.SIGTERM) + break + if self.dev_mount_path != None: umount_device(self.dev_mount_path) self.done(0) @@ -213,8 +361,6 @@ def handle_exception( exc_type, exc_value, exc_traceback ): % ( line, filename )) - - def main(): global qubes_host diff --git a/qubesmanager/backup_utils.py b/qubesmanager/backup_utils.py index 4cec39c..368b546 100644 --- a/qubesmanager/backup_utils.py +++ b/qubesmanager/backup_utils.py @@ -72,6 +72,8 @@ def umount_device(dev_mount_path): def fill_devs_list(dialog): dialog.dev_combobox.clear() dialog.dev_combobox.addItem("None") + + dialog.blk_manager.blk_lock.acquire() for a in dialog.blk_manager.attached_devs: if dialog.blk_manager.attached_devs[a]['attached_to']['vm'] == dialog.vm.name : att = a + " " + unicode(dialog.blk_manager.attached_devs[a]['size']) + " " + dialog.blk_manager.attached_devs[a]['desc'] @@ -79,6 +81,8 @@ def fill_devs_list(dialog): for a in dialog.blk_manager.free_devs: att = a + " " + unicode(dialog.blk_manager.free_devs[a]['size']) + " " + dialog.blk_manager.free_devs[a]['desc'] dialog.dev_combobox.addItem(att, QVariant(a)) + dialog.blk_manager.blk_lock.release() + dialog.dev_combobox.setCurrentIndex(0) #current selected is null "" dialog.prev_dev_idx = 0 dialog.dir_line_edit.clear() @@ -107,6 +111,7 @@ def dev_combobox_activated(dialog, idx): if dialog.dev_combobox.currentText() != "None": #An existing device chosen dev_name = str(dialog.dev_combobox.itemData(idx).toString()) + dialog.blk_manager.blk_lock.acquire() if dev_name in dialog.blk_manager.free_devs: if dev_name.startswith(dialog.vm.name): # originally attached to dom0 dev_path = "/dev/"+dev_name.split(":")[1] @@ -118,6 +123,7 @@ def dev_combobox_activated(dialog, idx): if dev_name in dialog.blk_manager.attached_devs: #is attached to dom0 assert dialog.blk_manager.attached_devs[dev_name]['attached_to']['vm'] == dialog.vm.name dev_path = "/dev/" + dialog.blk_manager.attached_devs[dev_name]['attached_to']['frontend'] + dialog.blk_manager.blk_lock.release() #check if device mounted dialog.dev_mount_path = check_if_mounted(dev_path) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index f47c805..5cc58d6 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -43,7 +43,12 @@ from operator import itemgetter from ui_globalsettingsdlg import * +from ConfigParser import SafeConfigParser +from qubes.qubesutils import parse_size +from qubes import qmemman_algo + dont_keep_dvm_in_memory_path = '/var/lib/qubes/dvmdata/dont_use_shm' +qmemman_config_path = '/etc/qubes/qmemman.conf' class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): @@ -187,13 +192,80 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): def __init_mem_defaults__(self): + + #qmemman settings + self.qmemman_config = SafeConfigParser() + self.vm_min_mem_val = str(qmemman_algo.MIN_PREFMEM) + self.dom0_mem_boost_val = str(qmemman_algo.DOM0_MEM_BOOST) + self.qmemman_config.read(qmemman_config_path) + if self.qmemman_config.has_section('global'): + self.vm_min_mem_val = self.qmemman_config.get('global', 'vm-min-mem') + self.dom0_mem_boost_val = self.qmemman_config.get('global', 'dom0-mem-boost') + + self.vm_min_mem_val = parse_size(self.vm_min_mem_val) + self.dom0_mem_boost_val = parse_size(self.dom0_mem_boost_val) + + self.min_vm_mem.setValue(self.vm_min_mem_val/1024/1024) + self.dom0_mem_boost.setValue(self.dom0_mem_boost_val/1024/1024) + #keep dispvm in memory exists = os.path.exists(dont_keep_dvm_in_memory_path) self.dispvm_in_memory.setChecked( not exists) def __apply_mem_defaults__(self): + + #qmemman settings + current_min_vm_mem = self.min_vm_mem.value() + current_dom0_mem_boost = self.dom0_mem_boost.value() + + if current_min_vm_mem*1024*1024 != self.vm_min_mem_val or current_dom0_mem_boost*1024*1024 != self.dom0_mem_boost_val: + + current_min_vm_mem = str(current_min_vm_mem)+'M' + current_dom0_mem_boost = str(current_dom0_mem_boost)+'M' + + if not self.qmemman_config.has_section('global'): + #add the whole section + self.qmemman_config.add_section('global') + self.qmemman_config.set('global', 'vm-min-mem', current_min_vm_mem) + self.qmemman_config.set('global', 'dom0-mem-boost', current_dom0_mem_boost) + self.qmemman_config.set('global', 'cache-margin-factor', str(qmemman_algo.CACHE_FACTOR)) + + qmemman_config_file = open(qmemman_config_path, 'a') + self.qmemman_config.write(qmemman_config_file) + qmemman_config_file.close() + + else: + #If there already is a 'global' section, we don't use SafeConfigParser.write() - it would get rid of all the comments... + + lines_to_add = {} + lines_to_add['vm-min-mem'] = "vm-min-mem = " + current_min_vm_mem + "\n" + lines_to_add['dom0-mem-boost'] = "dom0-mem-boost = " + current_dom0_mem_boost +"\n" + + config_lines = [] + + qmemman_config_file = open(qmemman_config_path, 'r') + for l in qmemman_config_file: + if l.strip().startswith('vm-min-mem'): + config_lines.append(lines_to_add['vm-min-mem']) + del lines_to_add['vm-min-mem'] + elif l.strip().startswith('dom0-mem-boost'): + config_lines.append(lines_to_add['dom0-mem-boost']) + del lines_to_add['dom0-mem-boost'] + else: + config_lines.append(l) + + qmemman_config_file.close() + + for l in lines_to_add: + config_lines.append(l) + + qmemman_config_file = open(qmemman_config_path, 'w') + qmemman_config_file.writelines(config_lines) + qmemman_config_file.close() + + self.anything_changed = True #keep dispvm in memory was_checked = not os.path.exists(dont_keep_dvm_in_memory_path) @@ -206,6 +278,7 @@ class GlobalSettingsWindow(Ui_GlobalSettings, QDialog): os.remove(dont_keep_dvm_in_memory_path) self.anything_changed = True + def reject(self): self.done(0) diff --git a/qubesmanager/main.py b/qubesmanager/main.py index 9b46970..d12f083 100755 --- a/qubesmanager/main.py +++ b/qubesmanager/main.py @@ -557,9 +557,14 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): self.setupUi(self) self.toolbar = self.toolBar + self.qubes_watch = qubesutils.QubesWatch() self.qvm_collection = QubesVmCollection() self.blk_manager = QubesBlockDevicesManager(self.qvm_collection) - + self.qubes_watch.setup_block_watch(self.blk_manager.block_devs_event) + self.blk_watch_thread = threading.Thread(target=self.qubes_watch.watch_loop) + self.blk_watch_thread.daemon = True + self.blk_watch_thread.start() + self.connect(self.table, SIGNAL("itemSelectionChanged()"), self.table_selection_changed) self.table.setColumnWidth(0, self.column_width) @@ -794,8 +799,10 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): rows_with_blk = None if update_devs == True: rows_with_blk = [] + self.blk_manager.blk_lock.acquire() for d in self.blk_manager.attached_devs: rows_with_blk.append( self.blk_manager.attached_devs[d]['attached_to']['vm']) + self.blk_manager.blk_lock.release() if self.counter % 3 == 0 or out_of_schedule: (self.last_measure_time, self.last_measure_results) = \ @@ -843,7 +850,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): def update_block_devices(self): - res, msg = self.blk_manager.update() + res, msg = self.blk_manager.check_for_updates() if msg != None and len(msg) > 0: str = "\n".join(msg) trayIcon.showMessage ("Qubes Manager", str, msecs=5000) @@ -1142,16 +1149,22 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): app.processEvents() if reply == QMessageBox.Yes: - try: - subprocess.check_call (["/usr/sbin/xl", "shutdown", vm.name]) - except Exception as ex: - QMessageBox.warning (None, "Error shutting down VM!", "ERROR: {0}".format(ex)) - return + self.shutdown_vm(vm) + + + def shutdown_vm(self, vm): + try: + subprocess.check_call (["/usr/sbin/xl", "shutdown", vm.name]) + except Exception as ex: + QMessageBox.warning (None, "Error shutting down VM!", "ERROR: {0}".format(ex)) + return + + trayIcon.showMessage ("Qubes Manager", "VM '{0}' is shutting down...".format(vm.name), msecs=3000) + + self.shutdown_monitor[vm.qid] = VmShutdownMonitor (vm) + QTimer.singleShot (vm_shutdown_timeout, self.shutdown_monitor[vm.qid].check_if_vm_has_shutdown) - trayIcon.showMessage ("Qubes Manager", "VM '{0}' is shutting down...".format(vm.name), msecs=3000) - self.shutdown_monitor[vm.qid] = VmShutdownMonitor (vm) - QTimer.singleShot (vm_shutdown_timeout, self.shutdown_monitor[vm.qid].check_if_vm_has_shutdown) @pyqtSlot(name='on_action_settings_triggered') def action_settings_triggered(self): @@ -1235,7 +1248,7 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): @pyqtSlot(name='on_action_backup_triggered') def action_backup_triggered(self): - backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager) + backup_window = BackupVMsWindow(app, self.qvm_collection, self.blk_manager, self.shutdown_vm) backup_window.exec_() @@ -1298,6 +1311,8 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): else: self.blk_menu.clear() self.blk_menu.setEnabled(True) + + self.blk_manager.blk_lock.acquire() if len(self.blk_manager.attached_devs) > 0 : for d in self.blk_manager.attached_devs: if self.blk_manager.attached_devs[d]['attached_to']['vm'] == vm.name: @@ -1313,6 +1328,8 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): action = self.blk_menu.addAction(QIcon(":/add.png"), str) action.setData(QVariant(d)) + self.blk_manager.blk_lock.release() + if self.blk_menu.isEmpty(): self.blk_menu.setEnabled(False) @@ -1322,10 +1339,13 @@ class VmManagerWindow(Ui_VmManagerWindow, QMainWindow): def attach_dettach_device_triggered(self, action): dev = str(action.data().toString()) vm = self.get_selected_vm() + + self.blk_manager.blk_lock.acquire() if dev in self.blk_manager.attached_devs: self.blk_manager.detach_device(vm, dev) else: self.blk_manager.attach_device(vm, dev) + self.blk_manager.blk_lock.release() class QubesBlockDevicesManager(): @@ -1338,43 +1358,75 @@ class QubesBlockDevicesManager(): self.current_attached = {} self.devs_changed = False + self.last_update_time = time.time() + self.blk_state_changed = True + self.msg = [] + self.check_counter = 0 + self.blk_lock = threading.Lock() + + self.update() + + def block_devs_event(self, xid): + now = time.time() + #don't update more often than 1/10 s + if now - self.last_update_time >= 0.1: + self.last_update_time = now + + self.blk_lock.acquire() + + self.blk_state_changed = True + + self.blk_lock.release() + + def check_for_updates(self): + self.blk_lock.acquire() + + ret = (self.blk_state_changed, self.msg) + + if self.blk_state_changed == True: + self.check_counter += 1 + + self.update() + ret = (self.blk_state_changed, self.msg) + + #let the update last for 3 manager-update cycles + if self.check_counter == 3: + self.check_counter = 0 + self.blk_state_changed = False + self.msg = [] + + self.blk_lock.release() + + return ret + + def update(self): blk = qubesutils.block_list() - msg = [] for b in blk: att = qubesutils.block_check_attached(None, blk[b]['device'], backend_xid = blk[b]['xid']) if b in self.current_blk: if blk[b] == self.current_blk[b]: if self.current_attached[b] != att: #devices the same, sth with attaching changed self.current_attached[b] = att - self.devs_changed = True else: #device changed ?! self.current_blk[b] = blk[b] self.current_attached[b] = att - self.devs_changed = True else: #new device self.current_blk[b] = blk[b] self.current_attached[b] = att - self.devs_changed = True - msg.append("Attached new device: {0}".format(blk[b]['device'])) + self.msg.append("Attached new device: {0}".format(blk[b]['device'])) to_delete = [] for b in self.current_blk: #remove devices that are not there anymore if b not in blk: to_delete.append(b) - self.devs_changed = True - msg.append("Detached device: {0}".format(self.current_blk[b]['device'])) + self.msg.append("Detached device: {0}".format(self.current_blk[b]['device'])) for d in to_delete: del self.current_blk[d] del self.current_attached[d] - if self.devs_changed == True: - self.devs_changed = False - self.__update_blk_entries__() - return True, msg - else: - return False, None + self.__update_blk_entries__() def __update_blk_entries__(self): @@ -1402,16 +1454,12 @@ class QubesBlockDevicesManager(): backend_vm = self.qvm_collection.get_vm_by_name(backend_vm_name) trayIcon.showMessage ("Qubes Manager", "{0} - attaching {1}".format(vm.name, dev), msecs=3000) qubesutils.block_attach(vm, backend_vm, dev_id) - self.devs_changed = True def detach_device(self, vm, dev_name): dev_id = self.attached_devs[dev_name]['attached_to']['devid'] vm_xid = self.attached_devs[dev_name]['attached_to']['xid'] trayIcon.showMessage ("Qubes Manager", "{0} - detaching {1}".format(vm.name, dev_name), msecs=3000) qubesutils.block_detach(None, dev_id, vm_xid) - self.devs_changed = True - - class QubesTrayIcon(QSystemTrayIcon): diff --git a/qubesmanager/multiselectwidget.py b/qubesmanager/multiselectwidget.py index a9de974..b373f43 100644 --- a/qubesmanager/multiselectwidget.py +++ b/qubesmanager/multiselectwidget.py @@ -6,6 +6,8 @@ from ui_multiselectwidget import * class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): __pyqtSignals__ = ("selected_changed()",) + __pyqtSignals__ = ("items_added(list)",) + __pyqtSignals__ = ("items_removed(list)",) def __init__(self, parent=None): super(MultiSelectWidget, self).__init__() @@ -19,13 +21,19 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): def switch_selected(self, src, dst): selected = src.selectedItems() + items = [] for s in selected: row = src.indexFromItem(s).row() item = src.takeItem(row) dst.addItem(item) + items.append(item) dst.sortItems() self.emit(SIGNAL("selected_changed()")) + if src is self.selected_list: + self.emit(SIGNAL("items_removed(list)"), items) + else: + self.emit(SIGNAL("items_added(list)"), items) def add_selected(self): self.switch_selected(self.available_list, self.selected_list) @@ -34,11 +42,18 @@ class MultiSelectWidget(Ui_MultiSelectWidget, QWidget): self.switch_selected(self.selected_list, self.available_list) def move_all(self, src, dst): + items = [] while src.count() > 0: item = src.takeItem(0) dst.addItem(item) + items.append(item) dst.sortItems() self.emit(SIGNAL("selected_changed()")) + if src is self.selected_list: + self.emit(SIGNAL("items_removed(list)"), items) + else: + self.emit(SIGNAL("items_added(list)"), items) + def add_all(self): self.move_all(self.available_list, self.selected_list) diff --git a/qubesmanager/settings.py b/qubesmanager/settings.py index 483a750..69bf3e8 100644 --- a/qubesmanager/settings.py +++ b/qubesmanager/settings.py @@ -358,7 +358,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.vcpus.setMaximum(QubesHost().no_cpus) self.vcpus.setValue(int(self.vm.vcpus)) - self.include_in_balancing.setChecked('meminfo-writer' in self.vm.services and self.vm.services['meminfo-writer']==True) + self.include_in_balancing.setChecked(self.vm.services['meminfo-writer']==True) #kernel if self.vm.template is not None: @@ -415,10 +415,8 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.vm.vcpus = self.vcpus.value() self.anything_changed = True - balancing_was_checked = ('meminfo-writer' in self.vm.services and self.vm.services['meminfo-writer']==True) - if self.include_in_balancing.isChecked() != balancing_was_checked: - self.new_srv_dict['meminfo-writer'] = self.include_in_balancing.isChecked() - self.anything_changed = True + #include_in_memory_balancing applied in services tab + #kernel changed if self.kernel.currentIndex() != self.kernel_idx: @@ -489,6 +487,12 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): self.anything_changed = True def include_in_balancing_state_changed(self, state): + for r in range (self.services_list.count()): + item = self.services_list.item(r) + if str(item.text()) == 'meminfo-writer': + item.setCheckState(state) + break; + if self.dev_list.selected_list.count() > 0: if state == QtCore.Qt.Checked: self.dmm_warning_adv.show() @@ -496,6 +500,7 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): else: self.dmm_warning_adv.hide() self.dmm_warning_dev.hide() + def devices_selection_changed(self): if self.include_in_balancing.isChecked(): if self.dev_list.selected_list.count() > 0 : @@ -515,28 +520,54 @@ class VMSettingsWindow(Ui_SettingsDialog, QDialog): else: item.setCheckState(QtCore.Qt.Unchecked) self.services_list.addItem(item) + + self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked) self.new_srv_dict = copy(self.vm.services) def __add_service__(self): srv = str(self.service_line_edit.text()).strip() - if srv != "" and srv not in self.new_srv_dict: - item = QListWidgetItem(srv) - item.setCheckState(QtCore.Qt.Checked) - self.services_list.addItem(item) - self.new_srv_dict[srv] = True + if srv != "": + if srv in self.new_srv_dict: + QMessageBox.information(None, "", "Service already on the list!") + else: + item = QListWidgetItem(srv) + item.setCheckState(QtCore.Qt.Checked) + self.services_list.addItem(item) + self.new_srv_dict[srv] = True def __remove_service__(self): - row = self.services_list.currentRow() - if row: + item = self.services_list.currentItem() + if str(item.text()) == 'meminfo-writer': + QMessageBox.information(None, "Service can not be removed", "Service meminfo-writer can not be removed from the list.") + else: + row = self.services_list.currentRow() item = self.services_list.takeItem(row) del self.new_srv_dict[str(item.text())] + + def services_item_clicked(self, item): + if str(item.text()) == 'meminfo-writer': + if item.checkState() == QtCore.Qt.Checked: + if not self.include_in_balancing.isChecked(): + self.include_in_balancing.setChecked(True) + elif item.checkState() == QtCore.Qt.Unchecked: + if self.include_in_balancing.isChecked(): + self.include_in_balancing.setChecked(False) + + def __apply_services_tab__(self): - new_dict = {} for r in range (self.services_list.count()): item = self.services_list.item(r) self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked) + balancing_was_checked = (self.vm.services['meminfo-writer']==True) + balancing_is_checked = self.include_in_balancing.isChecked() + meminfo_writer_checked = (self.new_srv_dict['meminfo-writer'] == True) + + if balancing_is_checked != meminfo_writer_checked: + if balancing_is_checked != balancing_was_checked: + self.new_srv_dict['meminfo-writer'] = balancing_is_checked + if self.new_srv_dict != self.vm.services: self.vm.services = self.new_srv_dict self.anything_changed = True diff --git a/resources.qrc b/resources.qrc index 662aaa4..639edfc 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,19 +1,26 @@ + icons/apps.png + icons/settings.png + icons/wall.png + icons/restore.png + icons/backup.png + icons/global-settings.png icons/on-icon/off.png - icons/on-icon/on.png + icons/on-icon/on.png + icons/on-icon/show-all-running.png icons/mount.png icons/pencil.png icons/redfirewall.png icons/edit.png icons/add.png icons/flag-blue.png + icons/running.png icons/flag-green.png icons/flag-red.png icons/flag-yellow.png icons/remove.png icons/appsprefs.png - icons/newfirewall.png icons/qubes.png icons/appvm.png icons/netvm.png @@ -31,6 +38,6 @@ icons/pausevm.png icons/showallvms.png icons/showcpuload.png - icons/firewall.png + diff --git a/rpm_spec/qmgr.spec b/rpm_spec/qmgr.spec index 0942ec3..660ea58 100644 --- a/rpm_spec/qmgr.spec +++ b/rpm_spec/qmgr.spec @@ -11,7 +11,7 @@ Group: Qubes Vendor: Invisible Things Lab License: GPL URL: http://fixme -Requires: python, PyQt4, qubes-core-dom0 >= 1.7.15, kdebase +Requires: python, PyQt4, qubes-core-dom0 > 1.7.17, kdebase Requires: pmount, cryptsetup BuildRequires: PyQt4-devel AutoReq: 0 diff --git a/settingsdlg.ui b/settingsdlg.ui index 4cb4d8a..c485c2a 100644 --- a/settingsdlg.ui +++ b/settingsdlg.ui @@ -29,7 +29,7 @@
- 1 + 4 @@ -586,6 +586,10 @@ + + + :/firewall.png:/firewall.png + Firewall rules @@ -746,6 +750,10 @@ true + + + :/storagevm.png:/storagevm.png + Devices @@ -776,7 +784,7 @@ - :/appsprefs.png:/appsprefs.png + :/apps.png:/apps.png Applications