2015-08-24 03:49:51 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
import crypt
|
|
|
|
import errno
|
|
|
|
import logging
|
2015-09-02 19:21:14 +00:00
|
|
|
import os
|
2015-09-21 21:40:26 +00:00
|
|
|
import random
|
2015-10-22 19:39:25 +00:00
|
|
|
import yaml
|
2018-05-18 01:00:43 +00:00
|
|
|
import subprocess
|
2015-08-24 03:49:51 +00:00
|
|
|
|
2016-06-30 18:17:01 +00:00
|
|
|
log = logging.getLogger("subiquitycore.utils")
|
2015-09-03 18:35:07 +00:00
|
|
|
SYS_CLASS_NET = "/sys/class/net/"
|
2015-10-22 19:39:25 +00:00
|
|
|
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2016-07-26 03:09:44 +00:00
|
|
|
def environment_check(check):
|
2015-10-22 19:39:25 +00:00
|
|
|
''' Check the environment to ensure subiquity can run without issues.
|
|
|
|
'''
|
|
|
|
log.info('Checking environment for installer requirements...')
|
2015-11-02 22:48:37 +00:00
|
|
|
|
2015-10-22 19:39:25 +00:00
|
|
|
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):
|
2017-03-03 20:38:47 +00:00
|
|
|
if 'SNAP' in os.environ:
|
|
|
|
log.warn("Adjusting path for snaps: {}".format(os.environ.get('SNAP')))
|
|
|
|
i = os.environ.get('SNAP') + i
|
|
|
|
if not os.path.exists(i):
|
|
|
|
env_ok = False
|
|
|
|
else:
|
|
|
|
env_ok = False
|
|
|
|
|
|
|
|
if not env_ok:
|
|
|
|
log.error('FAIL: {} is not found on the'
|
|
|
|
' filesystem'.format(i))
|
|
|
|
continue
|
2015-10-22 19:39:25 +00:00
|
|
|
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:
|
2015-11-02 22:48:37 +00:00
|
|
|
log.error('FAIL: {} does NOT have required attr:'
|
|
|
|
' {}'.format(i, check_type))
|
2015-10-22 19:39:25 +00:00
|
|
|
env_ok = False
|
|
|
|
|
|
|
|
return env_ok
|
2015-08-24 03:49:51 +00:00
|
|
|
|
|
|
|
|
2018-05-18 01:00:43 +00:00
|
|
|
def _clean_env(env):
|
|
|
|
if env is None:
|
|
|
|
env = os.environ.copy()
|
|
|
|
else:
|
|
|
|
env = env.copy()
|
|
|
|
# Do we always want to do this?
|
|
|
|
env['LC_ALL'] = 'C'
|
|
|
|
# Maaaybe want to remove SNAP here too.
|
|
|
|
return env
|
2015-09-02 19:21:14 +00:00
|
|
|
|
2018-05-18 01:00:43 +00:00
|
|
|
|
2018-05-18 01:38:13 +00:00
|
|
|
def run_command(cmd, *, input=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', errors='replace', env=None, **kw):
|
2018-05-18 01:00:43 +00:00
|
|
|
"""A wrapper around subprocess.run with logging and different defaults.
|
|
|
|
|
|
|
|
We never ever want a subprocess to inherit our file descriptors!
|
|
|
|
"""
|
|
|
|
if input is None:
|
|
|
|
kw['stdin'] = subprocess.DEVNULL
|
2018-05-18 01:21:38 +00:00
|
|
|
log.debug("run_command called: %s", cmd)
|
2015-09-02 19:21:14 +00:00
|
|
|
try:
|
2018-05-21 16:54:34 +00:00
|
|
|
cp = subprocess.run(cmd, input=input, stdout=stdout, stderr=stderr, env=_clean_env(env), **kw)
|
|
|
|
if encoding:
|
|
|
|
if isinstance(cp.stdout, bytes):
|
|
|
|
cp.stdout = cp.stdout.decode(encoding)
|
|
|
|
if isinstance(cp.stderr, bytes):
|
|
|
|
cp.stderr = cp.stderr.decode(encoding)
|
2018-05-18 01:00:43 +00:00
|
|
|
except subprocess.CalledProcessError as e:
|
2018-05-18 01:21:38 +00:00
|
|
|
log.debug("run_command %s", str(e))
|
2018-05-18 01:00:43 +00:00
|
|
|
raise
|
|
|
|
else:
|
2018-05-18 01:21:38 +00:00
|
|
|
log.debug("run_command %s exited with code %s", cp.args, cp.returncode)
|
2018-05-18 01:00:43 +00:00
|
|
|
return cp
|
|
|
|
|
|
|
|
|
2018-05-18 01:38:13 +00:00
|
|
|
def start_command(cmd, *, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', errors='replace', env=None, **kw):
|
2018-05-18 01:00:43 +00:00
|
|
|
"""A wrapper around subprocess.Popen with logging and different defaults.
|
|
|
|
|
|
|
|
We never ever want a subprocess to inherit our file descriptors!
|
2016-08-19 03:33:22 +00:00
|
|
|
"""
|
2018-05-18 01:21:38 +00:00
|
|
|
log.debug('start_command called: %s', cmd)
|
2018-05-21 16:54:34 +00:00
|
|
|
return subprocess.Popen(cmd, stdin=stdin, stdout=stdout, stderr=stderr, env=_clean_env(env), **kw)
|
2016-08-19 03:33:22 +00:00
|
|
|
|
|
|
|
|
2015-12-04 15:15:49 +00:00
|
|
|
# FIXME: replace with passlib and update package deps
|
2015-09-21 21:40:26 +00:00
|
|
|
def crypt_password(passwd, algo='SHA-512'):
|
|
|
|
# encryption algo - id pairs for crypt()
|
|
|
|
algos = {'SHA-512': '$6$', 'SHA-256': '$5$', 'MD5': '$1$', 'DES': ''}
|
|
|
|
if algo not in algos:
|
|
|
|
raise Exception('Invalid algo({}), must be one of: {}. '.format(
|
|
|
|
algo, ','.join(algos.keys())))
|
|
|
|
|
|
|
|
salt_set = ('abcdefghijklmnopqrstuvwxyz'
|
|
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
|
|
'0123456789./')
|
|
|
|
salt = 16 * ' '
|
|
|
|
salt = ''.join([random.choice(salt_set) for c in salt])
|
|
|
|
return crypt.crypt(passwd, algos[algo] + salt)
|
2015-09-28 14:45:54 +00:00
|
|
|
|
|
|
|
|
2017-01-25 22:18:03 +00:00
|
|
|
def disable_console_conf():
|
|
|
|
""" Stop console-conf service; which also restores getty service """
|
|
|
|
log.info('disabling console-conf service')
|
2016-09-01 10:33:00 +00:00
|
|
|
run_command(["systemctl", "stop", "--no-block", "console-conf@*.service", "serial-console-conf@*.service"])
|
2016-06-22 19:19:54 +00:00
|
|
|
return
|
2016-12-20 20:42:04 +00:00
|
|
|
|
|
|
|
def disable_subiquity():
|
|
|
|
""" Stop subiquity service; which also restores getty service """
|
|
|
|
log.info('disabling subiquity service')
|
2017-01-25 01:09:49 +00:00
|
|
|
run_command(["mkdir", "-p", "/run/subiquity"])
|
2016-12-23 03:12:03 +00:00
|
|
|
run_command(["touch", "/run/subiquity/complete"])
|
2018-02-13 02:38:55 +00:00
|
|
|
run_command(["systemctl", "start", "--no-block", "getty@tty1.service"])
|
|
|
|
run_command(["systemctl", "stop", "--no-block", "snap.subiquity.subiquity-service.service", "serial-subiquity@*.service"])
|
2016-12-20 20:42:04 +00:00
|
|
|
return
|