From a8df324b2daeaf2447fcce2dde05148f6196eb4e Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Mon, 8 Apr 2019 03:18:16 -0400 Subject: [PATCH 01/13] Fix typo --- qubesmanager/global_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index fdcee8a0..67632111 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -104,7 +104,7 @@ def __init_system_defaults__(self): ) def __apply_system_defaults__(self): - # upatevm + # updatevm if self.qvm_collection.updatevm != \ self.update_vm_vmlist[self.update_vm_combo.currentIndex()]: self.qvm_collection.updatevm = \ From fdd06d32a5de9d57cc8b7e952eef100722424194 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Mon, 8 Apr 2019 03:19:07 -0400 Subject: [PATCH 02/13] Add UI for managing Qubes update repositories Depends on QubesOS/qubes-core-admin-linux#48 Fixes QubesOS/qubes-issues#4550 --- qubesmanager/global_settings.py | 77 ++++++++++++++++++++++++++++++++- ui/globalsettingsdlg.ui | 59 ++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 2 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 67632111..a6f9c00a 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -24,6 +24,7 @@ import os import os.path import traceback +import subprocess from PyQt4 import QtCore, QtGui # pylint: disable=import-error from qubesadmin import Qubes @@ -233,8 +234,15 @@ def __apply_mem_defaults__(self): qmemman_config_file.writelines(config_lines) qmemman_config_file.close() - def __init_updates__(self): + def __run_qrexec_repo(self, service, arg=''): + # Fake up a "qrexec call" to dom0 because dom0 can't qrexec to itself yet + cmd = '/etc/qubes-rpc/' + service + p = subprocess.run(['sudo', cmd, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert not p.stderr + assert p.returncode == 0 + return p.stdout.decode('utf-8') + def __init_updates__(self): # TODO: remove workaround when it is no longer needed self.dom0_updates_file_path = '/var/lib/qubes/updates/disable-updates' @@ -251,6 +259,73 @@ def __init_updates__(self): self.enable_updates_all.clicked.connect(self.__enable_updates_all) self.disable_updates_all.clicked.connect(self.__disable_updates_all) + repos = dict() + for i in self.__run_qrexec_repo('qubes.repos.List').split('\n'): + l = i.split('\0') + # Keyed by repo name + d = repos[l[0]] = dict() + d['prettyname'] = l[1] + d['enabled'] = True if l[2] == 'enabled' else False + + if repos['qubes-dom0-unstable']['enabled']: + self.dom0_updates_repo.setCurrentIndex(3) + elif repos['qubes-dom0-current-testing']['enabled']: + self.dom0_updates_repo.setCurrentIndex(2) + elif repos['qubes-dom0-security-testing']['enabled']: + self.dom0_updates_repo.setCurrentIndex(1) + elif repos['qubes-dom0-current']['enabled']: + self.dom0_updates_repo.setCurrentIndex(0) + else: + raise Exception('Cannot detect enabled dom0 update repositories') + + if repos['qubes-templates-itl-testing']['enabled']: + self.itl_tmpl_updates_repo.setCurrentIndex(1) + elif repos['qubes-templates-itl']['enabled']: + self.itl_tmpl_updates_repo.setCurrentIndex(0) + else: + raise Exception('Cannot detect enabled ITL template update repositories') + + if repos['qubes-templates-community-testing']['enabled']: + self.comm_tmpl_updates_repo.setCurrentIndex(2) + elif repos['qubes-templates-community']['enabled']: + self.comm_tmpl_updates_repo.setCurrentIndex(1) + else: + self.comm_tmpl_updates_repo.setCurrentIndex(0) + + self.dom0_updates_repo.currentIndexChanged.connect(self.__handle_dom0_updates_combobox) + self.itl_tmpl_updates_repo.currentIndexChanged.connect(self.__handle_itl_tmpl_updates_combobox) + self.comm_tmpl_updates_repo.currentIndexChanged.connect(self.__handle_comm_tmpl_updates_combobox) + + def __manage_repos(self, l, action): + for i in l: + assert self.__run_qrexec_repo('qubes.repos.' + action, i) == 'ok\n' + + def __handle_dom0_updates_combobox(self, idx): + idx += 1 + l = ['qubes-dom0-current', 'qubes-dom0-security-testing', + 'qubes-dom0-current-testing', 'qubes-dom0-unstable'] + enable = l[:idx] + disable = l[idx:] + self.__manage_repos(enable, 'Enable') + self.__manage_repos(disable, 'Disable') + + def __handle_itl_tmpl_updates_combobox(self, idx): + idx += 1 + l = ['qubes-templates-itl', 'qubes-templates-itl-testing'] + enable = l[:idx] + disable = l[idx:] + self.__manage_repos(enable, 'Enable') + self.__manage_repos(disable, 'Disable') + + def __handle_comm_tmpl_updates_combobox(self, idx): + # We don't increment idx by 1 because this is the only combobox that + # has an explicit "disable this repository entirely" option + l = ['qubes-templates-community', 'qubes-templates-community-testing'] + enable = l[:idx] + disable = l[idx:] + self.__manage_repos(enable, 'Enable') + self.__manage_repos(disable, 'Disable') + def __enable_updates_all(self): reply = QtGui.QMessageBox.question( self, self.tr("Change state of all qubes"), diff --git a/ui/globalsettingsdlg.ui b/ui/globalsettingsdlg.ui index 0f72cdac..40de4118 100644 --- a/ui/globalsettingsdlg.ui +++ b/ui/globalsettingsdlg.ui @@ -98,7 +98,7 @@ - + Qt::Horizontal @@ -209,6 +209,30 @@ Updates + + + + + Stable updates + + + + + Testing updates (security only) + + + + + Testing updates + + + + + Unstable updates + + + + @@ -243,6 +267,39 @@ + + + + + ITL template updates + + + + + ITL template updates (testing) + + + + + + + + + (Community templates disabled) + + + + + Community template updates + + + + + Community template updates (testing) + + + + From df48c749c40f2b71d7e7a529dbdcbda4690d840d Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Mon, 8 Apr 2019 12:08:47 -0400 Subject: [PATCH 03/13] Squash some PyLint warnings --- qubesmanager/global_settings.py | 102 ++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index a6f9c00a..c0533b65 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -37,6 +37,51 @@ qmemman_config_path = '/etc/qubes/qmemman.conf' +def _run_qrexec_repo(service, arg=''): + # Fake up a "qrexec call" to dom0 because dom0 can't qrexec to itself yet + cmd = '/etc/qubes-rpc/' + service + p = subprocess.run( + ['sudo', cmd, arg], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + assert not p.stderr + assert p.returncode == 0 + return p.stdout.decode('utf-8') + +def _manage_repos(repolist, action): + for i in repolist: + assert _run_qrexec_repo('qubes.repos.' + action, i) == 'ok\n' + +def _handle_dom0_updates_combobox(idx): + idx += 1 + repolist = ['qubes-dom0-current', 'qubes-dom0-security-testing', + 'qubes-dom0-current-testing', 'qubes-dom0-unstable'] + enable = repolist[:idx] + disable = repolist[idx:] + _manage_repos(enable, 'Enable') + _manage_repos(disable, 'Disable') + +# pylint: disable=invalid-name +def _handle_itl_tmpl_updates_combobox(idx): + idx += 1 + repolist = ['qubes-templates-itl', 'qubes-templates-itl-testing'] + enable = repolist[:idx] + disable = repolist[idx:] + _manage_repos(enable, 'Enable') + _manage_repos(disable, 'Disable') + +# pylint: disable=invalid-name +def _handle_comm_tmpl_updates_combobox(idx): + # We don't increment idx by 1 because this is the only combobox that + # has an explicit "disable this repository entirely" option + repolist = ['qubes-templates-community', + 'qubes-templates-community-testing'] + enable = repolist[:idx] + disable = repolist[idx:] + _manage_repos(enable, 'Enable') + _manage_repos(disable, 'Disable') + # pylint: disable=too-many-instance-attributes class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog): @@ -234,14 +279,6 @@ def __apply_mem_defaults__(self): qmemman_config_file.writelines(config_lines) qmemman_config_file.close() - def __run_qrexec_repo(self, service, arg=''): - # Fake up a "qrexec call" to dom0 because dom0 can't qrexec to itself yet - cmd = '/etc/qubes-rpc/' + service - p = subprocess.run(['sudo', cmd, arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - assert not p.stderr - assert p.returncode == 0 - return p.stdout.decode('utf-8') - def __init_updates__(self): # TODO: remove workaround when it is no longer needed self.dom0_updates_file_path = '/var/lib/qubes/updates/disable-updates' @@ -260,12 +297,12 @@ def __init_updates__(self): self.disable_updates_all.clicked.connect(self.__disable_updates_all) repos = dict() - for i in self.__run_qrexec_repo('qubes.repos.List').split('\n'): + for i in _run_qrexec_repo('qubes.repos.List').split('\n'): l = i.split('\0') # Keyed by repo name d = repos[l[0]] = dict() d['prettyname'] = l[1] - d['enabled'] = True if l[2] == 'enabled' else False + d['enabled'] = l[2] == 'enabled' if repos['qubes-dom0-unstable']['enabled']: self.dom0_updates_repo.setCurrentIndex(3) @@ -283,7 +320,8 @@ def __init_updates__(self): elif repos['qubes-templates-itl']['enabled']: self.itl_tmpl_updates_repo.setCurrentIndex(0) else: - raise Exception('Cannot detect enabled ITL template update repositories') + raise Exception('Cannot detect enabled ITL template update ' + 'repositories') if repos['qubes-templates-community-testing']['enabled']: self.comm_tmpl_updates_repo.setCurrentIndex(2) @@ -292,39 +330,15 @@ def __init_updates__(self): else: self.comm_tmpl_updates_repo.setCurrentIndex(0) - self.dom0_updates_repo.currentIndexChanged.connect(self.__handle_dom0_updates_combobox) - self.itl_tmpl_updates_repo.currentIndexChanged.connect(self.__handle_itl_tmpl_updates_combobox) - self.comm_tmpl_updates_repo.currentIndexChanged.connect(self.__handle_comm_tmpl_updates_combobox) - - def __manage_repos(self, l, action): - for i in l: - assert self.__run_qrexec_repo('qubes.repos.' + action, i) == 'ok\n' - - def __handle_dom0_updates_combobox(self, idx): - idx += 1 - l = ['qubes-dom0-current', 'qubes-dom0-security-testing', - 'qubes-dom0-current-testing', 'qubes-dom0-unstable'] - enable = l[:idx] - disable = l[idx:] - self.__manage_repos(enable, 'Enable') - self.__manage_repos(disable, 'Disable') - - def __handle_itl_tmpl_updates_combobox(self, idx): - idx += 1 - l = ['qubes-templates-itl', 'qubes-templates-itl-testing'] - enable = l[:idx] - disable = l[idx:] - self.__manage_repos(enable, 'Enable') - self.__manage_repos(disable, 'Disable') - - def __handle_comm_tmpl_updates_combobox(self, idx): - # We don't increment idx by 1 because this is the only combobox that - # has an explicit "disable this repository entirely" option - l = ['qubes-templates-community', 'qubes-templates-community-testing'] - enable = l[:idx] - disable = l[idx:] - self.__manage_repos(enable, 'Enable') - self.__manage_repos(disable, 'Disable') + self.dom0_updates_repo.currentIndexChanged.connect( + _handle_dom0_updates_combobox + ) + self.itl_tmpl_updates_repo.currentIndexChanged.connect( + _handle_itl_tmpl_updates_combobox + ) + self.comm_tmpl_updates_repo.currentIndexChanged.connect( + _handle_comm_tmpl_updates_combobox + ) def __enable_updates_all(self): reply = QtGui.QMessageBox.question( From 047cfbc407c7da5e47a65472dbcec90d63161b56 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Mon, 8 Apr 2019 15:34:39 -0400 Subject: [PATCH 04/13] Remove unnecessary assert First of all, the method that was being called has asserts of its own, so it's guaranteed to either succeed or crash the program with AssertionError. Second, asserts are optimized out by the interpreter when -O is passed. Therefore, this code is buggy because it sometimes wouldn't be run, but we need the side effects. --- qubesmanager/global_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index c0533b65..42b2e92a 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -51,7 +51,7 @@ def _run_qrexec_repo(service, arg=''): def _manage_repos(repolist, action): for i in repolist: - assert _run_qrexec_repo('qubes.repos.' + action, i) == 'ok\n' + _run_qrexec_repo('qubes.repos.' + action, i) == 'ok\n' def _handle_dom0_updates_combobox(idx): idx += 1 From 8f9989a61da5933a99fcc51f1867bfe4b0512926 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 9 Apr 2019 03:57:03 -0400 Subject: [PATCH 05/13] Check that repo management succeeded --- qubesmanager/global_settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 42b2e92a..e11e0786 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -51,7 +51,8 @@ def _run_qrexec_repo(service, arg=''): def _manage_repos(repolist, action): for i in repolist: - _run_qrexec_repo('qubes.repos.' + action, i) == 'ok\n' + result = _run_qrexec_repo('qubes.repos.' + action, i) + assert result == 'ok\n' def _handle_dom0_updates_combobox(idx): idx += 1 From f3305b8288ad2962baa16314d23400dcdd442027 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Mon, 1 Jul 2019 18:04:28 -0700 Subject: [PATCH 06/13] Don't use asserts for error handling --- qubesmanager/global_settings.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index e11e0786..0a9e84cb 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -45,14 +45,27 @@ def _run_qrexec_repo(service, arg=''): stdout=subprocess.PIPE, stderr=subprocess.PIPE ) - assert not p.stderr - assert p.returncode == 0 + if p.stderr: + raise RuntimeError('qrexec call stderr was not empty', stderr=p.stderr) + if p.returncode != 0: + raise RuntimeError('qrexec call exited with non-zero return code', + returncode=p.returncode) return p.stdout.decode('utf-8') def _manage_repos(repolist, action): for i in repolist: - result = _run_qrexec_repo('qubes.repos.' + action, i) - assert result == 'ok\n' + try: + result = _run_qrexec_repo('qubes.repos.' + action, i) + if result != 'ok\n': + raise RuntimeError( + 'qrexec call stdout did not contain "ok" as expected', + result=result) + except RuntimeError as ex: + QtGui.QMessageBox.warning( + None, + self.tr("ERROR!"), + self.tr("Error managing repository settings: {e}".format( + e=str(ex)))) def _handle_dom0_updates_combobox(idx): idx += 1 From 026a844a91cf7debc0fb840732e51a69e18b59ef Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 2 Jul 2019 00:06:56 -0700 Subject: [PATCH 07/13] Only apply repo preferences when "OK" is clicked --- qubesmanager/global_settings.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 0a9e84cb..2c8a6214 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -344,16 +344,6 @@ def __init_updates__(self): else: self.comm_tmpl_updates_repo.setCurrentIndex(0) - self.dom0_updates_repo.currentIndexChanged.connect( - _handle_dom0_updates_combobox - ) - self.itl_tmpl_updates_repo.currentIndexChanged.connect( - _handle_itl_tmpl_updates_combobox - ) - self.comm_tmpl_updates_repo.currentIndexChanged.connect( - _handle_comm_tmpl_updates_combobox - ) - def __enable_updates_all(self): reply = QtGui.QMessageBox.question( self, self.tr("Change state of all qubes"), @@ -390,6 +380,14 @@ def __apply_updates__(self): if self.qvm_collection.check_updates_vm != self.updates_vm.isChecked(): self.qvm_collection.check_updates_vm = self.updates_vm.isChecked() + def __apply_repos__(self): + _handle_dom0_updates_combobox( + self.dom0_updates_repo.currentIndex()) + _handle_itl_tmpl_updates_combobox( + self.itl_tmpl_updates_repo.currentIndex()) + _handle_comm_tmpl_updates_combobox( + self.comm_tmpl_updates_repo.currentIndex()) + def reject(self): self.done(0) @@ -399,8 +397,7 @@ def save_and_apply(self): self.__apply_kernel_defaults__() self.__apply_mem_defaults__() self.__apply_updates__() - - + self.__apply_repos__() # Bases on the original code by: # Copyright (c) 2002-2007 Pascal Varet From 33ef4786269eb6278364630e895a487633cb272b Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 2 Jul 2019 12:46:51 -0700 Subject: [PATCH 08/13] Fix `self` being undefined when showing warnings --- qubesmanager/global_settings.py | 94 ++++++++++++++++----------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 2c8a6214..fedc5449 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -52,50 +52,6 @@ def _run_qrexec_repo(service, arg=''): returncode=p.returncode) return p.stdout.decode('utf-8') -def _manage_repos(repolist, action): - for i in repolist: - try: - result = _run_qrexec_repo('qubes.repos.' + action, i) - if result != 'ok\n': - raise RuntimeError( - 'qrexec call stdout did not contain "ok" as expected', - result=result) - except RuntimeError as ex: - QtGui.QMessageBox.warning( - None, - self.tr("ERROR!"), - self.tr("Error managing repository settings: {e}".format( - e=str(ex)))) - -def _handle_dom0_updates_combobox(idx): - idx += 1 - repolist = ['qubes-dom0-current', 'qubes-dom0-security-testing', - 'qubes-dom0-current-testing', 'qubes-dom0-unstable'] - enable = repolist[:idx] - disable = repolist[idx:] - _manage_repos(enable, 'Enable') - _manage_repos(disable, 'Disable') - -# pylint: disable=invalid-name -def _handle_itl_tmpl_updates_combobox(idx): - idx += 1 - repolist = ['qubes-templates-itl', 'qubes-templates-itl-testing'] - enable = repolist[:idx] - disable = repolist[idx:] - _manage_repos(enable, 'Enable') - _manage_repos(disable, 'Disable') - -# pylint: disable=invalid-name -def _handle_comm_tmpl_updates_combobox(idx): - # We don't increment idx by 1 because this is the only combobox that - # has an explicit "disable this repository entirely" option - repolist = ['qubes-templates-community', - 'qubes-templates-community-testing'] - enable = repolist[:idx] - disable = repolist[idx:] - _manage_repos(enable, 'Enable') - _manage_repos(disable, 'Disable') - # pylint: disable=too-many-instance-attributes class GlobalSettingsWindow(ui_globalsettingsdlg.Ui_GlobalSettings, QtGui.QDialog): @@ -380,12 +336,56 @@ def __apply_updates__(self): if self.qvm_collection.check_updates_vm != self.updates_vm.isChecked(): self.qvm_collection.check_updates_vm = self.updates_vm.isChecked() + def _manage_repos(self, repolist, action): + for i in repolist: + try: + result = _run_qrexec_repo('qubes.repos.' + action, i) + if result != 'ok\n': + raise RuntimeError( + 'qrexec call stdout did not contain "ok" as expected', + result=result) + except RuntimeError as ex: + QtGui.QMessageBox.warning( + None, + self.tr("ERROR!"), + self.tr("Error managing repository settings: {e}".format( + e=str(ex)))) + + def _handle_dom0_updates_combobox(self, idx): + idx += 1 + repolist = ['qubes-dom0-current', 'qubes-dom0-security-testing', + 'qubes-dom0-current-testing', 'qubes-dom0-unstable'] + enable = repolist[:idx] + disable = repolist[idx:] + self._manage_repos(enable, 'Enable') + self._manage_repos(disable, 'Disable') + + # pylint: disable=invalid-name + def _handle_itl_tmpl_updates_combobox(self, idx): + idx += 1 + repolist = ['qubes-templates-itl', 'qubes-templates-itl-testing'] + enable = repolist[:idx] + disable = repolist[idx:] + self._manage_repos(enable, 'Enable') + self._manage_repos(disable, 'Disable') + + # pylint: disable=invalid-name + def _handle_comm_tmpl_updates_combobox(self, idx): + # We don't increment idx by 1 because this is the only combobox that + # has an explicit "disable this repository entirely" option + repolist = ['qubes-templates-community', + 'qubes-templates-community-testing'] + enable = repolist[:idx] + disable = repolist[idx:] + self._manage_repos(enable, 'Enable') + self._manage_repos(disable, 'Disable') + def __apply_repos__(self): - _handle_dom0_updates_combobox( + self._handle_dom0_updates_combobox( self.dom0_updates_repo.currentIndex()) - _handle_itl_tmpl_updates_combobox( + self._handle_itl_tmpl_updates_combobox( self.itl_tmpl_updates_repo.currentIndex()) - _handle_comm_tmpl_updates_combobox( + self._handle_comm_tmpl_updates_combobox( self.comm_tmpl_updates_repo.currentIndex()) def reject(self): From 4931bf94010e756cbd6653c21076347b541a68ed Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 2 Jul 2019 13:37:29 -0700 Subject: [PATCH 09/13] Fix error handling Apparently Python exceptions don't take **kwargs, so we just pass a dictionary as the second (regular) argument. While we're at it, we pretty-print said dictionary when displaying error messages. --- qubesmanager/global_settings.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index fedc5449..32d8d07f 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -46,10 +46,11 @@ def _run_qrexec_repo(service, arg=''): stderr=subprocess.PIPE ) if p.stderr: - raise RuntimeError('qrexec call stderr was not empty', stderr=p.stderr) + raise RuntimeError('qrexec call stderr was not empty', + {'stderr': p.stderr}) if p.returncode != 0: raise RuntimeError('qrexec call exited with non-zero return code', - returncode=p.returncode) + {'returncode': p.returncode}) return p.stdout.decode('utf-8') # pylint: disable=too-many-instance-attributes @@ -343,13 +344,19 @@ def _manage_repos(self, repolist, action): if result != 'ok\n': raise RuntimeError( 'qrexec call stdout did not contain "ok" as expected', - result=result) + {'stdout': result}) except RuntimeError as ex: + msg = '{desc}; {args}'.format(desc=ex.args[0], args=', '.join( + # This is kind of hard to mentally parse but really all + # it does is pretty-print args[1], which is a dictionary + ['{key}: {val}'.format(key=i[0], val=i[1]) for i in + ex.args[1].items()] + )) QtGui.QMessageBox.warning( None, self.tr("ERROR!"), - self.tr("Error managing repository settings: {e}".format( - e=str(ex)))) + self.tr("Error managing repository settings: {msg}".format( + msg=msg))) def _handle_dom0_updates_combobox(self, idx): idx += 1 From 6a5823b74ac641459c300683291db09a69c61453 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 2 Jul 2019 13:45:21 -0700 Subject: [PATCH 10/13] Decode stderr in repo qrexec calls Before this change, "foobar\n" on stderr would be rendered (in the warning dialog) as "b'foobar\n'", which is ugly. Now it'll be rendered just as "foobar", followed by an actual newline character. --- qubesmanager/global_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 32d8d07f..5f79f9d5 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -47,7 +47,7 @@ def _run_qrexec_repo(service, arg=''): ) if p.stderr: raise RuntimeError('qrexec call stderr was not empty', - {'stderr': p.stderr}) + {'stderr': p.stderr.decode('utf-8')}) if p.returncode != 0: raise RuntimeError('qrexec call exited with non-zero return code', {'returncode': p.returncode}) From e0919beec1accfbe9a15df0c9139be0d74dbe408 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Tue, 2 Jul 2019 16:04:04 -0700 Subject: [PATCH 11/13] Squash more PyLint warnings This definitely worked before, and I haven't touched this code recently so I have no idea why these errors are just now popping up... but whatever. --- qubesmanager/global_settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 5f79f9d5..2fb8867c 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -269,11 +269,11 @@ def __init_updates__(self): repos = dict() for i in _run_qrexec_repo('qubes.repos.List').split('\n'): - l = i.split('\0') + lst = i.split('\0') # Keyed by repo name - d = repos[l[0]] = dict() - d['prettyname'] = l[1] - d['enabled'] = l[2] == 'enabled' + dct = repos[lst[0]] = dict() + dct['prettyname'] = lst[1] + dct['enabled'] = lst[2] == 'enabled' if repos['qubes-dom0-unstable']['enabled']: self.dom0_updates_repo.setCurrentIndex(3) From 352c0d03862f97efdcca5b289f6012c5d117f070 Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Wed, 3 Jul 2019 01:46:39 -0700 Subject: [PATCH 12/13] Only make qrexec calls when necessary This helps in situations where the qrexec calls are forbidden; it also prevents settings from being unintentionally changed and gives a noticeable performance boost when the "OK" button is clicked. --- qubesmanager/global_settings.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index 2fb8867c..ee7767ca 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -267,7 +267,7 @@ def __init_updates__(self): self.enable_updates_all.clicked.connect(self.__enable_updates_all) self.disable_updates_all.clicked.connect(self.__disable_updates_all) - repos = dict() + self.repos = repos = dict() for i in _run_qrexec_repo('qubes.repos.List').split('\n'): lst = i.split('\0') # Keyed by repo name @@ -338,9 +338,13 @@ def __apply_updates__(self): self.qvm_collection.check_updates_vm = self.updates_vm.isChecked() def _manage_repos(self, repolist, action): - for i in repolist: + for name in repolist: + if self.repos[name]['enabled'] and action == 'Enable' or \ + not self.repos[name]['enabled'] and action == 'Disable': + continue + try: - result = _run_qrexec_repo('qubes.repos.' + action, i) + result = _run_qrexec_repo('qubes.repos.' + action, name) if result != 'ok\n': raise RuntimeError( 'qrexec call stdout did not contain "ok" as expected', From 72456036286d3ae71bf5255a8370fdc40020d0de Mon Sep 17 00:00:00 2001 From: AJ Jordan Date: Wed, 3 Jul 2019 01:48:55 -0700 Subject: [PATCH 13/13] Say which repository caused the error in warnings --- qubesmanager/global_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qubesmanager/global_settings.py b/qubesmanager/global_settings.py index ee7767ca..f2b173c6 100644 --- a/qubesmanager/global_settings.py +++ b/qubesmanager/global_settings.py @@ -359,8 +359,8 @@ def _manage_repos(self, repolist, action): QtGui.QMessageBox.warning( None, self.tr("ERROR!"), - self.tr("Error managing repository settings: {msg}".format( - msg=msg))) + self.tr("Error managing {repo} repository settings:" + " {msg}".format(repo=name, msg=msg))) def _handle_dom0_updates_combobox(self, idx): idx += 1