diff --git a/bin/subiquity-tui b/bin/subiquity-tui index 9194fe21..67efc499 100755 --- a/bin/subiquity-tui +++ b/bin/subiquity-tui @@ -18,10 +18,11 @@ import argparse import sys import logging import signal -from subiquity.log import setup_logger +from subiquity.log import setup_logger, LOGFILE from subiquity import __version__ as VERSION from subiquity.core import Controller as Subiquity from subiquity.ui.frame import SubiquityUI +from subiquity.utils import environment_check def parse_options(argv): @@ -55,10 +56,15 @@ def main(): signal.signal(signal.SIGINT, control_c_handler) + env_ok = environment_check() + if env_ok is False: + print('Failed environment check. Check {} for errors.'.format(LOGFILE)) + return 1 + ui = SubiquityUI() subiquity_interface = Subiquity(ui, opts) subiquity_interface.run() if __name__ == '__main__': - main() + sys.exit(main()) diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py index de9c2700..3a529cae 100644 --- a/subiquity/controllers/filesystem.py +++ b/subiquity/controllers/filesystem.py @@ -24,6 +24,7 @@ from subiquity.ui.views import (DiskPartitionView, AddPartitionView, RaidView) import subiquity.utils as utils from subiquity.ui.dummy import DummyView +from subiquity.ui.error import ErrorView from subiquity.curtin import (curtin_write_storage_actions, curtin_write_preserved_actions) @@ -55,16 +56,37 @@ class FilesystemController(ControllerPolicy): self.ui.set_body(FilesystemView(self.model, self.signal)) + def filesystem_error(self, error_fname): + title = "Filesystem error" + footer = ("Error while installing Ubuntu") + error_msg = "Failed to obtain write permissions to /tmp" + self.ui.set_header(title) + self.ui.set_footer(footer, 30) + self.ui.set_body(ErrorView(self.signal, error_msg)) + def filesystem_handler(self, reset=False, actions=None): if actions is None and reset is False: self.signal.emit_signal('network:show') log.info("Rendering curtin config from user choices") - curtin_write_storage_actions(actions=actions) + try: + curtin_write_storage_actions(actions=actions) + except PermissionError: + log.exception('Failed to write storage actions') + self.signal.emit_signal('filesystem:error', + 'curtin_write_storage_actions') + return None log.info("Rendering preserved config for post install") preserved_actions = [preserve_action(a) for a in actions] - curtin_write_preserved_actions(actions=preserved_actions) + try: + curtin_write_preserved_actions(actions=preserved_actions) + except PermissionError: + log.exception('Failed to write preserved actions') + self.signal.emit_signal('filesystem:error', + 'curtin_write_preserved_actions') + return None + self.signal.emit_signal('identity:show') # Filesystem/Disk partition ----------------------------------------------- diff --git a/subiquity/controllers/installprogress.py b/subiquity/controllers/installprogress.py index 334f51d8..13c690eb 100644 --- a/subiquity/controllers/installprogress.py +++ b/subiquity/controllers/installprogress.py @@ -59,7 +59,13 @@ class InstallProgressController(ControllerPolicy): log.debug('Curtin Install: calling curtin with ' 'storage/net/postinstall config') - curtin_write_postinst_config(postconfig) + try: + curtin_write_postinst_config(postconfig) + except PermissionError: + log.exception('Failed to write curtin post-install config') + self.signal.emit_signal('filesystem:error', + 'curtin_write_postinst_config') + return None configs = [CURTIN_CONFIGS['network'], CURTIN_CONFIGS['storage'], diff --git a/subiquity/controllers/network.py b/subiquity/controllers/network.py index 4e4b03fa..8a10c4e2 100644 --- a/subiquity/controllers/network.py +++ b/subiquity/controllers/network.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging from subiquity.controller import ControllerPolicy from subiquity.models import NetworkModel from subiquity.ui.views import (NetworkView, @@ -23,6 +24,7 @@ from subiquity.ui.dummy import DummyView from subiquity.curtin import curtin_write_network_actions +log = logging.getLogger("subiquity.controller.network") class NetworkController(ControllerPolicy): def __init__(self, common): @@ -40,7 +42,14 @@ class NetworkController(ControllerPolicy): self.ui.set_body(NetworkView(self.model, self.signal)) def network_finish(self, actions): - curtin_write_network_actions(actions) + try: + curtin_write_network_actions(actions) + except (PermissionError, FileNotFoundError): + log.exception('Failed to obtain write permission') + self.signal.emit_signal('filesystem:error', + 'curtin_write_network_actions') + return None + self.signal.emit_signal('filesystem:show') def set_default_route(self): diff --git a/subiquity/log.py b/subiquity/log.py index e70d648e..780ba8fb 100644 --- a/subiquity/log.py +++ b/subiquity/log.py @@ -17,10 +17,10 @@ import logging import os from logging.handlers import TimedRotatingFileHandler +LOGDIR = "logs" +LOGFILE = os.path.join(LOGDIR, "debug.log") def setup_logger(name=__name__): - LOGDIR = "logs" - LOGFILE = os.path.join(LOGDIR, "debug.log") if not os.path.isdir(LOGDIR): os.makedirs(LOGDIR) log = TimedRotatingFileHandler(LOGFILE, diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py index 822dfbbb..52fa10c5 100644 --- a/subiquity/models/filesystem.py +++ b/subiquity/models/filesystem.py @@ -44,6 +44,9 @@ class FilesystemModel(ModelPolicy): ('Filesystem view', 'filesystem:show', 'filesystem'), + ('Filesystem error', + 'filesystem:error', + 'filesystem_error'), ('Filesystem finish', 'filesystem:finish', 'filesystem_handler'), diff --git a/subiquity/utils.py b/subiquity/utils.py index 2206224c..1b99eeca 100644 --- a/subiquity/utils.py +++ b/subiquity/utils.py @@ -18,11 +18,85 @@ import errno import logging import os import random +import yaml from subprocess import Popen, PIPE from subiquity.async import Async log = logging.getLogger("subiquity.utils") SYS_CLASS_NET = "/sys/class/net/" +ENVIRONMENT_CHECK = ''' +checks: + read: + file: + - /var/log/syslog + write: + directory: + - /tmp + mount: + directory: + - /proc + - /sys + exec: + file: + - /sbin/hdparm + - /usr/bin/curtin +''' + +def environment_check(check=ENVIRONMENT_CHECK): + ''' Check the environment to ensure subiquity can run without issues. + ''' + log.info('Checking environment for installer requirements...') + def is_file(x): + return os.path.isfile(x) + + def is_directory(x): + return os.path.isdir(x) + + def is_mount(x): + return os.path.ismount(x) + + def is_writable(x): + return os.access(x, os.W_OK) + + def is_readable(x): + return os.access(x, os.R_OK) + + def is_executable(x): + return os.access(x, os.X_OK) + + check_map = { + 'read': is_readable, + 'write': is_writable, + 'exec': is_executable, + 'file': is_file, + 'directory': is_directory, + 'mount': is_mount, + } + + checks = yaml.safe_load(check).get('checks', None) + if not checks: + log.error('Invalid environment check configuration') + return False + + env_ok = True + for check_type in [c for c in checks + if c in ['read', 'write', 'mount', 'exec']]: + for ftype, items in checks[check_type].items(): + for i in items: + if not os.path.exists(i): + log.error('FAIL: {} is not found on the filesystem'.format(i)) + env_ok = False + continue + if check_map[ftype](i) is False: + log.error('FAIL: {} is NOT of type: {}'.format(i, ftype)) + env_ok = False + continue + if check_map[check_type](i) is False: + log.error('FAIL: {} does NOT have required attr: {}'.format(i, + check_type)) + env_ok = False + + return env_ok def run_command_async(cmd, timeout=None):