diff options
author | James Meyer <james.meyer@operamail.com> | 2010-12-02 22:37:23 (GMT) |
---|---|---|
committer | James Meyer <james.meyer@operamail.com> | 2010-12-02 22:37:34 (GMT) |
commit | 8b94d7f39c71234712bead363526a0283efeb9fa (patch) | |
tree | 23f1dbd6458dc39a2c1b08bcdd4cbf768a60d84d /build_tools/larch8/larch0/cli | |
parent | 338af567e74d08cbd357079941208e494463d61e (diff) | |
download | linhes_dev-8b94d7f39c71234712bead363526a0283efeb9fa.zip |
larch8: first checkin, still needs some work
Diffstat (limited to 'build_tools/larch8/larch0/cli')
-rwxr-xr-x | build_tools/larch8/larch0/cli/archin.py | 380 | ||||
-rw-r--r-- | build_tools/larch8/larch0/cli/backend.py | 459 | ||||
-rw-r--r-- | build_tools/larch8/larch0/cli/config.py | 83 | ||||
-rwxr-xr-x | build_tools/larch8/larch0/cli/larchify.py | 606 | ||||
-rw-r--r-- | build_tools/larch8/larch0/cli/media_common.py | 432 | ||||
-rwxr-xr-x | build_tools/larch8/larch0/cli/medium.py | 277 | ||||
-rw-r--r-- | build_tools/larch8/larch0/cli/userinfo.py | 93 |
7 files changed, 2330 insertions, 0 deletions
diff --git a/build_tools/larch8/larch0/cli/archin.py b/build_tools/larch8/larch0/cli/archin.py new file mode 100755 index 0000000..0dc7306 --- /dev/null +++ b/build_tools/larch8/larch0/cli/archin.py @@ -0,0 +1,380 @@ +#!/usr/bin/env python2 +# +# archin.py +# +# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.10.04 + +# This is a command line script to perform an Arch Linux installation +# based on a list of packages. All needed parameters are passed as options. + +import os +from glob import glob +from backend import * + +class Installation: + def __init__(self, options): + self.options = options + self.installation_dir = get_installation_dir() + if self.installation_dir == '/': + errout(_("Operations on '/' are not supported ...")) + + self.profile_dir = get_profile() + self.pacman_cmd = self.make_pacman_command() + self.make_pacman_conf() + + + def make_pacman_command(self): + """Construct pacman command. Return the command, including options. + This includes options for installation path, cache directory and + for suppressing the progress bar. It assumes a temporary location + for pacman.conf, which must also be set up. + If there is no pacman executable in the system PATH, check that + it is available in the larch directory. + """ + pacman = runcmd('bash -c "which pacman || echo _FAIL_"')[1][-1].strip() + if pacman == '_FAIL_': + # If the host is not Arch, there will probably be no pacman + # (if there is some other program called 'pacman' that's + # a real spanner in the works). + # The alternative is to provide it in the larch directory. + pacman = base_dir + '/pacman' + if not os.path.isfile(pacman): + errout(_("No pacman executable found")) + + pacman += (' -r %s --config %s --noconfirm' + % (self.installation_dir, PACMAN_CONF)) + if self.options.noprogress: + pacman += ' --noprogressbar' + if self.options.cache: + pacman += ' --cachedir ' + self.options.cache + return pacman + + + def make_pacman_conf(self, final=False): + """Construct the pacman.conf file used by larch. + To make it a little easier to manage upstream changes to the default + pacman.conf, a separate file (pacman.conf.repos) is used to specify + the repositories to use. The contents of this file are used to modify + the basic pacman.conf file, which may be the default version or one + provided in the profile. + The 'final' parameter determines whether the version for the resulting + live system (True) or for the installation process (False) is + generated. If generating the installation version, it is possible + to specify alternative repositories, via the 'repofile' option, + which allows the pacman.conf used for the installation to be + different from the version in the resulting live system. + The return value is a list of the names of the repositories which + are included. + It is also possible to specify just a customized mirrorlist for the + installation by placing it in the working directory. + """ + # Allow use of '*platform*' in pacman.conf.repos + platform = os.uname()[4] + if platform != 'x86_64': + platform = 'i686' + + # Get pacman.conf header part + pc0 = self.profile_dir + '/pacman.conf.options' + if not os.path.isfile(pc0): + pc0 = base_dir + '/data/pacman.conf' + pacmanconf = self.pacmanoptions(readfile(pc0)) + + # Get file with repository entries + pc1 = self.profile_dir + '/pacman.conf.repos' + if not os.path.isfile(pc1): + pc1 = base_dir + '/data/pacman.conf.repos' + if self.options.repofile and not final: + pc1 = os.path.realpath(self.options.repofile) + + # Get repository path + if final: + default = 'Include = /etc/pacman.d/mirrorlist' + else: + mlist = cwd + '/mirrorlist' + if not os.path.isfile(mlist): + mlist = '/etc/pacman.d/mirrorlist' + if not os.path.isfile(mlist): + mlist = base_dir + '/data/mirrorlist' + default = 'Include = ' + mlist + + # Read repository entries + repos = [] + for line in readfile(pc1).splitlines(): + line = line.strip() + if (not line) or (line[0] == '#'): + continue + r, s = [t.strip() for t in line.split(':', 1)] + repos.append(r) + s = s.replace('*default*', default) + pacmanconf += ('\n[%s]\n%s\n' + % (r, s.replace('*platform*', platform))) + + + writefile(pacmanconf, self.installation_dir + '/etc/pacman.conf' + if final else PACMAN_CONF) + return repos + + + def install(self): + """Clear the chosen installation directory and install the base + set of packages, together with any additional ones listed in the + file 'addedpacks' (in the profile), removing the packages in + 'vetopacks' from the list. + """ + if not query_yn(_("Install Arch to '%s'?") % self.installation_dir): + return False + # Can't delete the whole directory because it might be a mount point + if os.path.isdir(self.installation_dir): + if script('cleardir %s' % self.installation_dir): + return False + + # Ensure installation directory exists and check that device nodes + # can be created (creating /dev/null is also a workaround for an + # Arch bug - which may have been fixed, but this does no harm) + if not (runcmd('bash -c "mkdir -p %s/{dev,proc,sys}"' + % self.installation_dir)[0] + and runcmd('mknod -m 666 %s/dev/null c 1 3' + % self.installation_dir)[0]): + errout(_("Couldn't write to the installation path (%s)") + % self.installation_dir) + if not runcmd('bash -c "echo test >%s/dev/null"' + % self.installation_dir)[0]: + errout(_("The installation path (%s) is mounted 'nodev'.") + % self.installation_dir) + + # I should also check that it is possible to run stuff in the + # installation directory. + runcmd('bash -c "cp $( which echo ) %s"' % self.installation_dir) + if not runcmd('%s/echo "yes"' % self.installation_dir)[0]: + errout(_("The installation path (%s) is mounted 'noexec'.") + % self.installation_dir) + runcmd('rm %s/echo' % self.installation_dir) + + # Fetch package database + runcmd('mkdir -p %s/var/lib/pacman' % self.installation_dir) + self.refresh() + + # Get list of vetoed packages. + self.packages = [] + self.veto_packages = [] + self.add_packsfile(self.profile_dir, 'vetopacks', must=False) + self.veto_packages = self.packages + + # Include 'required' packages (these can still be vetoed, but + # in some cases that will mean a larch system cannot be built) + self.packages = [] + self.add_packsfile(base_dir + '/data', 'requiredpacks') + + # Add additional packages and groups, from 'addedpacks' file. + self.add_packsfile(self.profile_dir, 'addedpacks') + + # Now do the actual installation. + ok = self.pacmancall('-S', ' '.join(self.packages)) + if not ok: + errout(_("Package installation failed")) + + # Some chroot scripts might need /etc/mtab + runcmd('bash -c ":> %s/etc/mtab"' % self.installation_dir) + + # Build the final version of pacman.conf + self.make_pacman_conf(True) + comment(" *** %s ***" % _("Arch installation completed")) + return True + + + def add_packsfile(self, dir, packs_file, must=True): + path = dir + '/' + packs_file + if must and not os.path.isfile(path): + errout(_("No '%s' file") % path) + fh = open(path) + for line in fh: + line = line.strip() + if line and (line[0] != '#'): + if line[0] == '*': + self.add_group(line[1:].split()[0]) + elif line[0] == '+': + # Include directive + line = line[1:].split()[0] + if line[0] != '/': + line = dir + '/' + line + d, pf = line.rsplit('/', 1) + if not d: + errout(_("Invalid package file include: %s")) + self.add_packsfile(d, pf) + elif line.startswith('!!!'): + # Ignore everything (!) entered previously. + # Allows requiredpacks to be overridden in addedpacks. + self.packages = [] + else: + line = line.split()[0] + if ((line not in self.packages) + and (line not in self.veto_packages)): + self.packages.append(line) + fh.close() + + + def add_group(self, gname): + """Add the packages belonging to a group to the installaion list, + removing any in the veto list. + """ + # In the next line the call could be done as a normal user. + for line in runcmd('%s -Sg %s' % (self.pacman_cmd, gname))[1]: + l = line.split() + if l and (l[0] == gname) and (l[1] not in self.veto_packages): + self.packages.append(l[1]) + + + def refresh(self): + """This updates or creates the pacman-db in the installation. + This is done using using 'pacman ... -Sy' together with the + customized pacman.conf file. + """ + if not runcmd(self.pacman_cmd + ' -Sy', + filter=pacman_filter_gen())[0]: + errout(_("Couldn't synchronize pacman database (pacman -Sy)")) + return True + + + def pacmancall(self, op, arg=''): + """Mount-bind the sys and proc directories before calling the + pacman command built by make_pacman_command to perform operation + 'op' (e.g. '-S') with argument(s) 'arg' (a string). + Then unmount sys and proc and return True if the command succeeded. + """ + # (a) Prepare the destination environment (bind mounts) + mount("/sys", "%s/sys" % self.installation_dir, "--bind") + mount("/proc", "%s/proc" % self.installation_dir, "--bind") + + # (b) Call pacman + ok = runcmd("%s %s %s" % (self.pacman_cmd, op, arg), + filter=pacman_filter_gen())[0] + + # (c) Remove bound mounts + unmount(("%s/sys" % self.installation_dir, + "%s/proc" % self.installation_dir)) + return ok + + + def pacmanoptions(self, text): + """A filter for pacman.conf to remove the repository info. + """ + texto = "" + block = "" + for line in text.splitlines(): + block += line + "\n" + if line.startswith("#["): + break + if line.startswith("[") and not line.startswith("[options]"): + break + if not line.strip(): + texto += block + block = "" + return texto + + + def sync(self, *packs): + return self.pacmancall('-S', ' '.join(packs)) + + + def update(self, *files): + return self.pacmancall('-U', ' '.join(files)) + + + def updateall(self, *files): + return self.pacmancall('-Su') + + + def remove(self, *packs): + return self.pacmancall('-Rs', ' '.join(packs)) + + + +if __name__ == "__main__": + cwd = os.getcwd() + + operations = 'install|sync|updateall|update|remove|refresh' + from optparse import OptionParser, OptionGroup + parser = OptionParser(usage=(_("usage: %%prog [options] %s [packages]") + % operations)) + + parser.add_option("-p", "--profile", action="store", type="string", + default="", dest="profile", + help=_("Profile: 'user:profile-name' or path to profile directory")) + parser.add_option("-i", "--installation-dir", action="store", type="string", + default="", dest="idir", + help=_("Path to directory to be larchified (default %s)") + % INSTALLATION) + parser.add_option("-s", "--slave", action="store_true", dest="slave", + default=False, help=_("Run as a slave from a controlling program" + " (e.g. from a gui)")) + parser.add_option("-q", "--quiet", action="store_true", dest="quiet", + default=False, help=_("Suppress output messages, except errors" + " (no effect if -s specified)")) + + + parser.add_option("-f", "--force", action="store_true", dest="force", + default=False, help=_("Don't ask for confirmation")) + + parser.add_option("-r", "--repofile", action="store", type="string", + default="", dest="repofile", + help=_("Supply a substitute repository list (pacman.conf.repos)" + " for the installation only")) + parser.add_option("-c", "--cache-dir", action="store", type="string", + default="", dest="cache", + help=_("pacman cache directory (default /var/cache/pacman/pkg)")) + parser.add_option("-n", "--noprogress", action="store_true", + dest="noprogress", + default=False, help=_("Don't show pacman's progress bar")) +## I think pacman is going to get support for something like '$arch', at +## which stage I could again consider architecture switching support in larch. +# parser.add_option("-a", "--arch", action="store", type="string", +# default="", dest="arch", +# help=_("processor architecture (x86_64|i686) - defaults to" +# " that of the host." +# " This is an untested feature, which is probably only partially" +# " implemented and may well not work.")) + + (options, args) = parser.parse_args() + if not args: + print _("You must specify which operation to perform:\n") + parser.print_help() + sys.exit(1) + + if os.getuid() != 0: + print _("This application must be run as root") + sys.exit(1) + + init('archin', options) + op = args[0] + if op not in operations.split('|'): + print (_("Invalid operation: '%s'\n") % op) + parser.print_help() + sys.exit(1) + + installation = Installation(options) + method = getattr(installation, op) + + if method(*args[1:]): + sys.exit(0) + else: + sys.exit(1) + diff --git a/build_tools/larch8/larch0/cli/backend.py b/build_tools/larch8/larch0/cli/backend.py new file mode 100644 index 0000000..510491c --- /dev/null +++ b/build_tools/larch8/larch0/cli/backend.py @@ -0,0 +1,459 @@ +# backend.py - for the cli modules: handles processes and io +# +# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.11.09 + +# There was also the vague idea of a web interface, using a sort of state- +# based approach. Connecting to a running larch process would then require +# the ability to get the logging history, but presumably not the whole +# history on every ui update, which would need to be incremental. +# The logging function would need to be modified to accommodate this. + +import os, sys, signal, atexit, __builtin__ +import traceback, pwd +from subprocess import Popen, PIPE, STDOUT +import pexpect +try: + import json as serialize +except: + import simplejson as serialize +from config import * + +def debug(text): + sys.stderr.write("DEBUG: " + text.strip() + "\n") + sys.stderr.flush() + +sys.path.append(os.path.dirname(base_dir)) +from liblarch.translation import i18n_module, lang +__builtin__._ = i18n_module(base_dir, 'larch') +__builtin__.lang = lang +# Run subprocesses without i18n in case the output is parsed. +os.environ["LANGUAGE"] = "C" + + +def init(app, options, app_quit=None): + global _options, _quit_function, _log, _controlled, _dontask, _quiet + _options = options + _quit_function = app_quit + _controlled = options.slave + _dontask = options.force + _log = None + _quiet = False if _controlled else options.quiet + + atexit.register(sys_quit) + if _controlled: + _out('>-_$$_%d' % os.getpid()) + + def sigint(num, frame): + """A handler for SIGINT. Tidy up properly and quit. + """ + errout("INTERRUPTED - killing subprocesses", 0) + if _sub_process and _sub_process.pid: + Popen(["pkill", "-g", str(_sub_process.pid)], + stdout=PIPE).communicate() + errout("QUITTING", 2) + signal.signal(signal.SIGINT, sigint) + + + # Check no other instance of the script is running + if os.path.isfile(LOCKFILE): + app0 = readfile(LOCKFILE) + if not query_yn(_( + "larch (%s) seems to be running already." + "\nIf you are absolutely sure this is not the case," + "\nyou may continue. Otherwise you should cancel." + "\n\nShall I continue?") % app0): + sys.exit(102) + writefile(app, LOCKFILE) + _log = open(LOGFILE + app, 'w') + + # For systems without /sbin and /usr/sbin in the normal PATH + p = os.environ['PATH'] + ps = p.split(':') + for px in ('/sbin', '/usr/sbin'): + if px not in ps: + p = px + ':' + p + os.environ['PATH'] = p + + +def _out(text, force=False): + """Send the string to standard output. + How it is output depends on the '-s' command line option (whether the + script is being run on the console or as a subprocess of another script). + In the latter case the text will be slightly encoded - to avoid newline + characters - and sent as a single unit. + Otherwise output the lines as they are, but all lines except + the first get a '--' prefix. + """ + lines = text.encode('utf-8').splitlines() + if _log and not text.startswith('>-'): + # Don't log the progress report lines + _log.write(lines[0] + '\n') + for l in lines[1:]: + _log.write('--' + l + '\n') + + if force or not _quiet: + if _controlled: + sys.stdout.write(serialize.dumps(text) + '\n') + else: + prefix = '' + for line in lines: + sys.stdout.write(prefix + line + '\n') + prefix = '--' + sys.stdout.flush() + + +def sys_quit(): + unmount() + if _quit_function: + _quit_function() + if _errorcount: + _out('!! ' + (_("The backend reported %d failed calls," + " you may want to investigate") % _errorcount)) + if _log: + _log.close() + os.remove(LOCKFILE) + + +def comment(text): + _out('##' + text) + + +def query_yn(message): + _out('?>' + message) + if _dontask: + result = True + + elif _controlled: + result = (raw_input().strip() == '??YES') + + else: + prompt = _("Yes:y|No:n") + py, pn = prompt.split('|') + respy = py.lower().split(':') + respn = pn.lower().split(':') + while True: + resp = raw_input(" [ %s ]: " % prompt).strip().lower() + if resp in respy: + result = True + break + if resp in respn: + result = False + break + + _out('#>%s' % ('Yes' if result else 'No')) + return result + + +def errout(message="ERROR", quit=1): + _out('!>' + message, True) + if quit: + sys_quit() + os._exit(quit) + + +def error0(message): + errout(message, 0) +__builtin__.error0 = error0 + + +# Catch all unhandled errors. +def errortrap(type, value, tb): + etext = "".join(traceback.format_exception(type, value, tb)) + errout(_("Something went wrong:\n") + etext, 100) +sys.excepthook = errortrap + + +_sub_process = None +_errorcount = 0 +def runcmd(cmd, filter=None): + global _sub_process, _errorcount + _out('>>' + cmd) + _sub_process = pexpect.spawn(cmd) + result = [] + line0 = '' + # A normal end-of-line is '\r\n', so split on '\r' but don't + # process a line until the next character is available. + while True: + try: + line0 += _sub_process.read_nonblocking(size=256, timeout=None) + except: + break + + while True: + lines = line0.split('\r', 1) + if (len(lines) > 1) and lines[1]: + line = lines[0] + line0 = lines[1] + nl = (line0[0] == '\n') + if nl: + # Strip the '\n' + line0 = line0[1:] + if filter: + nl, line = filter(line, nl) + if line == '/*/': + continue + if nl: + line = line.rstrip() + _out('>_' + line) + result.append(line) + else: + # Probably a progress line + if _controlled: + _out('>-' + line) + else: + sys.stdout.write(line + '\r') + sys.stdout.flush() + + else: + break + + _sub_process.close() + rc = _sub_process.exitstatus + ok = (rc == 0) + if not ok: + _errorcount += 1 + _out(('>?%s' % repr(rc)) + ('' if ok else (' $$$ %s $$$' % cmd))) + return (ok, result) + + +def script(cmd): + s = runcmd("%s/%s" % (script_dir, cmd)) + if s[0]: + return "" + else: + return "SCRIPT ERROR: (%s)\n" % cmd + "".join(s[1]) + + +def chroot(ip, cmd, mnts=[], filter=None): + if ip: + for m in mnts: + mdir = "%s/%s" % (ip, m) + if not os.path.isdir(mdir): + runcmd('mkdir -p %s' % mdir) + mount("/" + m, mdir, "--bind") + cmd = "chroot %s %s" % (ip, cmd) + + s = runcmd(cmd, filter) + + if ip: + unmount(["%s/%s" % (ip, m) for m in mnts]) + + if s[0]: + if s[1]: + return s[1] + else: + return True + return False + + +_mounts = [] +def mount(src, dst, opts=""): + if runcmd("mount %s %s %s" % (opts, src, dst))[0]: + _mounts.append(dst) + return True + return False + + +def unmount(dst=None): + if dst == None: + mnts = list(_mounts) + elif type(dst) in (list, tuple): + mnts = list(dst) + else: + mnts = [dst] + + r = True + for m in mnts: + if runcmd("umount %s" % m)[0]: + _mounts.remove(m) + else: + r = False + return r + + +def get_installation_dir(): + return os.path.realpath(_options.idir if _options.idir + else INSTALLATION) + + +def get_profile(): + """Get the absolute path to the profile folder given its path in any + acceptable form, including 'user:profile-name' + """ + pd = (_options.profile if _options.profile + else base_dir + '/profiles/default') + p = pd.split(':') + if len(p) == 1: + pd = os.path.realpath(pd) + else: + try: + pd = (pwd.getpwnam(p[0])[5] + PROFILE_DIR + + '/' + p[1]) + except: + errout(_("Invalid profile: %s") % pd, quit=0) + raise + if not os.path.isfile(pd + '/addedpacks'): + errout(_("Invalid profile folder: %s") % pd) + return pd + + + +#+++++++++++++++++++++++++++++++++++++++++ +#Regular expression search strings for progress reports +import re +#lit: give []() a \-prefix +#grp: surround string in () +#opt: surround string in [] + +def _lit(s): + for c in r'[()]': + s = s.replace(c, '\\' + c) + return s + +def _grp(s, x=''): + return '(' + s + ')' + x + +def _grp0(s, x=''): + return '(?:' + s + ')' + x + +def _opt(s, x=''): + return '[' + s + ']' + x + +re_psub = re.compile(r'\[[#-]+\]') +_re_pacman = re.compile( _grp0(_lit('(') + + _grp(_opt('^/', '+') + '/' + _opt('^)', '+')) + + _lit(')'), '?') + + _grp('.*?') + + _lit('[') + _grp(_opt('-#', '+')) + _lit(r']\s+') + + _grp(_opt('0-9', '+')) + + '%' + ) + +_re_mksquashfs = re.compile(_lit('[.*]') + + _grp('.* ' + + _grp(_opt('0-9', '+')) + + '%') + ) + +_re_mkisofs = re.compile(_opt(' 1') + _opt(' \d') + '\d\.\d\d%') + +#----------------------------------------- +class pacman_filter_gen: + """Return a function to detect and process the progress output of + pacman. + """ + def __init__(self): + self.progress = '' + + def __call__(self, line, nl): + ms = _re_pacman.match(line) + if ms: + p = ms.group(3) + if (self.progress != p) or nl: + self.progress = p + xfromy = ms.group(1) + if _controlled: + if not xfromy: + xfromy = '' + line = 'pacman:%s|%s|%s%%' % (xfromy, ms.group(2), + ms.group(4)) + elif ms.group(4) == '100': + line = re_psub.sub('[##########]', line) + if nl: + sys.stdout.write(' '*80 + '\r') + else: + line = '/*/' + return (nl, line) + + +class mksquashfs_filter_gen: + """Return a function to detect and process the progress output of + mksquashfs. + """ + def __init__(self): + self.progress = '' + + def __call__(self, line, nl): + ms = _re_mksquashfs.match(line) + if ms: + percent = ms.group(2) + if (self.progress != percent) or nl: + self.progress = percent + if _controlled: + line = 'mksquashfs:' + ms.group(1) + else: + line = re.sub(r'=[-\\/|]', '= ', line) + else: + line = '/*/' + return (nl, line) + + +class mkisofs_filter_gen: + """Return a function to detect and process the progress output of + mkisofs. + """ + def __init__(self): + self.running = None + + def __call__(self, line, nl): + ms = _re_mkisofs.match(line) + if ms: + if _controlled: + line = 'mkisofs:' + line + self.running = line + nl = False + elif self.running: + line = self.running + '\n' + line + self.running = None + return (nl, line) + + +def readdata(filename): + return readfile(base_dir + '/data/' + filename) + + +def readfile(fpath): + try: + fh = open(fpath) + text = fh.read() + fh.close() + except: + errout(_("Couldn't read file: %s") % fpath) + return None + return text + + +def writefile(text, path): + try: + pd = os.path.dirname(path) + if not os.path.isdir(pd): + os.makedirs(pd) + fh = None + fh = open(path, 'w') + fh.write(text) + return True + except: + return False + finally: + if fh: + fh.close() + diff --git a/build_tools/larch8/larch0/cli/config.py b/build_tools/larch8/larch0/cli/config.py new file mode 100644 index 0000000..b7f0c37 --- /dev/null +++ b/build_tools/larch8/larch0/cli/config.py @@ -0,0 +1,83 @@ +# config.py +# +# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.10.23 + +# Basic (default) configuration information for larch +# Some of these can be changed ...???? + +# Default volume label +LABEL = 'LARCH-8' +# Default iso file +ISOFILE = 'larch8.iso' +# Default volume label for boot iso +BOOTISOLABEL = 'LARCH-8-BOOT' +# Default boot iso file +BOOTISO = 'larch8boot.iso' +# Used as a mount point for destination medium +MPD = '/tmp/larch_mnt' +# Used as a mount point for source medium +MPS = '/tmp/larch_source_mnt' +# Mount point (within chroot) for bind-mounting iso build directory +ISOBUILDMNT = '/tmp/isodir_mnt' +# Mount point for medium sources +SOURCEMOUNT = '/tmp/larch_mntsrc' +# Temporary directory for building medium boot directory +BUILD0 = '/tmp/larch_build' + +SYSLINUXDIR = '/usr/lib/syslinux' + +# A customized pacman.conf file is used for the installation, generated +# dynamically according to the options and the profile. +PACMAN_CONF = '/tmp/larch_pacman.conf' + +# Medium detection alternatives +detection_methods = 'label|uuid|device|search' +# Directories to ignore when squashing mods.sqf +IGNOREDIRS = 'boot dev larch media proc sys tmp .*' +# Valid file-system types for extlinux +OKFS = ('ext2', 'ext3', 'ext4', 'btrfs') + +# Some basic paths +import os, sys +module_dir = os.path.dirname(os.path.realpath(__file__)) +base_dir = os.path.dirname(module_dir) +script_dir = base_dir + '/buildscripts' + +# File to prevent two instances of larch from running +LOCKFILE = '/tmp/larch_lock' +# File (stem) for log +LOGFILE = '/tmp/larch_log_' +# The path to the Arch installation which is to be larchified +INSTALLATION = '/home/larchbuild' + +# These paths are intended for use in 'chroot installation_dir', etc. +# The base directory of the larchified stuff +CHROOT_DIR_BUILD = '/.larch' +# This is the base of all stuff to be cleared on a rerun of larchify +CHROOT_DIR_LARCHIFY = CHROOT_DIR_BUILD + '/larchify' +# The base directory of the medium building area +CHROOT_DIR_MEDIUM = CHROOT_DIR_LARCHIFY + '/medium' +# Area for building the (mods) overlay +CHROOT_DIR_OVERLAY = CHROOT_DIR_LARCHIFY + '/overlay' +# Location for saving the system.sqf (outside of the larchify area) +CHROOT_SYSTEMSQF = CHROOT_DIR_BUILD + '/system.sqf' + diff --git a/build_tools/larch8/larch0/cli/larchify.py b/build_tools/larch8/larch0/cli/larchify.py new file mode 100755 index 0000000..7e7c646 --- /dev/null +++ b/build_tools/larch8/larch0/cli/larchify.py @@ -0,0 +1,606 @@ +#!/usr/bin/env python2 +# +# larchify.py +# +# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.11.28 + +# This is a command line script to prepare a larch live system from an +# Arch Linux installation. All needed parameters are passed as options. + +import os, sys +from backend import * +from userinfo import Userinfo +from glob import glob +import random, crypt +from subprocess import Popen, PIPE, STDOUT + + +class Builder: + """This class manages 'larchifying' an Arch Linux installation. + """ + def __init__(self, options): + self.installation_dir = get_installation_dir() + self.installation0 = (self.installation_dir + if self.installation_dir != "/" else "") + testfile = self.installation0 + '/etc/pacman.conf' + if not os.path.isfile(testfile): + errout(_("File '%s' doesn't exist:\n" + " '%s' not an Arch installation?") + % (testfile, self.installation_dir)) + + self.profile_dir = get_profile() + + + def build(self, options): + if not (self.installation0 or query_yn(_( + "Building a larch live medium from the running system is" + "\nan error prone process. Changes to the running system" + "\nmade while running this function may be only partially" + "\nincorporated into the compressed system images." + "\n\nDo you wish to continue?"))): + return False + + # Define the working area - it must be inside the installation + # because of the use of chroot for some functions + self.larchify_dir = self.installation0 + CHROOT_DIR_LARCHIFY + # Location for the live medium image + self.medium_dir = self.installation0 + CHROOT_DIR_MEDIUM + # And potentially a saved system.sqf + self.system_sqf = self.installation0 + CHROOT_SYSTEMSQF + # Needed for a potentially saved locales directory + self.locales_base = self.installation0 + CHROOT_DIR_BUILD + # For building the (mods.sqf) overlay + self.overlay = self.installation0 + CHROOT_DIR_OVERLAY + comment("Initializing larchify process") + + if options.oldsqf: + if os.path.isfile(self.medium_dir + "/larch/system.sqf"): + runcmd("mv %s/larch/system.sqf %s" % + (self.medium_dir, self.system_sqf)) + else: + runcmd("rm -f %s" % self.system_sqf) + + # Clean out larchify area and create overlay directory + runcmd('rm -rf %s' % self.larchify_dir) + runcmd('mkdir -p %s' % self.overlay) + + if not self.find_kernel(): + return False + + if not self.system_check(): + return False + + comment("Beginning to build larch medium files") + # Clear out the directory + runcmd('rm -rf %s' % self.medium_dir) + # The medium's boot directory + runcmd('mkdir -p %s/boot' % self.medium_dir) + # The main larch direcory + runcmd('mkdir -p %s/larch' % self.medium_dir) + + # Copy kernel to medium boot directory + runcmd('cp -f %s/boot/%s %s/boot' % + (self.installation0, self.kname, self.medium_dir)) + # Remember file name + writefile(self.kname, self.medium_dir + '/boot/kernelname') + + # if no saved system.sqf, squash the Arch installation at self.installation_dir + if not os.path.isfile(self.system_sqf): + comment("Generating system.sqf") + # root directories which are not included in the squashed system.sqf + ignoredirs = IGNOREDIRS + " mnt " + # .larch and .livesys should be excluded by the '.*' entry in IGNOREDIRS + # /var stuff + ignoredirs += " var/log var/tmp var/lock var/cache/pacman/pkg var/lib/hwclock" + # others + ignoredirs += " usr/lib/locale" + + # Additional directories to ignore can also be specified in the + # profile. This is a nasty option. It was requested, and might + # be useful under certain special circumstances, but I recommend + # not using it unless you are really sure what you are doing. + veto_file = self.profile_dir + '/vetodirs' + if os.path.isfile(veto_file): + fh = open(veto_file) + for line in fh: + line = line.strip() + if line and (line[0] != '#'): + ignoredirs += ' ' + line.lstrip('/') + fh.close() + + if not chroot(self.installation0, + "/sbin/mksquashfs '/' '%s' -wildcards -e %s" + % (CHROOT_SYSTEMSQF, ignoredirs), + filter=mksquashfs_filter_gen()): + errout(_("Squashing system.sqf failed")) + # remove execute attrib + runcmd("chmod oga-x %s" % self.system_sqf) + + # move system.sqf to medium's larch directory + runcmd("mv %s %s/larch" % (self.system_sqf, self.medium_dir)) + + # prepare overlay + comment("Generating larch overlay") + # Copy over the overlay from the selected profile + if os.path.isdir("%s/rootoverlay" % self.profile_dir): + runcmd('bash -c "cp -rf %s/rootoverlay/* %s"' + % (self.profile_dir, self.overlay)) + # Ensure there is an /etc directory in the overlay + runcmd("mkdir -p %s/etc" % self.overlay) + # Check there is no /boot + if os.path.exists(self.overlay + '/boot'): + error0(_("Warning: /boot in the profile's overlay will not be used")) +# # Fix access permission on /root +# if os.path.isdir("%s/root" % self.overlay): +# runcmd("chmod 0750 %s/root" % self.overlay) + # Fix sudoers if any + if os.path.isfile("%s/etc/sudoers" % self.overlay): + runcmd("chmod 0440 %s/etc/sudoers" % self.overlay) + runcmd("chown root:root %s/etc/sudoers" % self.overlay) + + # Prepare inittab + inittab = self.overlay + "/etc/inittab" + itsave = inittab + ".larchsave" + it0 = self.installation0 + "/etc/inittab" + itl = self.overlay + "/etc/inittab.larch" + if not os.path.isfile(itl): + itl = self.installation0 + "/etc/inittab.larch" + if not os.path.isfile(itl): + itl = None + # Save the original inittab if there is an inittab.larch file, + # ... if there isn't already a saved one + if itl: + if ((not os.path.isfile(it0 + ".larchsave")) + and (not os.path.isfile(itsave))): + runcmd("cp %s %s" % (it0, itsave)) + # Use the .larch version in the live system + runcmd("cp -f %s %s" % (itl, inittab)) + + comment("Generating larch initcpio") + if not self.gen_initramfs(): + return False + + lpath = self.locales_base + '/locale' + if self.installation0: + if options.oldlocales and os.path.isdir(lpath): + comment("Copying saved glibc locales") + runcmd('rm -rf %s/usr/lib/locale' % self.overlay) + runcmd('mkdir -p %s/usr/lib' % self.overlay) + runcmd('cp -a %s %s/usr/lib' % (lpath, self.overlay)) + else: + comment("Generating glibc locales") + runcmd('rm -rf %s' % lpath) + script('larch-locales "%s" "%s"' % (self.installation0, + self.overlay)) + # Save the generated locales for possible reuse + runcmd('cp -a %s/usr/lib/locale %s' % (self.overlay, + self.locales_base)) + + if (os.path.isfile(self.installation0 + '/usr/bin/ssh-keygen') + and not os.path.isfile(self.profile_dir + '/nosshkeys')): + # ssh initialisation - done here so that it doesn't need to + # be done when the live system boots + comment("Generating ssh keys to overlay") + sshdir = CHROOT_DIR_OVERLAY + "/etc/ssh" + runcmd("mkdir -p %s" % (self.installation0 + sshdir)) + for k, f in [("rsa1", "ssh_host_key"), ("rsa", "ssh_host_rsa_key"), + ("dsa", "ssh_host_dsa_key")]: + chroot(self.installation0, + "ssh-keygen -t %s -N '' -f %s/%s" + % (k, sshdir, f), ["dev"]) + + # Ensure the hostname is in /etc/hosts + script("larch-hosts %s %s" % (self.installation0, self.overlay)) + + # Handle /mnt (this is done like this in case something is mounted + # within /mnt - only the mount points should be included in the live system). + runcmd("mkdir -p %s/mnt" % self.overlay) + for d in os.listdir("%s/mnt" % self.installation0): + if os.path.isdir("%s/mnt/%s" % (self.installation0, d)): + runcmd("mkdir %s/mnt/%s" % (self.overlay, d)) + + # Add an empty /var/lib/hwclock directory + runcmd("mkdir -p %s/var/lib/hwclock" % self.overlay) + + # Run customization script + tweak = self.profile_dir + '/build-tweak' + if os.path.isfile(tweak): + comment("(WARNING): Running user's build customization script") + if runcmd(tweak + ' %s %s' % (self.installation0, + self.overlay))[0]: + comment("Customization script completed") + else: + errout(_("Build customization script failed")) + + # Get root password + rootpwf = self.profile_dir + '/rootpw' + if os.path.isfile(rootpwf): + rootpw = readfile(rootpwf).strip() + if rootpw == '!': + # Lock the password + rootcmd = 'usermod -L' + else: + rootcmd = "usermod -p '%s'" % encryptPW(rootpw) + else: + rootcmd = None + + # Add users and set root password + if self.installation0 and not self.add_users(rootcmd): + return False + + comment("Squashing mods.sqf") + if not chroot(self.installation0, + "/sbin/mksquashfs '%s' '%s/larch/mods.sqf' -wildcards -e %s" + % (CHROOT_DIR_OVERLAY, CHROOT_DIR_MEDIUM, IGNOREDIRS), + filter=mksquashfs_filter_gen()): + errout(_("Squashing mods.sqf failed")) + # remove execute attrib + runcmd("chmod oga-x %s/larch/mods.sqf" % self.medium_dir) + + runcmd("rm -rf %s" % self.overlay) + + comment(" *** %s ***" % _("larchify-process completed")) + return True + + + def add_users(self, rootcmd): + userinfo = Userinfo(self.profile_dir) + userlist = [] + for user in userinfo.allusers(): + # Only include if the user does not yet exist + if runcmd('bash -c "grep \"^%s\" %s/etc/passwd || echo -"' + % (user, self.installation_dir))[1][0] != '-': + comment("(WARNING): User '%s' exists already" % user) + else: + userlist.append(user) + + # Only continue if there are new users in the list + if rootcmd: + clist = [('root', rootcmd + ' %s')] + else: + if userlist == []: + return True + clist = [] + + # Save system files and replace them by the overlay versions + savedir = self.larchify_dir + '/save_etc' + runcmd('rm -rf %s' % savedir) + runcmd('mkdir -p %s/default' % savedir) + savelist = 'group,gshadow,passwd,shadow,login.defs,skel' + runcmd('bash -c "cp -a %s/etc/{%s} %s"' + % (self.installation0, savelist, savedir)) + runcmd('cp -a %s/etc/default/useradd %s/default' + % (self.installation0, savedir)) + for f in ('group', 'gshadow', 'passwd', 'shadow', 'login.defs'): + if os.path.isfile(self.overlay + '/etc/%s'): + runcmd('cp %s/etc/%s %s/etc' + % (self.overlay, f, self.installation0)) + if os.path.isfile(self.overlay + '/etc/default/useradd'): + runcmd('cp %s/etc/default/useradd %s/etc/default' + % (self.overlay, self.installation0)) + if os.path.isdir(self.overlay + '/etc/skel'): + runcmd('cp -r %s/etc/skel %s/etc' + % (self.overlay, self.installation0)) + + # Build the useradd command + userdir0 = '/users' + userdir = self.larchify_dir + userdir0 + userdirs = [] + runcmd('mkdir -p %s/home' % self.overlay) + for u in userlist: + cline = 'useradd -m' + pgroup = userinfo.get(u, 'maingroup') + if pgroup: + cline += ' -g ' + pgroup + uid = userinfo.get(u, 'uid') + if uid: + cline += ' -u ' + uid + pw = userinfo.get(u, 'pw') + if (pw == ''): + # Passwordless login + pwcrypt = '' + else: + # Normal MD5 password + pwcrypt = encryptPW(pw) + cline += " -p '%s'" % pwcrypt + skeldir = userinfo.get(u, 'skel') + if skeldir: + # Custom home initialization directories in the profile + # always start with 'skel_' + skel = 'skel_' + skeldir + if skel not in userdirs: + userdirs.append(skel) + cline += ' -k %s/%s' % (CHROOT_DIR_LARCHIFY + userdir0, + skel) + # Allow for expert tweaking + cline += ' ' + userinfo.get(u, 'expert') + # The user and the command to be run + clist.append((u, cline + ' %s')) + xgroups = userinfo.get(u, 'xgroups') + if xgroups: + xgl = [] + for g in xgroups.split(','): + clist.append((u, 'usermod -a -G %s %%s' % g)) + + if userdirs: + # Copy custom 'skel' directories to build space + runcmd('rm -rf %s' % userdir) + runcmd('mkdir -p %s' % userdir) + for ud in userdirs: + runcmd('cp -r %s/%s %s/%s' % + (self.profile_dir, ud, userdir, ud)) + + nfail = 0 + ok = True + for u, cmd in clist: + if not chroot(self.installation0, cmd % u): + nfail += 1 + # Errors adding users to groups are not fatal: + if not cmd.startswith('usermod -a -G'): + ok = False + if os.path.isdir('%s/home/%s' % (self.installation0, u)): + runcmd('mv %s/home/%s %s/home' + % (self.installation0, u, self.overlay)) + + if nfail > 0: + errout(_("%d user account operation(s) failed") % nfail, 0) + # Move changed /etc/{group,gshadow,passwd,shadow} to overlay + runcmd('bash -c "mv %s/etc/{group,gshadow,passwd,shadow} %s/etc"' + % (self.installation0, self.overlay)) + # Restore system files in base installation + runcmd('rm -rf %s/etc/skel' % self.installation0) + runcmd('bash -c "cp -a %s/* %s/etc"' + % (savedir, self.installation0)) + return ok + + + def system_check(self): + comment("Testing for necessary packages and kernel modules") + fail = "" + warn = "" + nplist = ["larch-live"] + + mdep = (self.installation0 + + "/lib/modules/%s/modules.dep" % self.kversion) + if Popen(["grep", "/squashfs.ko", mdep], stdout=PIPE, + stderr=STDOUT).wait() != 0: + fail += _("No squashfs module found\n") + + if Popen(["grep", "/aufs.ko", mdep], stdout=PIPE, + stderr=STDOUT).wait() == 0: + self.ufs='_aufs' + nplist.append("aufs2-util") + + elif Popen(["grep", "/unionfs.ko", mdep], stdout=PIPE, + stderr=STDOUT).wait() == 0: + self.ufs='_unionfs' + + else: + fail += _("No aufs or unionfs module found\n") + + for p in nplist: + if not self.haspack(p): + fail += _("Package '%s' is needed by larch systems\n") % p + + if not self.haspack("syslinux"): + warn += _("Without package 'syslinux' you will not be able\n" + "to create syslinux or isolinux booting media\n") + + if (not self.haspack("cdrkit")) and (not self.haspack("cdrtools")): + warn += _("Without package 'cdrkit' (or 'cdrtools') you will\n" + "not be able to create CD/DVD media\n") + + if not self.haspack("eject"): + warn += _("Without package 'eject' you will have problems\n" + "using CD/DVD media\n") + + if warn: + cont = query_yn(_("WARNING:\n%s" + "\n Continue building?") % warn) + else: + cont = True + + if fail: + errout(_("ERROR:\n%s") % fail) + + return cont + + + def haspack(self, package): + """Check whether the given package is installed. + """ + for p in os.listdir(self.installation0 + '/var/lib/pacman/local'): + if p.rsplit("-", 2)[0] == package: + return True + return False + + + def find_kernel(self): + # The uncomfortable length of this function is deceptive, + # most of it is for dealing with errors. + comment("Seeking kernel information") + kinfo = (readfile(self.profile_dir + '/kernel') + if os.path.isfile(self.profile_dir + '/kernel') + else readdata('kernel')) + vmlinuz, self.kernel = kinfo.split() + kernels = [] + files = os.listdir(self.installation0 + '/boot') + for kf in files: + p = Popen(['file', '-b', self.installation0 + '/boot/' + kf], + stdout=PIPE, stderr=STDOUT) + if p.communicate()[0].startswith('Linux kernel'): + kernels.append(kf) + if vmlinuz not in kernels: + if vmlinuz in files: + comment("(WARNING): File '%s' not recognised as kernel" + % vmlinuz) + else: + errout(_("Kernel file '%s' not found") % vmlinuz) + if len(kernels) > 1: + comment("(WARNING): More than one kernel found:\n %s" % + "\n ".join(kernels)) + self.kname = vmlinuz + + # Try to read the kernel version from a *.kver file + self.kversion = None + verfile = self.installation0 + '/etc/mkinitcpio.d/%s.kver' % self.kernel + if os.path.isfile(verfile): + gvars = {} + execfile(verfile, gvars) + self.kversion = gvars.get('ALL_kver') + else: + comment("(WARNING): No kernel version file (%s)" % verfile) + + # mkinitcpio preset file for the kernel + self.presetfile = self.installation0 + '/etc/mkinitcpio.d/%s.preset' % self.kernel + if not os.path.isfile(self.presetfile): + errout(_("No mkinitcpio preset file (%s)") % self.presetfile) + + # Now check module directories + moduledirs = [] + kvfound = False + dirs = os.listdir(self.installation0 + '/lib/modules') + for kv in dirs: + if os.path.isfile(self.installation0 + + ('/lib/modules/%s/modules.dep' % kv)): + moduledirs.append(kv) + if kv == self.kversion: + kvfound = True + + else: + kmpath = self.installation0 + ('/lib/modules/%s' % kv) + comment("Unexpected kernel files at %s" % kmpath) + # Try to find packages concerned + p = Popen(["find", ".", "-name", "*.ko"], cwd=kmpath, + stdout=PIPE, stderr=STDOUT) + r = p.communicate()[0] + if p.returncode == 0: + packs = [] + for km in r.split(): + a = chroot(self.installation0, + 'pacman -Qoq /lib/modules/%s/%s' + % (kv, km)) + + if a: + pack = "-".join(a[0].split()) + if pack not in packs: + packs.append(pack) + comment(" Package: %s" % pack) + + else: + comment("Couldn't determine guilty packages") + + if not query_yn(_("WARNING:" + "\n You seem to have installed a package containing modules" + "\nwhich aren't compatible with your kernel (see log)." + "\nPlease check that this won't cause problems." + "\nMaybe you need the corresponding package for your kernel?" + "\n\n Continue building?")): + return False + + if len(moduledirs) > 1: + comment("(WARNING): More than one kernel module directory found:\n %s" % + "\n ".join(moduledirs)) + if not kvfound: + if len(moduledirs) == 1: + self.kversion = moduledirs[0] + comment("Assuming kernel version = '%s'" % self.kversion) + comment("Kernel updates will probably fail.") + else: + errout(_("Couldn't find kernel modules")) + + comment("Kernel: %s - version: %s" % (self.kname, self.kversion)) + chroot(self.installation0, "depmod %s" % self.kversion) + return True + + + def gen_initramfs(self): + # Determine larch mkinitcpio.conf file + conf = '/etc/mkinitcpio.conf.larch' + if os.path.isfile(self.overlay + conf): + conf = CHROOT_DIR_OVERLAY + conf + + # Save original preset file (unless a '*.larchsave' is already present) + oldir = self.overlay + "/etc/mkinitcpio.d" + runcmd("mkdir -p %s" % oldir) + olpreset = '%s/%s.preset' % (oldir, self.kernel) + if not os.path.isfile("%s.larchsave" % self.presetfile): + runcmd("cp %s %s.larchsave" % (self.presetfile, olpreset)) + + # Adapt larch.preset file for kernel name and replace standard preset + writefile(readfile(self.installation0 + '/etc/mkinitcpio.d/larch.preset' + ).replace('_k_', self.kernel), olpreset) + + # Generate initramfs + return chroot(self.installation0, + "mkinitcpio -k %s -c %s -g %s" % + (self.kversion, conf, + CHROOT_DIR_MEDIUM + "/boot/larch.img")) + + +def encryptPW(pw): + """Encrypt a password - needed for user account generation. + """ + salt = '$1$' + for i in range(8): + salt += random.choice("./0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + return crypt.crypt(pw, salt) + + + +if __name__ == "__main__": + from optparse import OptionParser, OptionGroup + parser = OptionParser(usage=_("usage: %prog [options]")) + + parser.add_option("-p", "--profile", action="store", type="string", + default="", dest="profile", + help=_("Profile: 'user:profile-name' or path to profile directory")) + parser.add_option("-i", "--installation-dir", action="store", type="string", + default="", dest="idir", + help=_("Path to directory to be larchified (default %s)") + % INSTALLATION) + parser.add_option("-s", "--slave", action="store_true", dest="slave", + default=False, help=_("Run as a slave from a controlling program" + " (e.g. from a gui)")) + parser.add_option("-q", "--quiet", action="store_true", dest="quiet", + default=False, help=_("Suppress output messages, except errors" + " (no effect if -s specified)")) + parser.add_option("-o", "--oldsqf", action="store_true", dest="oldsqf", + default=False, help=_("Reuse previously generated system.sqf")) + parser.add_option("-l", "--oldlocales", action="store_true", + dest="oldlocales", default=False, + help=_("Reuse previously generated locales")) + parser.add_option("-f", "--force", action="store_true", dest="force", + default=False, help=_("Don't ask for confirmation")) + + (options, args) = parser.parse_args() + + if os.getuid() != 0: + print _("This application must be run as root") + sys.exit(1) + init('larchify', options) + builder = Builder(options) + if builder.build(options): + sys.exit(0) + else: + sys.exit(1) diff --git a/build_tools/larch8/larch0/cli/media_common.py b/build_tools/larch8/larch0/cli/media_common.py new file mode 100644 index 0000000..0a7fd6e --- /dev/null +++ b/build_tools/larch8/larch0/cli/media_common.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python2 +# +# media_common.py - support functions for medium creation +# +# (c) Copyright 2009 - 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.11.28 + +import os, re +from config import * +from backend import * + +class Medium: + """This class represents a boot medium image. + It converts a larchified system to a boot medium image, by preparing + the bootloader directories and adding customisation stuff + from the profile, but it does not write to any medium. + Alternatively it can mount and prepare an existing larch medium for + copying. + """ + def __init__(self, options): + self.options = options + + if options.source: + # Mount the device or file + runcmd('mkdir -p %s' % MPS) + ok = False + if options.source.endswith('.iso'): + if mount(options.source, MPS, '-o loop'): + ok = True + + elif options.source.startswith('/dev/'): + if mount(options.source, MPS): + ok = True + + elif options.source.startswith('/'): + if mount(options.source, MPS, '--bind'): + ok = True + + elif mount(options.source, MPS, '-L'): + ok = True + + if not ok: + errout(_("Invalid source medium: '%s'") % options.source) + + # Paths needed for the further processing + # - Assume no Arch installation available + self.chrootpath = '' + # - Temporary work area, mainly for building the boot directory + self.build = BUILD0 + runcmd('rm -rf %s' % self.build) + runcmd('mkdir -p %s' % self.build) + # - The source medium + self.medium_dir = MPS + + check_larchimage(self.medium_dir) + if options.testmedium: + unmount() + comment('-- larch medium: ok') + return + + # Fetch the existing boot directory + runcmd('cp -r %s/boot %s' % (self.medium_dir, self.build)) + runcmd('rm -f %s/boot/isolinux/isolinux.boot' % self.build) + runcmd('rm -f %s/boot/isolinux/ldlinux.sys' % self.build) + + else: + # Paths needed for the further processing + # - Using the Arch installation for chrooting + installation_dir = get_installation_dir() + self.chrootpath = (installation_dir + if installation_dir != '/' else '') + # - Temporary work area, mainly for building the boot directory + self.build = self.chrootpath + BUILD0 + runcmd('rm -rf %s' % self.build) + runcmd('mkdir -p %s' % self.build) + # - The source medium (as produced by larchify) + self.medium_dir = self.chrootpath + CHROOT_DIR_MEDIUM + if options.testmedium: + return + + # Further initialisation for initial builds. + check_larchimage(self.medium_dir) + self.profile_dir = get_profile() + self._bootdir() + self._customizelarchdir() + + + + def _bootdir(self): + """Prepare the boot directory for the bootloader. The + bootloader configuration files are not generated yet, as these + depend on the medium. + """ + comment("Fetch kernel and initramfs") + if not runcmd('cp -r %s/boot %s' % (self.medium_dir, self.build))[0]: + errout(_("No kernel and/or initramfs")) + + comment("Preparing bootloader directory") + # A basic /boot directory is provided in larch at cd-root/boot0. + # The contents of this directory are placed in the medium's 'boot' directory. + # Individual files can be added or substituted by + # supplying them in the profile at cd-root/boot. + # It is also possible to completely replace the basic boot directory + # by having cd-root/boot0 in the profile - then the default + # larch version will not be used. + source0 = '%s/cd-root/boot0' % self.profile_dir + if not os.path.isdir(source0): + source0 = '%s/cd-root/boot0' % base_dir + runcmd('bash -c "cp -r %s/* %s/boot"' % (source0, self.build)) + # Copy any additional profile stuff + psource = '%s/cd-root/boot' % self.profile_dir + if os.path.isdir(psource): + runcmd('bash -c "cp -rf %s/* %s/boot"' % (psource, self.build)) + + # Copy vesamenu.c32, chain.c32 to the boot directory + for slfile in ('vesamenu.c32', 'chain.c32'): + runcmd('cp %s/%s %s/boot/isolinux' % + (self.chrootpath + SYSLINUXDIR, slfile, self.build)) + # and rename base config file + runcmd(('mv %s/boot/isolinux/isolinux.cfg' + ' %s/boot/isolinux/isolinux.cfg_0') + % (self.build, self.build)) + + # Prepare utilities for copying media + supportlibdir = BUILD0 + '/boot/support/lib' + chroot(self.chrootpath, 'mkdir -p %s' % supportlibdir) + for application in ('extlinux', 'syslinux', + 'mksquashfs', 'unsquashfs'): + runnable = chroot(self.chrootpath, 'which %s' % application) + if runnable: + runnable = runnable[0] + else: +# Should I output something? + continue + + for line in chroot(self.chrootpath, 'ldd %s' % runnable): + m = re.search(r'=> (/[^ ]+) ', line) + if m: + chroot(self.chrootpath, 'cp -n %s %s' % + (m.group(1), supportlibdir)) + + chroot(self.chrootpath, 'cp %s %s' % (runnable, supportlibdir)) + + chroot(self.chrootpath, 'bash -c "cp /lib/ld-linux*.so.2 %s/loader"' + % supportlibdir) + + runcmd('cp %s/mbr.bin %s/boot/support' % + (self.chrootpath + SYSLINUXDIR, self.build)) + runcmd('cp %s/isolinux.bin %s/boot/support' % + (self.chrootpath + SYSLINUXDIR, self.build)) + + + def _customizelarchdir(self): + """The medium's larch directory will be (re)built. + First delete anything apart from system.sqf and mods.sqf, then + add anything relevant from the profile. + """ + for fd in os.listdir(self.medium_dir + '/larch'): + if fd not in ('system.sqf', 'mods.sqf'): + runcmd('rm -rf %s/larch/%s' % (self.medium_dir, fd)) + plarch = self.profile_dir + '/cd-root/larch' + if os.path.isdir(plarch): + runcmd('bash -c "cp -r %s/* %s/larch"' % (plarch, self.medium_dir)) + + + def setup_destination(self, device): + """The basic preparation of a destination partition. + """ + drive = device[:8] + partno = device[8:] + if not os.path.exists(device): + errout(_("Invalid output device: %s") % device) + + def parted(cmd, optm=''): + s = runcmd('parted -s %s %s %s' % (optm, drive, cmd)) + if s[0]: + if s[1]: + return s[1] + else: + return True + return False + + # Prepare for formatting + if self.options.format: + fmtcmd = 'mkfs.ext4' + self.fstype = 'ext4' + if self.options.nojournal: + fmtcmd += ' -O ^has_journal' + labellength = 16 + opt = 'L' + else: + fmtcmd = None + # Check device format + ok, lines = runcmd('blkid -c /dev/null -o value -s TYPE %s' % device) + if not ok: + errout(_("Couldn't get format information for %s") % device) + self.fstype = lines[0] + + fsflag = '#' + if self.fstype in OKFS: + fsflag = '+' + elif self.fstype == 'vfat': + fsflag = '-' + if fsflag == '#': + errout(_("Unsupported file-system: %s") % self.fstype) + + if self.options.testmedium: + return + + # List drive content: 'parted -m <drive> print' + # - don't use 'free' because then you may get multiple lines starting '1:'. + driveinfo = parted('print', '-m') + + if fmtcmd: + lopt = ('-%s "%s"' % (opt, check_label(self.options.label, labellength)) + if self.options.label else '') + + # Get partition table type from line with drive at beginning: + # > /dev/sdc:3898MB:scsi:512:512:msdos:Intenso Rainbow; + # - filter out partition table type (field 6) + ptable = driveinfo[1].split(':')[5] + + # Filter out line for chosen partition (1): + # 2:2000MB:3898MB:1898MB:::; + # - remember field 2 and 3 + pstart, pend = None, None + for p in driveinfo[2:]: + pinfo = p.split(':') + if pinfo[0] == partno: + pstart, pend = pinfo[1:3] + + fail = True + # Delete partition: 'parted <drive> rm <partno>' + if parted('rm %s' % partno): + + # Recreate partition: + # - if partition number > 4 AND it is an msdos partition table use + # 'logical' instead of 'primary' + # 'parted <drive> mkpart primary <ext2|fat32> <pstart> <pend>' + ptype = 'logical' if (ptable == 'msdos') and (int(partno) > 4) else 'primary' + if parted('mkpart %s %s %s %s' % (ptype, 'ext2', pstart, pend)): + + # Format file system + if chroot(self.chrootpath, '%s %s %s' % (fmtcmd, lopt, device), ['dev']): + fail = False + + if fail: + errout(_("Couldn't format %s") % device) + + # Set boot flag: 'parted <drive> set <partno> boot on' + # and make drive bootable. + # Only do this if installing mbr. + if self.options.mbr: + # First remove boot flag from any partition which might have it ('boot'): + # 'parted <drive> set <partno> boot off' + for l in driveinfo[2:]: + if 'boot' in l: + parted('set %s boot off' % l.split(':', 1)[0]) + + parted('set %s boot on' % partno) + runcmd('dd if=%s/boot/support/mbr.bin of=%s' % (self.build, drive)) + + # Need to get the label - if not formatting (an option for experts) + # it is probably not a good idea to change the volume label, so + # use the old one. + label = get_device_label(device) + + # Write bootloader configuration file + bootconfig(self.build, label, device, self.options.detection) + + # Mount partition and remove larch and boot dirs + runcmd('rm -rf %s' % MPD) + runcmd('mkdir -p %s' % MPD) + if not mount(device, MPD): + errout(_("Couldn't mount larch partition, %s") % device) + runcmd('rm -rf %s/larch' % MPD) + runcmd('rm -rf %s/boot' % MPD) + + # Copy files to device + runcmd('cp -r %s/larch %s' % (self.medium_dir, MPD)) + runcmd('cp -r %s/boot %s' % (self.build, MPD)) + + + def mkiso(self, xopts=''): + """Build an iso containing the stuff in self.build, and optionally + more - passed in xopts. + """ + # Actually the volume label can be 32 bytes, but 16 is compatible + # with ext2 (etc.) (though a little longer than vfat) + label = check_label(self.options.label, 16) + + # Get fresh isolinux.bin + runcmd('cp %s/boot/support/isolinux.bin %s/boot/isolinux' + % (self.build, self.build)) + + # Build iso + ok, res = runcmd('mkisofs -R -l -no-emul-boot -boot-load-size 4' + ' -b boot/isolinux/isolinux.bin -c boot/isolinux/isolinux.boot' + ' -boot-info-table -input-charset=UTF-8' + ' -V "%s"' + ' -o "%s"' + ' %s %s' % (label, self.options.isofile, xopts, self.build), + filter=mkisofs_filter_gen()) + if not ok: + errout(_("iso build failed")) + comment(" *** %s ***" % (_("%s was successfully created") + % (self.options.isofile))) + + + +def check_larchimage(mediumdir): + """A very simple check that the given path is that of a larch medium (image). + """ + testfile = mediumdir + '/larch/system.sqf' + if not os.path.isfile(testfile): + errout(_("File '%s' doesn't exist, '%s' is not a larch medium") + % (testfile, mediumdir)) + + +def get_device_label(device): + """Return the volume label of the given device. + """ + return runcmd('blkid -c /dev/null -o value -s LABEL %s' + % device)[1][0] + + +def check_label(l, n): + if isinstance(l, unicode): + l = l.encode('utf8') + if len(l) > n: + if query_yn(_("The volume label is too long. Use the default (%s)?") + % LABEL): + return LABEL + else: + errout(_("Cancelled")) + return l + + +def add_larchboot(idir): + writefile("The presence of the file 'larch/larchboot' enables\n" + "booting the device in 'search' mode.\n", idir + '/larch/larchboot') + + +def bootconfig(medium, label='', device='', detection=''): + """Convert and complete the bootlines file. + """ + kernel = readfile(medium + '/boot/kernelname').strip() + # - add boot partition to options + if detection == 'uuid': + bootp = ('uuid=' + + runcmd('blkid -c /dev/null -o value -s UUID %s' + % device)[1][0].strip()) + elif detection == 'label': + if not label: + errout(_("Can't boot to label - device has no label")) + bootp = 'label=' + label + elif detection == 'partition': + bootp = 'root=' + device + else: + bootp = '' + + # - convert bootfiles to the correct format, + # inserting necessary info + bootconf = medium + '/boot/bootlines' + if not os.path.isfile(bootconf): + errout(_("Boot configuration file '%s' not found") % bootconf) + fhi = open(bootconf) + insert = '' + i = 0 + block = '' + title = '' + opts = '' + for line in fhi: + line = line.strip() + if not line: + if title: + i += 1 + # A block is ready + block += 'label %02d\n' % i + block += 'MENU LABEL %s\n' % title + block += 'kernel /boot/%s\n' % kernel + block += 'append initrd=/boot/larch.img %s %s\n' % (bootp, opts) + if i > 1: + insert += '\n' + insert += block + block = '' + title = '' + opts = '' + + elif line.startswith('comment:'): + block += '#%s\n' % (line.split(':', 1)[1]) + + elif line.startswith('title:'): + title = line.split(':', 1)[1].lstrip() + + elif line.startswith('options:'): + opts = line.split(':', 1)[1].lstrip() + fhi.close() + + # - insert the resulting string into the bootloader config file + configfile = 'isolinux/isolinux.cfg' + configfile0 = configfile + '_0' + configpath = '%s/boot/%s' % (medium, configfile0) + if not os.path.isfile(configpath): + errout(_("Base configuration file (%s) not found") % configpath) + fhi = open(configpath) + fho = open('%s/boot/%s' % (medium, configfile), 'w') + for line in fhi: + if line.startswith("###LARCH"): + fho.write(insert) + else: + fho.write(line) + fhi.close() + fho.close() diff --git a/build_tools/larch8/larch0/cli/medium.py b/build_tools/larch8/larch0/cli/medium.py new file mode 100755 index 0000000..94cb0c9 --- /dev/null +++ b/build_tools/larch8/larch0/cli/medium.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python2 +# +# medium.py +# +# (c) Copyright 2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.11.09 + +"""This is a command line script to place a larch system on a medium. +It can handle a larchified Arch installation for the initial creation of +a larch medium (source=""), but it can also copy from one existing +medium to another, or create a boot-iso for an existing larch medium +(only relevant for partitions). +The source can be a CD/DVD, an iso file or a partition (unmounted), the +output can be an iso file or another (unmounted) partition. In the latter +case the partition detection mode can be selected and data persistence +can be enabled (or disabled). In addition, the volume label of the +destination device can be set. +Parameters are passed as options and arguments. + +A further source possiblity is an already mounted larch system, this being +specified by a source path starting with '/' (except '/dev/...'). This +option is provided for use with a running live system. +""" + + +import os +from backend import * +from media_common import * + +def build_medium(options, device): + """Copy an existing larch medium, specified by the option 'source' to + the partition specified as argument. + 'device' is the name (e.g. '/dev/sdb1') of the partition to + receive the larch image. + """ + # Basic initialisation of the larchified source + medium = Medium(options) + build = medium.build + medium_dir = medium.medium_dir + + if device: # for destination partition (not iso) + # This handles the bulk of the destination medium setup + medium.setup_destination(device) + + if options.testmedium: + return + + if options.bootiso: + unmount() + # Get bootloader configuration file + fconf = build + '/boot/isolinux/syslinux.cfg' + if not os.path.isfile(fconf): + fconf = build + '/boot/isolinux/extlinux.conf' + ok, res = runcmd('mv %s %s/boot/isolinux/isolinux.cfg' % (fconf, build)) + if not ok: + errout(_("Couldn't find boot configuration file")) + medium.mkiso() + + else: # Now not boot iso! + runcmd('rm -f %s/boot/isolinux/syslinux.cfg' % build) + runcmd('rm -f %s/boot/isolinux/extlinux.conf' % build) + + # Now, need to test for overlay.medium and/or mods.sqf - the presence + # of both is not supported here (although the larch initramfs hook + # can cope with it). + if os.path.isfile('%s/larch/overlay.medium' % medium_dir): + if os.path.isfile('%s/larch/mods.sqf' % medium_dir): + errout(_("Copying of devices with both 'overlay.medium' and 'mods.sqf'\n" + "is not supported.")) + if device and options.persist and (medium.fstype != 'vfat'): + # Copy the overlay files to the destination medium + for fd in os.listdir(medium_dir): + if (fd[0] != '.') and (fd not in IGNOREDIRS.split()): + runcmd('cp -a %s/%s %s' % (medium_dir, fd, MPD)) + + else: + # Create a modifications archive, mods.sqf + if device: + modsdst = MPD + else: + modsdst = build + runcomd('mkdir -p %s/larch' % modsdst) + if not runcmd('%s/boot/support/support mksquashfs %s %s/larch/mods.sqf' + ' -wildcards -e %s' + % (build, medium_dir, modsdst, IGNOREDIRS), + filter=mksquashfs_filter_gen())[0]: + errout(_("Squashing mods.sqf failed")) + # remove execute attrib + runcmd('chmod oga-x %s/mods.sqf' % modsdst) + + elif device and options.persist and (medium.fstype != 'vfat'): + # mods.sqf must be unpacked onto the medium + modsfile = medium_dir + '/larch/mods.sqf' + if os.path.isfile(modsfile): + runcmd('rm %s/larch/mods.sqf' % MPD) + runcmd('%s/boot/support/support unsquashfs -d %s/.mods %s/larch/mods.sqf' + % (build, MPD, medium_dir)) + if not os.path.isdir(MPD + '/.mods'): + errout(_("Unpacking of modifications archive failed, see log")) + runcmd('bash -c "mv %s/.mods/* %s"' % (MPD, MPD)) + runcmd('rm -rf %s/.mods' % MPD) + writefile("The presence of the file 'larch/overlay.medium' causes\n" + "the medium to be used as a writeable, persistent overlay\n" + "for the larch root file-system.\n", + MPD + '/larch/overlay.medium') + + if device: + # To boot in 'search' mode the file larch/larchboot must be present. + runcmd('rm -f %s/larch/larchboot' % MPD) + if options.larchboot: + add_larchboot(MPD) + + if medium.fstype != 'vfat': + # extlinux is installed to a mounted partition. + # The configuration file must be called extlinux.conf: + runcmd('mv %s/boot/isolinux/isolinux.cfg %s/boot/isolinux/extlinux.conf' + % (MPD, MPD)) + # Install extlinux + runcmd('%s/boot/support/support extlinux -i %s/boot/isolinux' + % (build, MPD)) + # Unmount device(s) + unmount() + + else: + # syslinux is installed to an unmounted partition. + # The configuration file must be called syslinux.cfg: + runcmd('mv %s/boot/isolinux/isolinux.cfg %s/boot/isolinux/syslinux.cfg' + % (MPD, MPD)) + unmount() + # Install syslinux + runcmd('%s/boot/support/support syslinux -d /boot/isolinux -i %s' + % (build, device)) + + comment(" *** %s ***" % (_("Completed writing to %s") % device)) + + else: # iso + # Write bootloader configuration file + bootconfig(build) + # At present the 'larchboot' file is not necessary for booting from + # optical media, but it should probably be present anyway. + if not os.path.isfile(medium_dir + '/larch/larchboot'): + add_larchboot(build) + + medium.mkiso(' -x %s/boot %s' % (medium_dir, medium_dir)) + unmount() + + runcmd('rm -rf %s' % build) + + + +if __name__ == '__main__': + from optparse import OptionParser, OptionGroup + parser = OptionParser(usage=_("usage: %prog [options] [partition (e.g. /dev/sdb1)]")) + + parser.add_option('-S', '--source', action='store', type="string", + dest='source', default='', + help=_("Specify source medium, an iso-file (path ending '.iso'), device" + " (starting '/dev/') or volume label")) + parser.add_option("-l", "--label", action="store", type="string", + default="", dest="label", + help=_("Volume label for boot medium (default: %s - or %s if boot iso)") + % (LABEL, BOOTISOLABEL)) + parser.add_option("-b", "--bootiso", action="store_true", + dest="bootiso", default=False, + help=_("Build a boot iso for the source partition")) + + # Options for building from larchified installation (no -S) + parser.add_option("-p", "--profile", action="store", type="string", + default="", dest="profile", + help=_("Profile: 'user:profile-name' or path to profile directory")) + parser.add_option("-i", "--installation-dir", action="store", type="string", + default="", dest="idir", + help=_("Path to larchified directory (default %s)") % INSTALLATION) + + # Options for iso output + parser.add_option("-o", "--isofile", action="store", type="string", + default="", dest="isofile", + help=_("Specify the output file (default: '%s' in the current " + "directory - or '%s' if boot iso)") % (ISOFILE, BOOTISO)) + + # Options for writing to partition + parser.add_option("-d", "--detect", action="store", type="string", + default="label", dest="detection", + help=(_("Method for boot partition detection: %s (default: label)") + % detection_methods)) + parser.add_option("-n", "--nosearchboot", action="store_false", + dest="larchboot", default=True, + help=_("Don't generate 'larch/larchboot' file")) + parser.add_option("-F", "--noformat", action="store_false", + default=True, dest="format", + help=_("Don't format the medium (WARNING: Only for experts)")) + parser.add_option("-P", "--persist", action="store_true", + dest="persist", default=False, + help=_("Enable data persistence (using medium as writeable" + " file-system). Default: disabled")) + parser.add_option("-m", "--noboot", action="store_false", + dest="mbr", default=True, + help=_("Don't install the bootloader (to the MBR)")) + parser.add_option("-j", "--nojournal", action="store_true", dest="nojournal", + default=False, help=_("Don't use journalling on boot medium" + " (default: journalling enabled)")) + + # General minor options + parser.add_option("-q", "--quiet", action="store_true", dest="quiet", + default=False, help=_("Suppress output messages, except errors" + " (no effect if -s specified)")) + parser.add_option("-s", "--slave", action="store_true", dest="slave", + default=False, help=_("Run as a slave from a controlling program" + " (e.g. from a gui)")) + parser.add_option('-T', '--testmedium', action='store_true', + dest='testmedium', default=False, + help=_("Test source or destination medium only (used by gui)")) + + (options, args) = parser.parse_args() + options.force = False + + if options.bootiso: + if args: + errout(_("Unexpected argument: %s") % args[0]) + if not options.source: + errout(_("No source specified for boot iso")) + if not options.isofile: + options.isofile = BOOTISO + if not options.label: + options.label = BOOTISOLABEL + greet = _("Generating larch boot iso file: %s\n") + else: + if not options.isofile: + options.isofile = ISOFILE + if not options.label: + options.label = LABEL + greet = _("Generating larch iso file: %s\n") + + # Location for the resulting iso, forcing a '.iso' suffix + if not options.isofile.endswith('.iso'): + options.isofile += '.iso' + + if args: + device = args[0] + if device.startswith('/dev/'): + print '##', ((_("Testing output medium: %s\n") + if options.testmedium + else _("Creating larch medium on: %s\n")) % device) + else: + print '##', (_("Invalid partition: '%s'") % device) + sys.exit(1) + else: + options.isofile = os.path.join(os.getcwd(), options.isofile) + print '##', ((_("Testing source medium: %s\n") % options.source) + if options.testmedium + else (greet % options.isofile)) + device = '' + + if os.getuid() != 0: + print _("This application must be run as root") + sys.exit(1) + + init('live_medium', options) + build_medium(options, device) diff --git a/build_tools/larch8/larch0/cli/userinfo.py b/build_tools/larch8/larch0/cli/userinfo.py new file mode 100644 index 0000000..f2045aa --- /dev/null +++ b/build_tools/larch8/larch0/cli/userinfo.py @@ -0,0 +1,93 @@ +# userinfo.py +# +# (c) Copyright 2009-2010 Michael Towers (larch42 at googlemail dot com) +# +# This file is part of the larch project. +# +# larch is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# larch 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with larch; if not, write to the Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# +#---------------------------------------------------------------------------- +# 2010.07.11 + +import os +from ConfigParser import SafeConfigParser + +# Default list of 'additional' groups for a new user +BASEGROUPS = 'video,audio,optical,storage,scanner,power,camera' + +class Userinfo: + def __init__(self, profile): + self.profile_dir = profile + + def getusers(self): + """Read user information by means of a SafeConfigParser instance. + This is then available as self.userconf. + """ + self.userconf = SafeConfigParser({'pw':'', 'maingroup':'', 'uid':'', + 'skel':'', 'xgroups':BASEGROUPS, 'expert':''}) + users = self.profile_dir + '/users' + if os.path.isfile(users): + try: + self.userconf.read(users) + except: + error0(_("Invalid 'users' file")) + + def allusers(self): + self.getusers() + return self.userconf.sections() + + def get(self, user, field): + return self.userconf.get(user, field) + + def userinfo(self, user, fields): + """Get an ordered list of the given field data for the given user. + """ + return [self.userconf.get(user, f) for f in fields] + + def userset(self, uname, field, text): + self.userconf.set(uname, field, text) + + def newuser(self, user): + try: + self.userconf.add_section(user) + return self.saveusers() + except: + error0(_("Couldn't add user '%s'") % user) + return False + + def deluser(self, user): + try: + self.userconf.remove_section(user) + return self.saveusers() + except: + error0(_("Couldn't remove user '%s'") % user) + return False + + def saveusers(self): + """Save the user configuration data (in 'INI' format) + """ + try: + fh = None + fh = open(self.profile_dir + '/users', 'w') + self.userconf.write(fh) + fh.close() + return True + except: + if fh: + fh.close() + error0(_("Couldn't save 'users' file")) + self.getusers() + return False + |