From b86522755bfbc24f55e9116ae6d5f08361ae0a93 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 4 Apr 2014 18:18:07 -0400 Subject: [PATCH 1/4] gui/spokes/software: Enable iff payload is PackagePayload This should be the equivalent of the livecd test. --- pyanaconda/ui/gui/spokes/software.py | 4 ++-- pyanaconda/ui/gui/spokes/source.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyanaconda/ui/gui/spokes/software.py b/pyanaconda/ui/gui/spokes/software.py index 322f8ca..77a4b4f 100644 --- a/pyanaconda/ui/gui/spokes/software.py +++ b/pyanaconda/ui/gui/spokes/software.py @@ -23,7 +23,7 @@ from gi.repository import Gtk, Pango from pyanaconda.flags import flags from pyanaconda.i18n import _, C_, CN_ -from pyanaconda.packaging import MetadataError +from pyanaconda.packaging import MetadataError, PackagePayload from pyanaconda.threads import threadMgr, AnacondaThread from pyanaconda import constants @@ -172,7 +172,7 @@ class SoftwareSelectionSpoke(NormalSpoke): @property def showable(self): - return not flags.livecdInstall and not self.data.method.method == "liveimg" + return isinstance(self.payload, PackagePayload) @property def status(self): diff --git a/pyanaconda/ui/gui/spokes/source.py b/pyanaconda/ui/gui/spokes/source.py index 5d13366..271c0ad 100644 --- a/pyanaconda/ui/gui/spokes/source.py +++ b/pyanaconda/ui/gui/spokes/source.py @@ -41,7 +41,7 @@ from pyanaconda.ui.gui.utils import enlightbox, fire_gtk_action from pyanaconda.iutil import ProxyString, ProxyStringError, cmp_obj_attrs from pyanaconda.ui.gui.utils import gtk_call_once, really_hide, really_show from pyanaconda.threads import threadMgr, AnacondaThread -from pyanaconda.packaging import PayloadError, MetadataError +from pyanaconda.packaging import PayloadError, MetadataError, PackagePayload from pyanaconda import constants from blivet.util import get_mount_paths @@ -797,7 +797,7 @@ class SourceSpoke(NormalSpoke): @property def showable(self): - return not flags.livecdInstall and not self.data.method.method == "liveimg" + return isinstance(self.payload, PackagePayload) def _mirror_active(self): return self._protocolComboBox.get_active() == PROTOCOL_MIRROR -- 1.8.3.1 From 0ae4b189334e51b1e412c301ab41f5b4de2471ea Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 4 Apr 2014 20:43:30 -0400 Subject: [PATCH 2/4] users: Deduplicate code to fork()+chroot() This is in preparation for a patch to do it for the password setting, which blew up in the OSTree patchset. I'm not sure why this apparently isn't broken in the traditional path. --- pyanaconda/users.py | 101 +++++++++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 61 deletions(-) diff --git a/pyanaconda/users.py b/pyanaconda/users.py index 9113a18..93b84b5 100644 --- a/pyanaconda/users.py +++ b/pyanaconda/users.py @@ -26,6 +26,7 @@ import random import tempfile import os import os.path +from contextlib import contextmanager from pyanaconda import iutil import pwquality from pyanaconda.iutil import strip_accents @@ -201,19 +202,13 @@ class Users: def __init__ (self): self.admin = libuser.admin() - def createGroup (self, group_name, **kwargs): - """Create a new user on the system with the given name. Optional kwargs: - - gid -- The GID for the new user. If none is given, the next - available one is used. - root -- The directory of the system to create the new user - in. homedir will be interpreted relative to this. - Defaults to /mnt/sysimage. - """ + def _prepareChroot(self, root): + # Unfortunately libuser doesn't have an API to operate on a + # chroot, so we hack it here by forking a child and calling + # chroot() in that child's context. + import sys childpid = os.fork() - root = kwargs.get("root", ROOT_PATH) - if not childpid: if not root in ["","/"]: os.chroot(root) @@ -221,7 +216,34 @@ class Users: del(os.environ["LIBUSER_CONF"]) self.admin = libuser.admin() + + return childpid + + def _finishChroot(self, childpid): + assert childpid > 0 + try: + status = os.waitpid(childpid, 0)[1] + except OSError as e: + log.critical("exception from waitpid: %s %s", e.errno, e.strerror) + return False + + if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0): + return True + else: + return False + def createGroup (self, group_name, **kwargs): + """Create a new user on the system with the given name. Optional kwargs: + + gid -- The GID for the new user. If none is given, the next + available one is used. + root -- The directory of the system to create the new user + in. homedir will be interpreted relative to this. + Defaults to /mnt/sysimage. + """ + + childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH)) + if childpid == 0: if self.admin.lookupGroupByName(group_name): log.error("Group %s already exists, not creating.", group_name) os._exit(1) @@ -238,17 +260,8 @@ class Users: os._exit(1) os._exit(0) - - try: - status = os.waitpid(childpid, 0)[1] - except OSError as e: - log.critical("exception from waitpid while creating a group: %s %s", e.errno, e.strerror) - return False - - if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0): - return True else: - return False + return self._finishChroot(childpid) def createUser (self, user_name, *args, **kwargs): """Create a new user on the system with the given name. Optional kwargs: @@ -279,17 +292,8 @@ class Users: gid -- The GID for the new user. If none is given, the next available one is used. """ - childpid = os.fork() - root = kwargs.get("root", ROOT_PATH) - - if not childpid: - if not root in ["","/"]: - os.chroot(root) - os.chdir("/") - del(os.environ["LIBUSER_CONF"]) - - self.admin = libuser.admin() - + childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH)) + if childpid == 0: if self.admin.lookupUserByName(user_name): log.error("User %s already exists, not creating.", user_name) os._exit(1) @@ -390,44 +394,19 @@ class Users: os._exit(1) os._exit(0) - - try: - status = os.waitpid(childpid, 0)[1] - except OSError as e: - log.critical("exception from waitpid while creating a user: %s %s", e.errno, e.strerror) - return False - - if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0): - return True else: - return False + return self._finishChroot(childpid) def checkUserExists(self, username, root=ROOT_PATH): - childpid = os.fork() - - if not childpid: - if not root in ["","/"]: - os.chroot(root) - os.chdir("/") - del(os.environ["LIBUSER_CONF"]) - - self.admin = libuser.admin() + childpid = self._prepareChroot(root) + if childpid == 0: if self.admin.lookupUserByName(username): os._exit(0) else: os._exit(1) - - try: - status = os.waitpid(childpid, 0)[1] - except OSError as e: - log.critical("exception from waitpid while creating a user: %s %s", e.errno, e.strerror) - return False - - if os.WIFEXITED(status) and (os.WEXITSTATUS(status) == 0): - return True else: - return False + return self._finishChroot(childpid) def setUserPassword(self, username, password, isCrypted, lock, algo=None): user = self.admin.lookupUserByName(username) -- 1.8.3.1 From 179933694d8fe8ec9b6ad0c00caef67a1ae9d3d4 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 4 Apr 2014 20:50:41 -0400 Subject: [PATCH 3/4] users: Add root= keyword argument to set{User,Root}Password I'm honestly not sure why this isn't broken with the current install path. Every other method has the fork()/chroot() dance around it, so we should do it here too. --- pyanaconda/users.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pyanaconda/users.py b/pyanaconda/users.py index 93b84b5..3c1c8da 100644 --- a/pyanaconda/users.py +++ b/pyanaconda/users.py @@ -408,19 +408,24 @@ class Users: else: return self._finishChroot(childpid) - def setUserPassword(self, username, password, isCrypted, lock, algo=None): - user = self.admin.lookupUserByName(username) + def setUserPassword(self, username, password, isCrypted, lock, algo=None, root=ROOT_PATH): + childpid = self._prepareChroot(root) - if isCrypted: - self.admin.setpassUser(user, password, True) - else: - self.admin.setpassUser(user, cryptPassword(password, algo=algo), True) + if childpid == 0: + user = self.admin.lookupUserByName(username) - if lock: - self.admin.lockUser(user) + if isCrypted: + self.admin.setpassUser(user, password, True) + else: + self.admin.setpassUser(user, cryptPassword(password, algo=algo), True) - user.set(libuser.SHADOWLASTCHANGE, "") - self.admin.modifyUser(user) + if lock: + self.admin.lockUser(user) + + user.set(libuser.SHADOWLASTCHANGE, "") + self.admin.modifyUser(user) + else: + return self._finishChroot(childpid) - def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None): - return self.setUserPassword("root", password, isCrypted, isLocked, algo) + def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None, root=ROOT_PATH): + return self.setUserPassword("root", password, isCrypted, isLocked, algo, root) -- 1.8.3.1 From 4e2269df868c465bdede5409884b3a0dde73cdde Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Wed, 19 Mar 2014 06:06:12 -0400 Subject: [PATCH 4/4] WIP Anaconda+OSTree Conflicts: pyanaconda/__init__.py pyanaconda/bootloader.py pyanaconda/kickstart.py pyanaconda/packaging/__init__.py pyanaconda/ui/gui/hubs/__init__.py pyanaconda/users.py Conflicts: pyanaconda/users.py --- pyanaconda/anaconda.py | 5 +- pyanaconda/bootloader.py | 71 ++++++++----- pyanaconda/flags.py | 3 +- pyanaconda/install.py | 41 +++++-- pyanaconda/iutil.py | 31 +++++- pyanaconda/kickstart.py | 30 +++--- pyanaconda/packaging/__init__.py | 16 +++ pyanaconda/packaging/ostreepayload.py | 194 ++++++++++++++++++++++++++++++++++ pyanaconda/users.py | 17 +-- 9 files changed, 347 insertions(+), 61 deletions(-) create mode 100644 pyanaconda/packaging/ostreepayload.py diff --git a/pyanaconda/anaconda.py b/pyanaconda/anaconda.py index 66706f1..03b2a44 100644 --- a/pyanaconda/anaconda.py +++ b/pyanaconda/anaconda.py @@ -117,7 +117,10 @@ class Anaconda(object): if not klass: from pyanaconda.flags import flags - if flags.livecdInstall: + if flags.ostree or self.ksdata.ostreesetup.osname is not None: + from pyanaconda.packaging.ostreepayload import OSTreePayload + klass = OSTreePayload + elif flags.livecdInstall: from pyanaconda.packaging.livepayload import LiveImagePayload klass = LiveImagePayload elif self.ksdata.method.method == "liveimg": diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py index f930752..f0aa7f7 100644 --- a/pyanaconda/bootloader.py +++ b/pyanaconda/bootloader.py @@ -945,10 +945,13 @@ class BootLoader(object): self.add_crash_args() - config_path = os.path.normpath(ROOT_PATH + self.config_file) + config_path = os.path.normpath(iutil.getSysroot() + self.config_file) if os.access(config_path, os.R_OK): os.rename(config_path, config_path + ".anacbak") + config_parent = os.path.dirname(config_path) + if not os.path.isdir(config_parent): + os.makedirs(config_parent) config = open(config_path, "w") self.write_config_header(config) self.write_config_images(config) @@ -1169,7 +1172,7 @@ class GRUB(BootLoader): if iutil.isConsoleOnVirtualTerminal(self.console): splash = "splash.xpm.gz" - splash_path = os.path.normpath("%s/boot/%s/%s" % (ROOT_PATH, + splash_path = os.path.normpath("%s/boot/%s/%s" % (iutil.getSysroot(), self.splash_dir, splash)) if os.access(splash_path, os.R_OK): @@ -1222,7 +1225,7 @@ class GRUB(BootLoader): def write_device_map(self): """ Write out a device map containing all supported devices. """ - map_path = os.path.normpath(ROOT_PATH + self.device_map_file) + map_path = os.path.normpath(iutil.getSysroot() + self.device_map_file) if os.access(map_path, os.R_OK): os.rename(map_path, map_path + ".anacbak") @@ -1238,7 +1241,7 @@ class GRUB(BootLoader): super(GRUB, self).write_config_post() # make symlink for menu.lst (grub's default config file name) - menu_lst = "%s%s/menu.lst" % (ROOT_PATH, self.config_dir) + menu_lst = "%s%s/menu.lst" % (iutil.getSysroot(), self.config_dir) if os.access(menu_lst, os.R_OK): try: os.rename(menu_lst, menu_lst + '.anacbak') @@ -1251,7 +1254,7 @@ class GRUB(BootLoader): log.error("failed to create grub menu.lst symlink: %s", e) # make symlink to grub.conf in /etc since that's where configs belong - etc_grub = "%s/etc/%s" % (ROOT_PATH, self._config_file) + etc_grub = "%s/etc/%s" % (iutil.getSysroot(), self._config_file) if os.access(etc_grub, os.R_OK): try: os.unlink(etc_grub) @@ -1441,7 +1444,7 @@ class GRUB2(GRUB): def write_device_map(self): """ Write out a device map containing all supported devices. """ - map_path = os.path.normpath(ROOT_PATH + self.device_map_file) + map_path = os.path.normpath(iutil.getSysroot() + self.device_map_file) if os.access(map_path, os.R_OK): os.rename(map_path, map_path + ".anacbak") @@ -1466,7 +1469,7 @@ class GRUB2(GRUB): dev_map.close() def write_defaults(self): - defaults_file = "%s%s" % (ROOT_PATH, self.defaults_file) + defaults_file = "%s%s" % (iutil.getSysroot(), self.defaults_file) defaults = open(defaults_file, "w+") defaults.write("GRUB_TIMEOUT=%d\n" % self.timeout) defaults.write("GRUB_DISTRIBUTOR=\"$(sed 's, release .*$,,g' /etc/system-release)\"\n") @@ -1510,7 +1513,7 @@ class GRUB2(GRUB): if not self.password and not self.encrypted_password: return - users_file = ROOT_PATH + "/etc/grub.d/01_users" + users_file = iutil.getSysroot() + "/etc/grub.d/01_users" header = open(users_file, "w") header.write("#!/bin/sh -e\n\n") header.write("cat << \"EOF\"\n") @@ -1894,8 +1897,9 @@ class Yaboot(YabootBase): log.error("failed to create /etc/yaboot.conf symlink: %s", e) def write_config(self): - if not os.path.isdir(ROOT_PATH + self.config_dir): - os.mkdir(ROOT_PATH + self.config_dir) + configdir = iutil.getSysroot() + self.config_dir + if not os.path.isdir(configdir): + os.mkdir(configdir) # this writes the config super(Yaboot, self).write_config() @@ -2222,17 +2226,20 @@ class EXTLINUX(BootLoader): config.write(stanza) def write_config_header(self, config): + # FIXME re-add this + # "ui menu.c32\n\n" + # after this is fixed: + # https://bugzilla.gnome.org/show_bug.cgi?id=726757 header = ("# extlinux.conf generated by anaconda\n\n" - "ui menu.c32\n\n" "menu autoboot Welcome to %(productName)s. Automatic boot in # second{,s}. Press a key for options.\n" "menu title %(productName)s Boot Options.\n" "menu hidden\n\n" "timeout %(timeout)d\n" "#totaltimeout 9000\n\n" - "default %(default)s\n\n" - % { "productName": productName, "timeout": self.timeout *10, - "default": self.image_label(self.default)}) + % { "productName": productName, "timeout": self.timeout *10 }) config.write(header) + if self.default is not None: + config.write("default %(default)s\n\n" % { "default" : self.image_label(self.default) }) self.write_config_password(config) def write_config_password(self, config): @@ -2241,7 +2248,7 @@ class EXTLINUX(BootLoader): config.write("menu notabmsg Press [Tab] and enter the password to edit options") def write_config_post(self): - etc_extlinux = os.path.normpath(ROOT_PATH + "/etc/" + self._config_file) + etc_extlinux = os.path.normpath(iutil.getSysroot() + "/etc/" + self._config_file) if not os.access(etc_extlinux, os.R_OK): try: os.symlink("../boot/%s" % self._config_file, etc_extlinux) @@ -2331,6 +2338,21 @@ def writeSysconfigKernel(storage, version, instClass): f.write("HYPERVISOR_ARGS=logging=vga,serial,memory\n") f.close() +def writeBootLoaderFinal(storage, payload, instClass, ksdata): + """ Do the final write of the bootloader. """ + + from pyanaconda.errors import errorHandler, ERROR_RAISE + + # set up dracut/fips boot args + # XXX FIXME: do this from elsewhere? + storage.bootloader.set_boot_args(storage=storage, + payload=payload) + try: + storage.bootloader.write() + except BootLoaderError as e: + if errorHandler.cb(e) == ERROR_RAISE: + raise + def writeBootLoader(storage, payload, instClass, ksdata): """ Write bootloader configuration to disk. @@ -2346,6 +2368,13 @@ def writeBootLoader(storage, payload, instClass, ksdata): stage2_device = storage.bootloader.stage2_device log.info("bootloader stage2 target device is %s", stage2_device.name) + if payload.handlesBootloaderConfiguration: + if storage.bootloader.skip_bootloader: + log.info("skipping bootloader install per user request") + return + writeBootLoaderFinal(storage, payload, instClass, ksdata) + return + # get a list of installed kernel packages kernel_versions = payload.kernelVersionList + payload.rescueKernelList if not kernel_versions: @@ -2390,14 +2419,4 @@ def writeBootLoader(storage, payload, instClass, ksdata): label=label, short=short) storage.bootloader.add_image(image) - # set up dracut/fips boot args - # XXX FIXME: do this from elsewhere? - storage.bootloader.set_boot_args(storage=storage, - payload=payload) - - try: - storage.bootloader.write() - except BootLoaderError as e: - if errorHandler.cb(e) == ERROR_RAISE: - raise - + writeBootLoaderFinal(storage, payload, instClass, ksdata) diff --git a/pyanaconda/flags.py b/pyanaconda/flags.py index e6d6e4a..4a7da01 100644 --- a/pyanaconda/flags.py +++ b/pyanaconda/flags.py @@ -68,6 +68,7 @@ class Flags(object): self.leavebootorder = False self.testing = False self.dnf = False + self.ostree = False self.mpathFriendlyNames = True # ksprompt is whether or not to prompt for missing ksdata self.ksprompt = True @@ -80,7 +81,7 @@ class Flags(object): def read_cmdline(self): for f in ("selinux", "debug", "leavebootorder", "testing", "extlinux", - "gpt", "dnf"): + "gpt", "dnf", "ostree"): self.set_cmdline_bool(f) if "rpmarch" in self.cmdline: diff --git a/pyanaconda/install.py b/pyanaconda/install.py index a7e9d38..ce3eca9 100644 --- a/pyanaconda/install.py +++ b/pyanaconda/install.py @@ -25,17 +25,26 @@ from blivet import turnOnFilesystems from pyanaconda.bootloader import writeBootLoader from pyanaconda.progress import progress_report, progressQ from pyanaconda.users import createLuserConf, getPassAlgo, Users +from pyanaconda import iutil from pyanaconda import flags from pyanaconda import timezone from pyanaconda.i18n import _ from pyanaconda.threads import threadMgr import logging +import blivet log = logging.getLogger("anaconda") def _writeKS(ksdata): import os - path = ROOT_PATH + "/root/anaconda-ks.cfg" + roothome = os.path.join(iutil.getSysroot(), "root") + if os.path.islink(roothome) and not os.path.isdir(roothome): + # In the OSTree case, /root -> /var/roothome, but + # it doesn't exist yet. If we created it here, + # we'd need to ensure it was labeled correctly. + # Punt on that for now and just stick it in /var. + roothome = os.path.join(iutil.getSysroot(), "var") + path = os.path.join(roothome, "anaconda-ks.cfg") # Clear out certain sensitive information that kickstart doesn't have a # way of representing encrypted. @@ -165,7 +174,8 @@ def doInstall(storage, payload, ksdata, instClass): payload.preStorage() turnOnFilesystems(storage, mountOnly=flags.flags.dirInstall) - if not flags.flags.livecdInstall and not flags.flags.dirInstall: + write_storage_late = flags.flags.livecdInstall or ksdata.ostreesetup.osname + if not write_storage_late and not flags.flags.dirInstall: storage.write() # Do packaging. @@ -188,15 +198,30 @@ def doInstall(storage, payload, ksdata, instClass): payload.preInstall(packages=packages, groups=payload.languageGroups()) payload.install() - if flags.flags.livecdInstall: - storage.write() - - with progress_report(_("Performing post-installation setup tasks")): - payload.postInstall() - + if write_storage_late: + if iutil.getSysroot() != ROOT_PATH: + blivet.targetSysroot = iutil.getSysroot() + storage.write() + # Now that we have the FS layout in the target, + # umount things that were in the legacy sysroot, + # and put them in the target root, except for the + # physical / + storage.umountFilesystems() + rootmnt = storage.mountpoints.get('/') + rootmnt.setup() + # Explicitly mount the root on the physical sysroot + rootmnt.format.setup(rootmnt.format.options, chroot=ROOT_PATH) + # But everything else goes in the target root + storage.mountFilesystems(skipRoot=True) + else: + storage.write() + # Do bootloader. if willInstallBootloader: with progress_report(_("Installing bootloader")): writeBootLoader(storage, payload, instClass, ksdata) + with progress_report(_("Performing post-installation setup tasks")): + payload.postInstall() + progressQ.send_complete() diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py index 0010ffe..18e8368 100644 --- a/pyanaconda/iutil.py +++ b/pyanaconda/iutil.py @@ -109,6 +109,23 @@ def _run_program(argv, root='/', stdin=None, stdout=None, env_prune=None, log_ou return (proc.returncode, output_string) +_sysroot = ROOT_PATH +def setSysroot(path): + global _sysroot + _sysroot = path + +def getSysroot(): + return _sysroot + +def execInSysimageRoot(command, argv, **kwargs): + """ Run a command from the installed target root. + @param command The command to run + @param argv The argument list + """ + + argv = [command] + argv + return _run_program(argv, root=_sysroot, **kwargs)[0] + def execWithRedirect(command, argv, stdin=None, stdout=None, root='/', env_prune=None, log_output=True, binary_output=False): """ Run an external program and redirect the output to a file. @@ -127,8 +144,14 @@ def execWithRedirect(command, argv, stdin=None, stdout=None, command, " ".join(argv)) return 0 + # Transparently redirect callers requesting root=ROOT_PATH to the + # configured system root. + target_root = root + if target_root == ROOT_PATH: + target_root = _sysroot + argv = [command] + argv - return _run_program(argv, stdin=stdin, stdout=stdout, root=root, env_prune=env_prune, + return _run_program(argv, stdin=stdin, stdout=stdout, root=target_root, env_prune=env_prune, log_output=log_output, binary_output=binary_output)[0] def execWithCapture(command, argv, stdin=None, root='/', log_output=True): @@ -172,6 +195,12 @@ def execReadlines(command, argv, stdin=None, root='/', env_prune=None): queue.put(line.strip()) out.close() + # Transparently redirect callers requesting root=ROOT_PATH to the + # configured system root. + target_root = root + if target_root == ROOT_PATH: + target_root = _sysroot + def chroot(): if root and root != '/': os.chroot(root) diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index b0c104f..e74853f 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -240,8 +240,8 @@ class Authconfig(commands.authconfig.FC3_Authconfig): args = ["--update", "--nostart"] + shlex.split(self.authconfig) if not flags.automatedInstall and \ - (os.path.exists(ROOT_PATH + "/lib64/security/pam_fprintd.so") or \ - os.path.exists(ROOT_PATH + "/lib/security/pam_fprintd.so")): + (os.path.exists(iutil.getSysroot() + "/lib64/security/pam_fprintd.so") or \ + os.path.exists(iutil.getSysroot() + "/lib/security/pam_fprintd.so")): args += ["--enablefingerprint"] try: @@ -482,7 +482,7 @@ class Realm(commands.realm.F19_Realm): # no explicit password arg using implicit --no-password pw_args = ["--no-password"] - argv = ["realm", "join", "--install", ROOT_PATH, "--verbose"] + \ + argv = ["realm", "join", "--install", iutil.getSysroot(), "--verbose"] + \ pw_args + self.join_args rc = -1 try: @@ -502,7 +502,6 @@ class Realm(commands.realm.F19_Realm): log.info("Joined realm %s", self.join_realm) - class ClearPart(commands.clearpart.F17_ClearPart): def parse(self, args): retval = commands.clearpart.F17_ClearPart.parse(self, args) @@ -592,7 +591,7 @@ class Firewall(commands.firewall.F20_Firewall): args += [ "--service=%s" % (service,) ] cmd = "/usr/bin/firewall-offline-cmd" - if not os.path.exists(ROOT_PATH+cmd): + if not os.path.exists(iutil.getSysroot()+cmd): if self.enabled: msg = _("%s is missing. Cannot setup firewall.") % (cmd,) raise KickstartError(msg) @@ -611,7 +610,7 @@ class Firstboot(commands.firstboot.FC3_Firstboot): if self.firstboot == FIRSTBOOT_SKIP: action = "disable" elif self.firstboot == FIRSTBOOT_RECONFIG: - f = open(ROOT_PATH + "/etc/reconfigSys", "w+") + f = open(iutil.getSysroot() + "/etc/reconfigSys", "w+") f.close() iutil.execWithRedirect("systemctl", [action, "initial-setup-graphical.service", @@ -622,7 +621,6 @@ class Group(commands.group.F12_Group): def execute(self, storage, ksdata, instClass, users): for grp in self.groupList: kwargs = grp.__dict__ - kwargs.update({"root": ROOT_PATH}) users.createGroup(grp.name, **kwargs) class IgnoreDisk(commands.ignoredisk.RHEL6_IgnoreDisk): @@ -691,7 +689,7 @@ class IscsiName(commands.iscsiname.FC6_IscsiName): class Lang(commands.lang.F19_Lang): def execute(self, *args, **kwargs): - localization.write_language_configuration(self, ROOT_PATH) + localization.write_language_configuration(self, iutil.getSysroot()) # no overrides needed here Eula = commands.eula.F20_Eula @@ -924,7 +922,7 @@ class Logging(commands.logging.FC6_Logging): class Network(commands.network.F21_Network): def execute(self, storage, ksdata, instClass): - network.write_network_config(storage, ksdata, instClass, ROOT_PATH) + network.write_network_config(storage, ksdata, instClass, iutil.getSysroot()) class MultiPath(commands.multipath.FC6_MultiPath): def parse(self, args): @@ -1375,7 +1373,7 @@ class SELinux(commands.selinux.FC3_SELinux): return try: - selinux_cfg = SimpleConfigFile(ROOT_PATH+"/etc/selinux/config") + selinux_cfg = SimpleConfigFile(iutil.getSysroot()+"/etc/selinux/config") selinux_cfg.read() selinux_cfg.set(("SELINUX", selinux_states[self.selinux])) selinux_cfg.write() @@ -1445,11 +1443,11 @@ class Timezone(commands.timezone.F18_Timezone): "back to default (America/New_York).", self.timezone) self.timezone = "America/New_York" - timezone.write_timezone_config(self, ROOT_PATH) + timezone.write_timezone_config(self, iutil.getSysroot()) # write out NTP configuration (if set) if not self.nontp and self.ntpservers: - chronyd_conf_path = os.path.normpath(ROOT_PATH + ntp.NTP_CONFIG_FILE) + chronyd_conf_path = os.path.normpath(iutil.getSysroot() + ntp.NTP_CONFIG_FILE) try: ntp.save_servers_to_config(self.ntpservers, conf_file_path=chronyd_conf_path) @@ -1462,7 +1460,7 @@ class User(commands.user.F19_User): for usr in self.userList: kwargs = usr.__dict__ - kwargs.update({"algo": algo, "root": ROOT_PATH}) + kwargs.update({"algo": algo}) # If the user password came from a kickstart and it is blank we # need to make sure the account is locked, not created with an @@ -1568,7 +1566,7 @@ class ZFCP(commands.zfcp.F14_ZFCP): class Keyboard(commands.keyboard.F18_Keyboard): def execute(self, *args): - keyboard.write_keyboard_config(self, ROOT_PATH) + keyboard.write_keyboard_config(self, iutil.getSysroot()) class Upgrade(commands.upgrade.F20_Upgrade): # Upgrade is no longer supported. If an upgrade command was included in @@ -1591,7 +1589,7 @@ class SpokeRegistry(dict): return "" def execute(self, storage, ksdata, instClass, users): - path = os.path.join(ROOT_PATH, "var", "lib", "inital-setup") + path = os.path.join(iutil.getSysroot(), "var", "lib", "inital-setup") try: os.makedirs(path, 0755) except OSError: @@ -1806,7 +1804,7 @@ def runPostScripts(scripts): del(os.environ[var]) log.info("Running kickstart %%post script(s)") - map (lambda s: s.run(ROOT_PATH), postScripts) + map (lambda s: s.run(iutil.getSysroot()), postScripts) log.info("All kickstart %%post script(s) have been run") def runPreScripts(scripts): diff --git a/pyanaconda/packaging/__init__.py b/pyanaconda/packaging/__init__.py index c0a8370..4c4b84e 100644 --- a/pyanaconda/packaging/__init__.py +++ b/pyanaconda/packaging/__init__.py @@ -140,6 +140,14 @@ class Payload(object): """ Reset the instance, not including ksdata. """ pass + @property + def handlesBootloaderConfiguration(self): + """ Set if the payload requires the bootloader be installed + but unconfigured before doing an install of the system. This + is used by the OSTreePayload subclass. + """ + return False + ### ### METHODS FOR WORKING WITH REPOSITORIES ### @@ -290,6 +298,14 @@ class Payload(object): ### ### METHODS FOR WORKING WITH PACKAGES ### + @property + def supportsPackages(self): + return False + + @property + def packages(self): + raise NotImplementedError() + def packageSelected(self, pkgid): return pkgid in self.data.packages.packageList diff --git a/pyanaconda/packaging/ostreepayload.py b/pyanaconda/packaging/ostreepayload.py new file mode 100644 index 0000000..f2ed03e --- /dev/null +++ b/pyanaconda/packaging/ostreepayload.py @@ -0,0 +1,194 @@ +# ostreepayload.py +# Deploy OSTree trees to target +# +# Copyright (C) 2012,2014 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Colin Walters +# + +import shutil + +from . import * + + +from pyanaconda.constants import * +from pyanaconda.flags import flags + +from pyanaconda import iutil +from pyanaconda.i18n import _ +from pyanaconda.progress import progressQ +from gi.repository import GLib +from gi.repository import Gio + +from blivet.size import Size + +import logging +log = logging.getLogger("anaconda") + +from pyanaconda.errors import * +#from pyanaconda.progress import progress + +class OSTreePayload(ArchivePayload): + """ A OSTreePayload deploys a tree onto the target system. """ + def __init__(self, data): + super(OSTreePayload, self).__init__(data) + + def setup(self, storage, instClass): + super(OSTreePayload, self).setup(storage, instClass) + + @property + def handlesBootloaderConfiguration(self): + return True + + @property + def kernelVersionList(self): + # OSTree handles bootloader configuration + return [] + + @property + def spaceRequired(self): + # We don't have this data with OSTree at the moment + return Size(500*1000*1000) + + def _safeExecWithRedirect(self, cmd, argv, **kwargs): + """Like iutil.execWithRedirect, but treat errors as fatal""" + rc = iutil.execWithRedirect(cmd, argv, **kwargs) + if rc != 0: + exn = PayloadInstallError("%s %s exited with code %d" % (cmd, argv, rc)) + if errorHandler.cb(exn) == ERROR_RAISE: + raise exn + + def _pullProgressCb(self, asyncProgress): + status = asyncProgress.get_status() + outstanding_fetches = asyncProgress.get_uint('outstanding-fetches') + if status: + progressQ.send_message(status) + elif outstanding_fetches > 0: + bytes_transferred = asyncProgress.get_uint64('bytes-transferred') + fetched = asyncProgress.get_uint('fetched') + requested = asyncProgress.get_uint('requested') + formatted_bytes = GLib.format_size_full(bytes_transferred, 0) + + if requested == 0: + percent = 0.0 + else: + percent = (fetched*1.0 / requested) * 100 + + progressQ.send_message("Receiving objects: %d%% (%d/%d) %s" % (percent, fetched, requested, formatted_bytes)) + else: + progressQ.send_message("Writing objects") + + def install(self): + cancellable = None + from gi.repository import OSTree + ostreesetup = self.data.ostreesetup + log.info("executing ostreesetup=%r" % (ostreesetup, )) + + # Initialize the filesystem - this will create the repo as well + self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH, + "init-fs", ROOT_PATH]) + + repo_arg = "--repo=" + ROOT_PATH + '/ostree/repo' + + # Set up the chosen remote + remote_args = [repo_arg, "remote", "add"] + if ostreesetup.noGpg: + remote_args.append("--set=gpg-verify=false") + remote_args.extend([ostreesetup.remote, + ostreesetup.url]) + self._safeExecWithRedirect("ostree", remote_args) + + sysroot_path = Gio.File.new_for_path(ROOT_PATH) + sysroot = OSTree.Sysroot.new(sysroot_path) + sysroot.load(cancellable) + + repo = sysroot.get_repo(None)[1] + progressQ.send_message(_("Starting pull of %s from %s") % (ostreesetup.ref, ostreesetup.remote)) + + progress = OSTree.AsyncProgress.new() + progress.connect('changed', self._pullProgressCb) + repo.pull(ostreesetup.remote, [ostreesetup.ref], 0, progress, cancellable) + + self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH, + "os-init", ostreesetup.osname]) + + admin_deploy_args = ["admin", "--sysroot=" + ROOT_PATH, + "deploy", "--os=" + ostreesetup.osname] + + admin_deploy_args.append(ostreesetup.osname + ':' + ostreesetup.ref) + + log.info("ostree admin deploy starting") + self._safeExecWithRedirect("ostree", admin_deploy_args) + log.info("ostree admin deploy complete") + + ostree_sysroot_path = os.path.join(ROOT_PATH, 'ostree/deploy', ostreesetup.osname, 'current') + iutil.setSysroot(ostree_sysroot_path) + + def postInstall(self): + super(OSTreePayload, self).postInstall() + + bootmnt = self.storage.mountpoints.get('/boot') + if bootmnt is not None: + # Okay, now /boot goes back to the physical /, since + # that's where ostree --sysroot expects to find it. The + # real fix for the madness is for tools like extlinux to + # have a --sysroot option too. + bootmnt.format.teardown() + bootmnt.teardown() + bootmnt.format.setup(bootmnt.format.options, chroot=ROOT_PATH) + + # Set up the sysroot bind mount after this so that we have the + # tmp -> sysroot/tmp when executing %post scripts and the + # like. + self._safeExecWithRedirect("mount", ["--bind", ROOT_PATH, os.path.join(iutil.getSysroot(), 'sysroot')]) + + # FIXME - Move extlinux.conf to syslinux.cfg, since that's + # what OSTree knows about. + sysroot_boot_extlinux = os.path.join(ROOT_PATH, 'boot/extlinux') + sysroot_boot_syslinux = os.path.join(ROOT_PATH, 'boot/syslinux') + sysroot_boot_loader = os.path.join(ROOT_PATH, 'boot/loader') + if os.path.isdir(sysroot_boot_extlinux): + assert os.path.isdir(sysroot_boot_loader) + orig_extlinux_conf = os.path.join(sysroot_boot_extlinux, 'extlinux.conf') + target_syslinux_cfg = os.path.join(sysroot_boot_loader, 'syslinux.cfg') + log.info("Moving %s -> %s" % (orig_extlinux_conf, target_syslinux_cfg)) + os.rename(orig_extlinux_conf, target_syslinux_cfg) + # A compatibility bit for OSTree + os.mkdir(sysroot_boot_syslinux) + os.symlink('../loader/syslinux.cfg', os.path.join(sysroot_boot_syslinux, 'syslinux.cfg')) + # And *also* tell syslinux that the config is really in /boot/loader + os.symlink('loader/syslinux.cfg', os.path.join(ROOT_PATH, 'boot/syslinux.cfg')) + + # FIXME - stub out firewalld + firewalloffline = os.path.join(iutil.getSysroot(), 'usr/bin/firewall-offline-cmd') + if not os.path.exists(firewalloffline): + os.symlink('true', firewalloffline) + + # OSTree owns the bootloader configuration, so here we give it + # the argument list we computed from storage, architecture and + # such. + set_kargs_args = ["admin", "--sysroot=" + ROOT_PATH, + "instutil", "set-kargs"] + set_kargs_args.extend(self.storage.bootloader.boot_args) + set_kargs_args.append("root=" + self.storage.rootDevice.fstabSpec) + self._safeExecWithRedirect("ostree", set_kargs_args) + # This command iterates over all files we might have created + # and ensures they're labeled. It's like running + # chroot(ROOT_PATH) + fixfiles, except with a better name and + # semantics. + self._safeExecWithRedirect("ostree", ["admin", "--sysroot=" + ROOT_PATH, + "instutil", "selinux-ensure-labeled", ROOT_PATH, ""]) diff --git a/pyanaconda/users.py b/pyanaconda/users.py index 3c1c8da..70da4f0 100644 --- a/pyanaconda/users.py +++ b/pyanaconda/users.py @@ -209,6 +209,7 @@ class Users: import sys childpid = os.fork() + if not childpid: if not root in ["","/"]: os.chroot(root) @@ -242,7 +243,7 @@ class Users: Defaults to /mnt/sysimage. """ - childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH)) + childpid = self._prepareChroot(kwargs.get("root", iutil.getSysroot())) if childpid == 0: if self.admin.lookupGroupByName(group_name): log.error("Group %s already exists, not creating.", group_name) @@ -292,7 +293,7 @@ class Users: gid -- The GID for the new user. If none is given, the next available one is used. """ - childpid = self._prepareChroot(kwargs.get("root", ROOT_PATH)) + childpid = self._prepareChroot(kwargs.get("root", iutil.getSysroot())) if childpid == 0: if self.admin.lookupUserByName(user_name): log.error("User %s already exists, not creating.", user_name) @@ -397,8 +398,8 @@ class Users: else: return self._finishChroot(childpid) - def checkUserExists(self, username, root=ROOT_PATH): - childpid = self._prepareChroot(root) + def checkUserExists(self, username): + childpid = self._prepareChroot(iutil.getSysroot()) if childpid == 0: if self.admin.lookupUserByName(username): @@ -408,8 +409,8 @@ class Users: else: return self._finishChroot(childpid) - def setUserPassword(self, username, password, isCrypted, lock, algo=None, root=ROOT_PATH): - childpid = self._prepareChroot(root) + def setUserPassword(self, username, password, isCrypted, lock, algo=None): + childpid = self._prepareChroot(iutil.getSysroot()) if childpid == 0: user = self.admin.lookupUserByName(username) @@ -427,5 +428,5 @@ class Users: else: return self._finishChroot(childpid) - def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None, root=ROOT_PATH): - return self.setUserPassword("root", password, isCrypted, isLocked, algo, root) + def setRootPassword(self, password, isCrypted=False, isLocked=False, algo=None): + return self.setUserPassword("root", password, isCrypted, isLocked, algo) -- 1.8.3.1