diff options
Diffstat (limited to 'build_tools/larch7/larch0/cli/media_common.py')
-rw-r--r-- | build_tools/larch7/larch0/cli/media_common.py | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/build_tools/larch7/larch0/cli/media_common.py b/build_tools/larch7/larch0/cli/media_common.py new file mode 100644 index 0000000..b39e67d --- /dev/null +++ b/build_tools/larch7/larch0/cli/media_common.py @@ -0,0 +1,450 @@ +#!/usr/bin/env python +# +# media_common.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.14 + +"""This code is used by both iso generator and partition creator modules. +The larch medium creation stage first uses the Medium class to prepare the +bootloader directories. build_iso and build_partition can also be used to +rebuild media starting from existing larch media, e.g. for conversions from +CD to usb-stick and vice-versa. Even a switch of bootloader is possible as +the bootloader configs for both GRUB and syslinux/isolinux are normally +included on the medium. +If not using a larch installation directory, the bootloader and/or mkisofs +must be supported by the host (according to medium and bootloader). +""" + +import os +from config import * +from backend import * + +class Medium: + """This class represents a boot medium image. + It checks the options for conflicts. + If necessary (no -S option provided on the command line) it + converts a larchified system to a boot medium image, by preparing + the bootloader directories and adding customization stuff + from the profile, but it does not write to any medium. + """ + def __init__(self, options): + self.options = options + if options.setdir: + try: + wd = os.path.realpath(options.setdir) + os.chdir(wd) + except: + errout(_("Couldn't switch directory to '%s'") % wd) + self.iso = False + self.installation_dir = get_installation_dir() + self.installation0 = (self.installation_dir + if self.installation_dir != '/' else '') + + # Check options + if options.chroot and options.nochroot: + errout(_("Option -C conflicts with -c")) + + if options.source: + # Using a specified source image + if options.profile: + errout(_("Option -S conflicts with -p")) + + # Use chroot? + self.chrootpath = self.installation0 if options.chroot else '' + + else: + # Using the larch build system + + # Use chroot? + self.chrootpath = '' if options.nochroot else self.installation0 + + # Mount point for source, if necessary + self.mps = self.chrootpath + SOURCEMOUNT + runcmd('rm -rf %s' % self.mps) + runcmd('mkdir -p %s' % self.mps) + + # Create a temporary work area + self.build = self.chrootpath + BUILD0 + runcmd('rm -rf %s' % self.build) + runcmd('mkdir -p %s' % self.build) + + if options.source: + # Location of medium + self.medium_dir = self._get_source(options.source, + options.syslinux, self.chrootpath, test=options.testmedium) + + else: + # Location for the live medium image + self.medium_dir = self.installation0 + CHROOT_DIR_MEDIUM + self._check_larchimage(self.medium_dir) + self.profile_dir = get_profile() + + self._bootdir(options.syslinux) + self._customizelarchdir() + + # The boot directory needs to be outside of the medium directory + # for some of the next steps + runcmd('cp -a %s/boot %s' % (self.medium_dir, self.build)) + + + def unmount(self): + if self.medium_dir == self.mps: + unmount(self.mps) + + if self.iso: + if self.chrootpath: + unmount(self.chrootpath + ISOBUILDMNT) + # Change owner of iso to match current directory + cwdstat = os.stat(os.getcwd()) + os.lchown(self.iso, cwdstat.st_uid, cwdstat.st_gid) + + + def get_device_label(self, device): + l = runcmd('blkid -c /dev/null -o value -s LABEL %s' + % device)[1][0] + if self.options.detection not in detection_methods.split('|'): + errout(_("Invalid detection method (-d option)")) + return l + + + def chroot_medium_dir(self): + """Get the medium path relative within the (possible) chroot. + """ + return self.medium_dir[len(self.chrootpath):] + + + def iso_path(self): + """If building an iso within a chroot the build directory must + be bind-mounted into the accessible area. + """ + self.iso = self.options.isofile.replace('/', '_') + basedir = os.getcwd() + if self.chrootpath: + runcmd('mkdir -p %s' % (self.chrootpath + ISOBUILDMNT)) + if not mount(basedir, self.chrootpath + ISOBUILDMNT, '--bind'): + errout(_("Couldn't bind-mount current directory")) + basedir = ISOBUILDMNT + return os.path.join(basedir, self.iso) + + + def _check_larchimage(self, mediumdir): + 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_source(self, source, syslinux, ipath='', test=False): + source = os.path.realpath(source) + testcode = 64 if test else 0 # For various tests, below + + # Is source an iso? + if os.path.isfile(source): + # Mount it + if mount(source, self.mps, '-o loop'): + source = self.mps + else: + errout(_("Couldn't mount '%s'. Not an iso?") % source) + + # Is source a device? + elif source.startswith('/dev/'): + mp = _get_mountpoint(source) + if mp: + source = mp + elif mount(source, self.mps): + source = self.mps + else: + errout(_("Couldn't mount '%s'") % source) + + # Simple check that it is really a larch image + self._check_larchimage(source) + + # Check bootloader support + if not (os.path.isdir(source + '/boot/isolinux') + or os.path.isdir(source + '/boot/syslinux')): + if test or syslinux: + errout(_("Source doesn't support syslinux"), 0) + testcode += 8 + if not os.path.isdir(source + '/boot/grub'): + if test or not syslinux: + errout(_("Source doesn't support GRUB"), 0) + testcode += 16 + + if testcode: + if os.path.isfile(source + 'larch/nosave'): + testcode += 2 + + unmount() + sys.exit(testcode) + return None + + # Is source within the chroot directory? + if ipath and not source.startswith(ipath + '/'): + if mount(source, self.mps, '--bind'): + source = self.mps + else: + errout(_("Couldn't bind-mount '%s'") % source) + + # Copy boot files + runcmd('cp -a %s/boot %s' % (source, self.build)) + + # Remove old config files + runcmd('rm -f %s/boot/syslinux/syslinux.cfg' % self.build) + runcmd('rm -f %s/boot/isolinux/isolinux.cfg' % self.build) + runcmd('rm -f %s/boot/grub/menu.lst' % self.build) + runcmd('rm -f %s/boot/isolinux/isolinux.bin' % self.build) + runcmd('rm -f %s/boot/isolinux/isolinux.boot' % self.build) + + # Rename the syslinux boot directory if necessary + if os.path.isdir(self.build + '/boot/syslinux'): + runcmd('mv %s/boot/syslinux %s/boot/isolinux' + % (self.build, self.build)) + + return source + + + def _bootdir(self, syslinux): + """Prepare boot directories for the various bootloaders. 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 directories") + # Only include the directories if the corresponding bootloader + # package ('grub' and/or 'syslinux') is installed + grub = os.path.isfile(self.installation0 + GRUBDIR + + '/stage2_eltorito') + isolinux = os.path.isfile(self.installation0 + SYSLINUXDIR + + '/isolinux.bin') + # Check bootloader support + if syslinux: + if not isolinux: + errout(_("Installation doesn't support syslinux")) + elif not grub: + errout(_("Installation doesn't support GRUB")) + + # Bootloader independent files are 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('cp -rf %s/* %s' % (psource, self.build)) + # Get the boot options file + bootlines = self.profile_dir + '/bootlines' + if not os.path.isfile(bootlines): + bootlines = base_dir + '/data/bootlines' + runcmd('cp -f %s %s/boot' % (bootlines, self.build)) + + # The idea is that a basic boot directory for each boot-loader is + # provided in larch at cd-root/{grub0,isolinux0}. These are copied + # to the medium as 'boot/grub' and 'boot/isolinux'. Individual files + # can be added or substituted by supplying them in the profile at + # cd-root/{grub,isolinux}. + # It is also possible to completely replace the basic boot directory + # by having cd-root/{grub0,isolinux0} in the profile - then the default + # larch versions will not be used. + for ok, boot_dir in ((grub, 'grub'), (isolinux, 'isolinux')): + if ok: + # Copy bootloader specific stuff + source0 = '%s/cd-root/%s0' % (self.profile_dir, boot_dir) + if not os.path.isdir(source0): + source0 = '%s/cd-root/%s0' % (base_dir, boot_dir) + runcmd('cp -rf %s %s/boot/%s' + % (source0, self.build, boot_dir)) + + # Copy any additional profile stuff + psource = '%s/cd-root/%s' % (self.profile_dir, boot_dir) + if os.path.isdir(psource): + runcmd('cp -rf %s %s/boot' + % (psource, self.build)) + + # Copy the grub boot files to the medium's grub directory + # and rename base config file + if grub: + runcmd('bash -c "cp %s/* %s/boot/grub"' % + (self.installation0 + GRUBDIR, self.build)) + runcmd('mv %s/boot/grub/menu.lst %s/boot/grub/menu.lst_0' + % (self.build, self.build)) + + # Copy mbr.bin, vesamenu.c32 and isolinux.bin (renamed) + # to the boot directory and rename base config file + if isolinux: + runcmd('cp %s/mbr.bin %s/boot/isolinux' % + (self.installation0 + SYSLINUXDIR, self.build)) + runcmd('cp %s/vesamenu.c32 %s/boot/isolinux' % + (self.installation0 + SYSLINUXDIR, self.build)) + runcmd('cp %s/isolinux.bin %s/boot/isolinux/isolinux.bin0' % + (self.installation0 + SYSLINUXDIR, self.build)) + runcmd(('mv %s/boot/isolinux/isolinux.cfg' + ' %s/boot/isolinux/isolinux.cfg_0') + % (self.build, self.build)) + + + def _customizelarchdir(self): + # Replace any existing larch/copy directory + runcmd('rm -rf %s/larch/copy' % self.medium_dir) + if os.path.isdir(self.profile_dir + '/cd-root/larch/copy'): + runcmd('cp -r %s/cd-root/larch/copy %s/larch' % + (self.profile_dir, self.medium_dir)) + + # Replace any existing larch/extra directory + runcmd('rm -rf %s/larch/extra' % self.medium_dir) + if os.path.isdir(self.profile_dir + '/cd-root/larch/extra'): + runcmd('cp -r %s/cd-root/larch/extra %s/larch' % + (self.profile_dir, self.medium_dir)) + + # Handle existence of larch/nosave + runcmd('rm -f %s/larch/nosave' % self.medium_dir) + if os.path.isfile(self.profile_dir + '/nosave'): + runcmd('cp %s/nosave %s/larch' % + (self.profile_dir, self.medium_dir)) + + + +def _get_mountpoint(path): + ok, lines = runcmd('cat /etc/mtab') + for l in lines: + ll = l.split() + if (len(ll) > 4) and (ll[1] == path): + return ll[0] + return '' + + +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, syslinux, label='', device='', detection=''): + """Convert and complete the bootlines file. + """ + # - 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 + + if syslinux: + # isolinux/syslinux + block += 'label %02d\n' % i + block += 'MENU LABEL %s\n' % title + block += 'kernel /boot/larch.kernel\n' + block += ('append initrd=/boot/larch.img %s %s\n' + % (bootp, opts)) + + else: + # GRUB + block += 'title %s\n' % title + block += ('kernel /boot/larch.kernel %s %s\n' + % (bootp, opts)) + block += 'initrd /boot/larch.img\n' + + 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 + if syslinux: + boot_dir = ('isolinux' if os.path.isdir(medium + '/boot/isolinux') + else 'syslinux') + configfile0 = 'isolinux.cfg_0' + configfile = boot_dir + '.cfg' + else: + boot_dir = 'grub' + configfile0 = 'menu.lst_0' + configfile = 'menu.lst' + configpath = '%s/boot/%s/%s' % (medium, boot_dir, configfile0) + if not os.path.isfile(configpath): + errout(_("Base configuration file (%s) not found") % configpath) + fhi = open(configpath) + fho = open('%s/boot/%s/%s' % (medium, boot_dir, configfile), 'w') + for line in fhi: + if line.startswith("###LARCH"): + fho.write(insert) + else: + fho.write(line) + fhi.close() + fho.close() |