lorax/src/pylorax/install.py
2010-01-14 14:26:55 +01:00

503 lines
18 KiB
Python

#
# install.py
# class for creating install images
#
# Copyright (C) 2009 Red Hat, Inc.
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Red Hat Author(s): David Cantrell <dcantrell@redhat.com>
# Martin Gracik <mgracik@redhat.com>
#
import os
import shutil
import glob
import fnmatch
import re
import commands
import pwd
import grp
import stat
import config
import output
import lcs
import utils
class InstallImage(object):
def __init__(self):
self.conf = config.LoraxConfig.get()
self.paths = config.LoraxPaths.get()
self.output = output.Terminal.get()
self.actions = self.get_actions_from_template()
def get_actions_from_template(self):
variables = { "instroot" : self.conf.installtree }
if self.conf.scrubs_template is not None:
template = lcs.TemplateParser(variables)
return template.get_actions(self.conf.scrubs_template)
return []
def move_shared_files(self):
dirs = [os.path.join(self.paths.INSTALLTREE_DATADIR, "noarch"),
os.path.join(self.paths.INSTALLTREE_DATADIR, self.conf.arch)]
self.output.info(":: copying the custom install tree files")
for dir in [dir for dir in dirs if os.path.isdir(dir)]:
utils.scopy(src_root=dir, src_path="*",
dst_root=self.conf.installtree, dst_path="")
def process_actions(self):
for action in self.actions:
action.execute()
# XXX why do we need this?
def copy_stubs(self):
for file in ("raidstart", "raidstop", "losetup", "list-harddrives",
"loadkeys", "mknod", "syslogd"):
src = os.path.join(self.conf.installtree, "usr", "lib", "anaconda",
"%s-stub" % file)
dst = os.path.join(self.conf.installtree, "usr", "bin", file)
shutil.copy2(src, dst)
# XXX i cannot find this in the repos for f12
def configure_fedorakmod(self):
if os.path.isfile(self.paths.FEDORAKMODCONF):
utils.replace(self.paths.FEDORAKMODCONF,
r"installforallkernels = 0",
r"installforallkernels = 1")
# XXX why do we need this?
def copy_bootloaders(self):
if self.conf.arch in ("i386", "i586", "x86_64"):
for f in glob.glob(os.path.join(self.paths.BOOTDIR, "memtest*")):
shutil.copy2(f, self.paths.ANACONDA_BOOT)
elif self.conf.arch == "sparc":
for f in glob.glob(os.path.join(self.paths.BOOTDIR, "*.b")):
shutil.copy2(f, self.paths.ANACONDA_BOOT)
elif self.conf.arch in ("ppc", "ppc64"):
f = os.path.join(self.paths.BOOTDIR, "efika.forth")
shutil.copy2(f, self.paths.ANACONDA_BOOT)
# XXX alpha stuff should not be needed anymore
#elif self.conf.arch == "alpha":
# f = os.path.join(self.paths.BOOTDIR, "bootlx")
# shutil.copy2(f, self.paths.ANACONDA_BOOT)
elif self.conf.arch == "ia64":
utils.scopy(src_root=self.paths.BOOTDIR_IA64, src_path="*",
dst_root=self.paths.ANACONDA_BOOT, dst_dir="")
# XXX why do we need this?
def move_repos(self):
src = os.path.join(self.conf.installtree, "etc", "yum.repos.d")
dst = os.path.join(self.conf.installtree, "etc", "anaconda.repos.d")
shutil.move(src, dst)
# XXX why do we need this?
def move_anaconda_files(self):
# move anaconda executable
src = os.path.join(self.conf.installtree, "usr", "sbin", "anaconda")
dst = os.path.join(self.conf.installtree, "usr", "bin", "anaconda")
shutil.move(src, dst)
# move anaconda libraries
dstdir = os.path.join(self.conf.installtree, "usr", self.conf.libdir)
utils.scopy(src_root=self.paths.ANACONDA_RUNTIME, src_path="lib*",
dst_root=dstdir, dst_path="")
utils.remove(os.path.join(self.paths.ANACONDA_RUNTIME, "lib*"))
# XXX this saves 40 MB
def create_modules_symlinks(self):
utils.mkdir(os.path.join(self.conf.installtree, "modules"))
utils.mkdir(os.path.join(self.conf.installtree, "firmware"))
utils.remove(os.path.join(self.conf.installtree, "lib", "modules"))
utils.remove(os.path.join(self.conf.installtree, "lib", "firmware"))
utils.symlink("/modules",
os.path.join(self.conf.installtree, "lib", "modules"))
utils.symlink("/firmware",
os.path.join(self.conf.installtree, "lib", "firmware"))
# XXX why do we need this?
def fix_man_pages(self):
# fix up some links for man page related stuff
for file in ("nroff", "groff", "iconv", "geqn", "gtbl", "gpic",
"grefer"):
target = os.path.join("/mnt/sysimage/usr/bin", file)
name = os.path.join(self.conf.installtree, "usr", "bin", file)
if not os.path.isfile(name):
utils.symlink(target, name)
# fix /etc/man.config to point into /mnt/sysimage
utils.replace(self.paths.MANCONFIG, r"^MANPATH\s+(\S+)",
"MANPATH\t/mnt/sysimage\g<1>")
utils.replace(self.paths.MANCONFIG, r"^MANPATH_MAP\s+(\S+)\s+(\S+)",
"MANPATH_MAP\t\g<1>\t/mnt/sysimage\g<2>")
# XXX this saves 2 MB
def remove_gtk_stuff(self):
pass
# # figure out the gtk+ theme to keep
# gtkrc = os.path.join(self.conf.installtree, "etc", "gtk-2.0", "gtkrc")
#
# gtk_theme_name = None
# gtk_engine = None
# gtk_icon_themes = []
#
# if os.path.isfile(gtkrc):
# f = open(gtkrc, "r")
# lines = f.readlines()
# f.close()
#
# for line in lines:
# line = line.strip()
# if line.startswith("gtk-theme-name"):
# gtk_theme_name = line[line.find("=") + 1:]
# gtk_theme_name = gtk_theme_name.replace('"', "").strip()
#
# # find the engine for this theme
# gtkrc = os.path.join(self.conf.installtree, "usr", "share",
# "themes", gtk_theme_name, "gtk-2.0", "gtkrc")
# if os.path.isfile(gtkrc):
# f = open(gtkrc, "r")
# engine_lines = f.readlines()
# f.close()
#
# for engine_l in engine_lines:
# engine_l = engine_l.strip()
# if engine_l.find("engine") != -1:
# gtk_engine = engine_l[engine_l.find('"') + 1:]
# gtk_engine = gtk_engine.replace('"', "").strip()
# break
#
# elif line.startswith("gtk-icon-theme-name"):
# icon_theme = line[line.find("=") + 1:]
# icon_theme = icon_theme.replace('"', "").strip()
# gtk_icon_themes.append(icon_theme)
#
# # bring in all inherited themes
# while True:
# icon_theme_index = os.path.join(self.conf.installtree,
# "usr", "share", "icons", icon_theme,
# "index.theme")
#
# if os.path.isfile(icon_theme_index):
# inherits = False
# f = open(icon_theme_index, "r")
# icon_lines = f.readlines()
# f.close()
#
# for icon_l in icon_lines:
# icon_l = icon_l.strip()
# if icon_l.startswith("Inherits="):
# inherits = True
# icon_theme = icon_l[icon_l.find("=") + 1:]
# icon_theme = \
# icon_theme.replace('"', "").strip()
#
# gtk_icon_themes.append(icon_theme)
# break
#
# if not inherits:
# break
# else:
# break
#
# # remove themes we don't need
# theme_path = os.path.join(self.conf.installtree, "usr", "share",
# "themes")
#
# if os.path.isdir(theme_path):
# for theme in filter(lambda theme: theme != gtk_theme_name,
# os.listdir(theme_path)):
#
# theme = os.path.join(theme_path, theme)
# shutil.rmtree(theme, ignore_errors=True)
#
# # remove icons we don't need
# icon_path = os.path.join(self.conf.installtree, "usr", "share",
# "icons")
#
# if os.path.isdir(icon_path):
# for icon in filter(lambda icon: icon not in gtk_icon_themes,
# os.listdir(icon_path)):
#
# icon = os.path.join(icon_path, icon)
# shutil.rmtree(icon, ignore_errors=True)
#
# # remove engines we don't need
# tmp_path = os.path.join(self.conf.installtree, "usr",
# self.conf.libdir, "gtk-2.0")
#
# if os.path.isdir(tmp_path):
# fnames = map(lambda fname: os.path.join(tmp_path, fname,
# "engines"), os.listdir(tmp_path))
#
# dnames = filter(lambda fname: os.path.isdir(fname), fnames)
# for dir in dnames:
# engines = filter(lambda e: e.find(gtk_engine) == -1,
# os.listdir(dir))
# for engine in engines:
# engine = os.path.join(dir, engine)
# os.unlink(engine)
# XXX this saves 5 MB
def remove_locales(self):
if os.path.isfile(self.paths.LANGTABLE):
keep = set()
with open(self.paths.LANGTABLE, "r") as f:
lines = f.readlines()
for line in lines:
line = line.strip()
if not line or line.startswith("#"):
continue
fields = line.split("\t")
dir = os.path.join(self.paths.LOCALEPATH, fields[1])
if os.path.isdir(dir):
keep.add(fields[1])
locale = fields[3].split(".")[0]
dir = os.path.join(self.paths.LOCALEPATH, locale)
if os.path.isdir(dir):
keep.add(locale)
for locale in os.listdir(self.paths.LOCALEPATH):
if locale not in keep:
path = os.path.join(self.paths.LOCALEPATH, locale)
utils.remove(path)
# XXX this saves 5 MB
def remove_unnecessary_files(self):
for root, dirs, files in os.walk(self.conf.installtree):
for file in files:
path = os.path.join(root, file)
if fnmatch.fnmatch(path, "*.a"):
if path.find("kernel-wrapper/wrapper.a") == -1:
os.unlink(path)
if fnmatch.fnmatch(path, "lib*.la"):
if path.find("usr/" + self.conf.libdir + "/gtk-2.0") == -1:
os.unlink(path)
if fnmatch.fnmatch(path, "*.py"):
if os.path.isfile(path + "o"):
os.unlink(path + "o")
if os.path.isfile(path + "c"):
os.unlink(path + "c")
utils.symlink("/dev/null", path + "c")
# remove libunicode-lite
utils.remove(os.path.join(self.conf.installtree, "usr",
self.conf.libdir, "libunicode-lite*"))
# XXX this saves 1 MB
def remove_python_stuff(self):
for fname in ("bsddb", "compiler", "curses", "distutils", "email",
"encodings", "hotshot", "idlelib", "test",
"doctest.py", "pydoc.py"):
utils.remove(os.path.join(self.conf.installtree, "usr",
self.conf.libdir, "python?.?", fname))
# XXX the udev package should get fixed,
# but for now, we have to fix it ourselves, otherwise we get an error
def fix_udev_links(self):
# these links are broken by default (at least on i386)
for filename in ("udevcontrol", "udevsettle", "udevtrigger"):
filename = os.path.join(self.conf.installtree, "sbin", filename)
if os.path.islink(filename):
os.unlink(filename)
os.symlink("udevadm", filename)
def move_bins(self):
# move bin to usr/bin
utils.scopy(src_root=self.conf.installtree,
src_path=os.path.join("bin", "*"),
dst_root=self.conf.installtree,
dst_path=os.path.join("usr", "bin"))
utils.remove(os.path.join(self.conf.installtree, "bin"))
# move sbin to /usr/sbin
utils.scopy(src_root=self.conf.installtree,
src_path=os.path.join("sbin", "*"),
dst_root=self.conf.installtree,
dst_path=os.path.join("usr", "sbin"))
utils.remove(os.path.join(self.conf.installtree, "sbin"))
# fix broken links
brokenlinks = []
for dir in ("bin", "sbin"):
dir = os.path.join(self.conf.installtree, "usr", dir)
for root, dnames, fnames in os.walk(dir):
for fname in fnames:
fname = os.path.join(root, fname)
if os.path.islink(fname) and not os.path.exists(fname):
brokenlinks.append(fname)
for link in brokenlinks:
target = os.readlink(link)
newtarget = re.sub(r"^\.\./\.\./(bin|sbin)/(.*)$",
r"../\g<1>/\g<2>", target)
if newtarget != target:
os.unlink(link)
utils.symlink(newtarget, link)
def create_ld_so_conf(self):
ldsoconf = os.path.join(self.conf.installtree, "etc", "ld.so.conf")
utils.touch(ldsoconf)
procdir = os.path.join(self.conf.installtree, "proc")
if not os.path.isdir(procdir):
utils.mkdir(procdir)
cmd = "mount -t proc proc %s" % procdir
err, output = commands.getstatusoutput(cmd)
with open(ldsoconf, "w") as f:
f.write("/usr/kerberos/%s\n" % self.conf.libdir)
cwd = os.getcwd()
os.chdir(self.conf.installtree)
# XXX os.chroot does not support exiting from the root
cmd = "/usr/sbin/chroot %s /sbin/ldconfig" % self.conf.installtree
err, output = commands.getstatusoutput(cmd)
os.chdir(cwd)
cmd = "umount %s" % procdir
err, output = commands.getstatusoutput(cmd)
os.unlink(ldsoconf)
def change_tree_permissions(self):
root_uid = pwd.getpwnam("root")[2]
root_gid = grp.getgrnam("root")[2]
for root, dirs, files in os.walk(self.conf.installtree):
os.chown(root, root_uid, root_gid)
os.chmod(root, 0755)
for file in files:
# skip broken symlinks
if not os.path.exists(file):
continue
path = os.path.join(root, file)
os.chown(path, root_uid, root_gid)
mode = os.stat(path).st_mode
if (mode & stat.S_IXUSR) or (mode & stat.S_IXGRP) \
or (mode & stat.S_IXOTH):
os.chmod(path, 0555)
else:
os.chmod(path, 0444)
def prepare(self):
# copy the .buildstamp
shutil.copy2(self.conf.buildstamp, self.conf.installtree)
self.move_shared_files()
self.process_actions()
self.copy_stubs()
self.configure_fedorakmod()
self.copy_bootloaders()
self.move_repos()
self.move_anaconda_files()
self.create_modules_symlinks()
self.fix_man_pages()
self.remove_gtk_stuff()
self.remove_locales()
self.remove_unnecessary_files()
self.remove_python_stuff()
self.fix_udev_links()
self.move_bins()
self.create_ld_so_conf()
self.change_tree_permissions()
def create(self, type="squashfs"):
self.prepare()
installimg = os.path.join(self.conf.tempdir, "install.img")
if os.path.exists(installimg):
os.unlink(installimg)
self.output.info(":: compressing the image file")
if type == "squashfs":
if not os.path.exists(self.paths.MKSQUASHFS):
self.output.error("'%s' does not exist" % self.paths.MKSQUASHFS)
return None
cmd = "%s %s %s -all-root -no-fragments -no-progress" % \
(self.paths.MKSQUASHFS, self.conf.installtree, installimg)
err, output = commands.getstatusoutput(cmd)
if err:
self.output.error(output)
return None
elif type == "cramfs":
if not os.path.exists(self.paths.MKCRAMFS):
self.output.error("'%s' does not exist" % self.paths.MKCRAMFS)
return None
crambs = ""
if self.conf.arch == "sparc64":
crambs = "--blocksize 8192"
elif self.conf.arch == "sparc":
crambs = "--blocksize 4096"
cmd = "%s %s %s %s" % (self.paths.MKCRAMFS, crambs,
self.conf.installtree, installimg)
err, output = commands.getstatusoutput(cmd)
if err:
self.output.error(output)
return None
elif type == "ext2":
# TODO
raise NotImplementedError
# edit the .treeinfo
text = "[stage2]\nmainimage = images/install.img\n"
utils.edit(self.conf.treeinfo, append=True, text=text)
return installimg