summaryrefslogtreecommitdiffstats
path: root/build_tools/larch8/larch0/cli
diff options
context:
space:
mode:
authorJames Meyer <james.meyer@operamail.com>2010-12-02 22:37:23 (GMT)
committerJames Meyer <james.meyer@operamail.com>2010-12-02 22:37:34 (GMT)
commit8b94d7f39c71234712bead363526a0283efeb9fa (patch)
tree23f1dbd6458dc39a2c1b08bcdd4cbf768a60d84d /build_tools/larch8/larch0/cli
parent338af567e74d08cbd357079941208e494463d61e (diff)
downloadlinhes_dev-8b94d7f39c71234712bead363526a0283efeb9fa.zip
larch8: first checkin, still needs some work
Diffstat (limited to 'build_tools/larch8/larch0/cli')
-rwxr-xr-xbuild_tools/larch8/larch0/cli/archin.py380
-rw-r--r--build_tools/larch8/larch0/cli/backend.py459
-rw-r--r--build_tools/larch8/larch0/cli/config.py83
-rwxr-xr-xbuild_tools/larch8/larch0/cli/larchify.py606
-rw-r--r--build_tools/larch8/larch0/cli/media_common.py432
-rwxr-xr-xbuild_tools/larch8/larch0/cli/medium.py277
-rw-r--r--build_tools/larch8/larch0/cli/userinfo.py93
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
+