aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorwiktor w brodlo <wiktor@brodlo.net>2011-06-15 16:59:54 +0000
committerwiktor w brodlo <wiktor@brodlo.net>2011-06-15 16:59:54 +0000
commit2590d96369d0217e31dc2812690dde61dac417b5 (patch)
tree82276f787b08a28548e342c7921486f1acefab9f /livecd.py
parentfirst commit (diff)
downloadanaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.gz
anaconda-2590d96369d0217e31dc2812690dde61dac417b5.tar.bz2
anaconda-2590d96369d0217e31dc2812690dde61dac417b5.zip
Initial import from Sabayon (ver 0.9.9.56)
Diffstat (limited to 'livecd.py')
-rw-r--r--livecd.py429
1 files changed, 429 insertions, 0 deletions
diff --git a/livecd.py b/livecd.py
new file mode 100644
index 0000000..1032121
--- /dev/null
+++ b/livecd.py
@@ -0,0 +1,429 @@
+#
+# livecd.py: An anaconda backend to do an install from a live CD image
+#
+# The basic idea is that with a live CD, we already have an install
+# and should be able to just copy those bits over to the disk. So we dd
+# the image, move things to the "right" filesystem as needed, and then
+# resize the rootfs to the size of its container.
+#
+# Copyright (C) 2007 Red Hat, Inc. All rights reserved.
+#
+# 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/>.
+#
+# Author(s): Jeremy Katz <katzj@redhat.com>
+#
+
+import os, sys
+import stat
+import shutil
+import time
+import subprocess
+import storage
+
+import selinux
+
+from flags import flags
+from constants import *
+
+import gettext
+_ = lambda x: gettext.ldgettext("anaconda", x)
+
+import backend
+import isys
+import iutil
+
+import packages
+
+import logging
+log = logging.getLogger("anaconda")
+
+class Error(EnvironmentError):
+ pass
+def copytree(src, dst, symlinks=False, preserveOwner=False,
+ preserveSelinux=False):
+ def tryChown(src, dest):
+ try:
+ os.chown(dest, os.stat(src)[stat.ST_UID], os.stat(src)[stat.ST_GID])
+ except OverflowError:
+ log.error("Could not set owner and group on file %s" % dest)
+
+ def trySetfilecon(src, dest):
+ try:
+ selinux.lsetfilecon(dest, selinux.lgetfilecon(src)[1])
+ except:
+ log.error("Could not set selinux context on file %s" % dest)
+
+ # copy of shutil.copytree which doesn't require dst to not exist
+ # and which also has options to preserve the owner and selinux contexts
+ names = os.listdir(src)
+ if not os.path.isdir(dst):
+ os.makedirs(dst)
+ errors = []
+ for name in names:
+ srcname = os.path.join(src, name)
+ dstname = os.path.join(dst, name)
+ try:
+ if symlinks and os.path.islink(srcname):
+ linkto = os.readlink(srcname)
+ os.symlink(linkto, dstname)
+ if preserveSelinux:
+ trySetfilecon(srcname, dstname)
+ elif os.path.isdir(srcname):
+ copytree(srcname, dstname, symlinks, preserveOwner, preserveSelinux)
+ else:
+ shutil.copyfile(srcname, dstname)
+ if preserveOwner:
+ tryChown(srcname, dstname)
+
+ if preserveSelinux:
+ trySetfilecon(srcname, dstname)
+
+ shutil.copystat(srcname, dstname)
+ except (IOError, os.error), why:
+ errors.append((srcname, dstname, str(why)))
+ # catch the Error from the recursive copytree so that we can
+ # continue with other files
+ except Error, err:
+ errors.extend(err.args[0])
+ try:
+ if preserveOwner:
+ tryChown(src, dst)
+ if preserveSelinux:
+ trySetfilecon(src, dst)
+
+ shutil.copystat(src, dst)
+ except OSError as e:
+ errors.extend((src, dst, e.strerror))
+ if errors:
+ raise Error, errors
+
+class LiveCDCopyBackend(backend.AnacondaBackend):
+ def __init__(self, anaconda):
+ backend.AnacondaBackend.__init__(self, anaconda)
+ flags.livecdInstall = True
+ self.supportsUpgrades = False
+ self.supportsPackageSelection = False
+ self.skipFormatRoot = True
+
+ self.osimg = anaconda.methodstr[8:]
+ if not stat.S_ISBLK(os.stat(self.osimg)[stat.ST_MODE]):
+ anaconda.intf.messageWindow(_("Unable to find image"),
+ _("The given location isn't a valid %s "
+ "live CD to use as an installation source.")
+ %(productName,), type = "custom",
+ custom_icon="error",
+ custom_buttons=[_("Exit installer")])
+ sys.exit(0)
+ self.rootFsType = isys.readFSType(self.osimg)
+
+ def _getLiveBlockDevice(self):
+ return os.path.normpath(self.osimg)
+
+ def _getLiveSize(self):
+ def parseField(output, field):
+ for line in output.split("\n"):
+ if line.startswith(field + ":"):
+ return line[len(field) + 1:].strip()
+ raise KeyError("Failed to find field '%s' in output" % field)
+
+ output = subprocess.Popen(['/sbin/dumpe2fs', '-h', self.osimg],
+ stdout=subprocess.PIPE,
+ stderr=open('/dev/null', 'w')
+ ).communicate()[0]
+ blkcnt = int(parseField(output, "Block count"))
+ blksize = int(parseField(output, "Block size"))
+ return blkcnt * blksize
+
+ def _getLiveSizeMB(self):
+ return self._getLiveSize() / 1048576
+
+ def _unmountNonFstabDirs(self, anaconda):
+ # unmount things that aren't listed in /etc/fstab. *sigh*
+ dirs = []
+ if flags.selinux:
+ dirs.append("/selinux")
+ for dir in dirs:
+ try:
+ isys.umount("%s/%s" %(anaconda.rootPath,dir), removeDir = False)
+ except Exception, e:
+ log.error("unable to unmount %s: %s" %(dir, e))
+
+ def postAction(self, anaconda):
+ self._unmountNonFstabDirs(anaconda)
+ try:
+ anaconda.storage.umountFilesystems(swapoff = False)
+ os.rmdir(anaconda.rootPath)
+ except Exception, e:
+ log.error("Unable to unmount filesystems: %s" % e)
+
+ def doPreInstall(self, anaconda):
+ if anaconda.dir == DISPATCH_BACK:
+ self._unmountNonFstabDirs(anaconda)
+ return
+ anaconda.storage.umountFilesystems(swapoff = False)
+
+ def doInstall(self, anaconda):
+ log.info("Preparing to install packages")
+
+ progress = anaconda.intf.instProgress
+ progress.set_label(_("Copying live image to hard drive."))
+ progress.processEvents()
+
+ osimg = self._getLiveBlockDevice() # the real image
+ osfd = os.open(osimg, os.O_RDONLY)
+
+ rootDevice = anaconda.storage.rootDevice
+ rootDevice.setup()
+ rootfd = os.open(rootDevice.path, os.O_WRONLY)
+
+ readamt = 1024 * 1024 * 8 # 8 megs at a time
+ size = self._getLiveSize()
+ copied = 0
+ while copied < size:
+ try:
+ buf = os.read(osfd, readamt)
+ written = os.write(rootfd, buf)
+ except:
+ rc = anaconda.intf.messageWindow(_("Error"),
+ _("There was an error installing the live image to "
+ "your hard drive. This could be due to bad media. "
+ "Please verify your installation media.\n\nIf you "
+ "exit, your system will be left in an inconsistent "
+ "state that will require reinstallation."),
+ type="custom", custom_icon="error",
+ custom_buttons=[_("_Exit installer"), _("_Retry")])
+
+ if rc == 0:
+ sys.exit(0)
+ else:
+ os.lseek(osfd, 0, 0)
+ os.lseek(rootfd, 0, 0)
+ copied = 0
+ continue
+
+ if (written < readamt) and (written < len(buf)):
+ raise RuntimeError, "error copying filesystem!"
+ copied += written
+ progress.set_fraction(pct = copied / float(size))
+ progress.processEvents()
+
+ os.close(osfd)
+ os.close(rootfd)
+
+ anaconda.intf.setInstallProgressClass(None)
+
+ def _doFilesystemMangling(self, anaconda):
+ # FIXME: this whole method is a big fucking mess
+ log.info("doing post-install fs mangling")
+ wait = anaconda.intf.waitWindow(_("Post-Installation"),
+ _("Performing post-installation filesystem changes. This may take several minutes."))
+
+ # resize rootfs first, since it is 100% full due to genMinInstDelta
+ self._resizeRootfs(anaconda, wait)
+
+ # remount filesystems
+ anaconda.storage.mountFilesystems()
+
+ # restore the label of / to what we think it is
+ rootDevice = anaconda.storage.rootDevice
+ rootDevice.setup()
+ # ensure we have a random UUID on the rootfs
+ # FIXME: this should be abstracted per filesystem type
+ iutil.execWithRedirect("tune2fs",
+ ["-U",
+ "random",
+ rootDevice.path],
+ stdout="/dev/tty5",
+ stderr="/dev/tty5")
+ # and now set the uuid in the storage layer
+ rootDevice.updateSysfsPath()
+ iutil.notify_kernel("/sys%s" %rootDevice.sysfsPath)
+ storage.udev.udev_settle()
+ rootDevice.updateSysfsPath()
+ info = storage.udev.udev_get_block_device(rootDevice.sysfsPath)
+ rootDevice.format.uuid = storage.udev.udev_device_get_uuid(info)
+ log.info("reset the rootdev (%s) to have a uuid of %s" %(rootDevice.sysfsPath, rootDevice.format.uuid))
+
+ # for any filesystem that's _not_ on the root, we need to handle
+ # moving the bits from the livecd -> the real filesystems.
+ # this is pretty distasteful, but should work with things like
+ # having a separate /usr/local
+
+ # now create a tree so that we know what's mounted under where
+ fsdict = {"/": []}
+ for mount in sorted(anaconda.storage.mountpoints.keys()):
+ entry = anaconda.storage.mountpoints[mount]
+ tocopy = entry.format.mountpoint
+ if tocopy.startswith("/mnt") or tocopy == "swap":
+ continue
+ keys = sorted(fsdict.keys(), reverse = True)
+ for key in keys:
+ if tocopy.startswith(key):
+ fsdict[key].append(entry)
+ break
+ fsdict[tocopy] = []
+ log.debug("mangling dict looks like %s" %(fsdict,))
+
+ # and now let's do the real copies; and we don't want to copy /!
+ copied = ["/"]
+ for tocopy in sorted(fsdict.keys()):
+ if tocopy in copied:
+ continue
+ copied.append(tocopy)
+ copied.extend(map(lambda x: x.format.mountpoint, fsdict[tocopy]))
+ entry = anaconda.storage.mountpoints[tocopy]
+
+ # FIXME: all calls to wait.refresh() are kind of a hack... we
+ # should do better about not doing blocking things in the
+ # main thread. but threading anaconda is a job for another
+ # time.
+ wait.refresh()
+
+ # unmount subdirs + this one and then remount under /mnt
+ for e in fsdict[tocopy] + [entry]:
+ e.format.teardown()
+ for e in [entry] + fsdict[tocopy]:
+ e.format.setup(chroot=anaconda.rootPath + "/mnt")
+
+ copytree("%s/%s" %(anaconda.rootPath, tocopy),
+ "%s/mnt/%s" %(anaconda.rootPath, tocopy), True, True,
+ flags.selinux)
+ shutil.rmtree("%s/%s" %(anaconda.rootPath, tocopy))
+ wait.refresh()
+
+ # mount it back in the correct place
+ for e in fsdict[tocopy] + [entry]:
+ e.format.teardown()
+ try:
+ os.rmdir("%s/mnt/%s" %(anaconda.rootPath,
+ e.format.mountpoint))
+ except OSError as e:
+ log.debug("error removing %s" %(tocopy,))
+ for e in [entry] + fsdict[tocopy]:
+ e.format.setup(chroot=anaconda.rootPath)
+
+ wait.refresh()
+
+ # ensure that non-fstab filesystems are mounted in the chroot
+ if flags.selinux:
+ try:
+ isys.mount("/selinux", anaconda.rootPath + "/selinux", "selinuxfs")
+ except Exception, e:
+ log.error("error mounting selinuxfs: %s" %(e,))
+
+ wait.pop()
+
+ def _resizeRootfs(self, anaconda, win = None):
+ log.info("going to do resize")
+ rootDevice = anaconda.storage.rootDevice
+
+ # FIXME: we'd like to have progress here to give an idea of
+ # how long it will take. or at least, to give an indefinite
+ # progress window. but, not for this time
+ cmd = ["resize2fs", rootDevice.path, "-p"]
+ out = open("/dev/tty5", "w")
+ proc = subprocess.Popen(cmd, stdout=out, stderr=out)
+ rc = proc.poll()
+ while rc is None:
+ win and win.refresh()
+ time.sleep(0.5)
+ rc = proc.poll()
+
+ if rc:
+ log.error("error running resize2fs; leaving filesystem as is")
+ return
+
+ # we should also do a fsck afterwards
+ cmd = ["e2fsck", "-f", "-y", rootDevice.path]
+ out = open("/dev/tty5", "w")
+ proc = subprocess.Popen(cmd, stdout=out, stderr=out)
+ rc = proc.poll()
+ while rc is None:
+ win and win.refresh()
+ time.sleep(0.5)
+ rc = proc.poll()
+
+ def doPostInstall(self, anaconda):
+ import rpm
+
+ self._doFilesystemMangling(anaconda)
+
+ storage.writeEscrowPackets(anaconda)
+
+ packages.rpmSetupGraphicalSystem(anaconda)
+
+ # now write out the "real" fstab and mtab
+ anaconda.storage.write(anaconda.rootPath)
+ f = open(anaconda.rootPath + "/etc/mtab", "w+")
+ f.write(anaconda.storage.mtab)
+ f.close()
+
+ # copy over the modprobe.conf
+ if os.path.exists("/etc/modprobe.conf"):
+ shutil.copyfile("/etc/modprobe.conf",
+ anaconda.rootPath + "/etc/modprobe.conf")
+
+ # rebuild the initrd(s)
+ vers = self.kernelVersionList(anaconda.rootPath)
+ for (n, arch, tag) in vers:
+ packages.recreateInitrd(n, anaconda.rootPath)
+
+ def writeConfiguration(self):
+ pass
+
+ def kernelVersionList(self, rootPath = "/"):
+ return packages.rpmKernelVersionList(rootPath)
+
+ def getMinimumSizeMB(self, part):
+ if part == "/":
+ return self._getLiveSizeMB()
+ return 0
+
+ def doBackendSetup(self, anaconda):
+ # ensure there's enough space on the rootfs
+ # FIXME: really, this should be in the general sanity checking, but
+ # trying to weave that in is a little tricky at present.
+ ossize = self._getLiveSizeMB()
+ slash = anaconda.storage.rootDevice
+ if slash.size < ossize:
+ rc = anaconda.intf.messageWindow(_("Error"),
+ _("The root filesystem you created is "
+ "not large enough for this live "
+ "image (%.2f MB required).") % ossize,
+ type = "custom",
+ custom_icon = "error",
+ custom_buttons=[_("_Back"),
+ _("_Exit installer")])
+ if rc == 0:
+ return DISPATCH_BACK
+ else:
+ sys.exit(1)
+
+ # package/group selection doesn't apply for this backend
+ def groupExists(self, group):
+ pass
+ def selectGroup(self, group, *args):
+ pass
+ def deselectGroup(self, group, *args):
+ pass
+ def selectPackage(self, pkg, *args):
+ pass
+ def deselectPackage(self, pkg, *args):
+ pass
+ def packageExists(self, pkg):
+ return True
+ def getDefaultGroups(self, anaconda):
+ return []
+ def writePackagesKS(self, f, anaconda):
+ pass