Package pyanaconda :: Module livecd
[hide private]
[frames] | no frames]

Source Code for Module pyanaconda.livecd

  1  # 
  2  # livecd.py: An anaconda backend to do an install from a live CD image 
  3  # 
  4  # The basic idea is that with a live CD, we already have an install 
  5  # and should be able to just copy those bits over to the disk.  So we dd 
  6  # the image, move things to the "right" filesystem as needed, and then 
  7  # resize the rootfs to the size of its container. 
  8  # 
  9  # Copyright (C) 2007  Red Hat, Inc.  All rights reserved. 
 10  # 
 11  # This program is free software; you can redistribute it and/or modify 
 12  # it under the terms of the GNU General Public License as published by 
 13  # the Free Software Foundation; either version 2 of the License, or 
 14  # (at your option) any later version. 
 15  # 
 16  # This program is distributed in the hope that it will be useful, 
 17  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 18  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 19  # GNU General Public License for more details. 
 20  # 
 21  # You should have received a copy of the GNU General Public License 
 22  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 23  # 
 24  # Author(s): Jeremy Katz <katzj@redhat.com> 
 25  # 
 26   
 27  import os, sys 
 28  import stat 
 29  import shutil 
 30  import time 
 31  import subprocess 
 32  import storage 
 33   
 34  import selinux 
 35   
 36  from flags import flags 
 37  from constants import * 
 38   
 39  import gettext 
 40  _ = lambda x: gettext.ldgettext("anaconda", x) 
 41   
 42  import backend 
 43  import isys 
 44  import iutil 
 45   
 46  import packages 
 47   
 48  import logging 
 49  log = logging.getLogger("anaconda") 
 50   
51 -class Error(EnvironmentError):
52 pass
53 -def copytree(src, dst, symlinks=False, preserveOwner=False, 54 preserveSelinux=False):
55 def tryChown(src, dest): 56 try: 57 os.chown(dest, os.stat(src)[stat.ST_UID], os.stat(src)[stat.ST_GID]) 58 except OverflowError: 59 log.error("Could not set owner and group on file %s" % dest)
60 61 def trySetfilecon(src, dest): 62 try: 63 selinux.lsetfilecon(dest, selinux.lgetfilecon(src)[1]) 64 except OSError: 65 log.error("Could not set selinux context on file %s" % dest) 66 67 # copy of shutil.copytree which doesn't require dst to not exist 68 # and which also has options to preserve the owner and selinux contexts 69 names = os.listdir(src) 70 if not os.path.isdir(dst): 71 os.makedirs(dst) 72 errors = [] 73 for name in names: 74 srcname = os.path.join(src, name) 75 dstname = os.path.join(dst, name) 76 try: 77 if symlinks and os.path.islink(srcname): 78 linkto = os.readlink(srcname) 79 os.symlink(linkto, dstname) 80 if preserveSelinux: 81 trySetfilecon(srcname, dstname) 82 elif os.path.isdir(srcname): 83 copytree(srcname, dstname, symlinks, preserveOwner, preserveSelinux) 84 else: 85 shutil.copyfile(srcname, dstname) 86 if preserveOwner: 87 tryChown(srcname, dstname) 88 89 if preserveSelinux: 90 trySetfilecon(srcname, dstname) 91 92 shutil.copystat(srcname, dstname) 93 except (IOError, OSError) as why: 94 errors.append((srcname, dstname, str(why))) 95 # catch the Error from the recursive copytree so that we can 96 # continue with other files 97 except Error as err: 98 errors.extend(err.args[0]) 99 try: 100 if preserveOwner: 101 tryChown(src, dst) 102 if preserveSelinux: 103 trySetfilecon(src, dst) 104 105 shutil.copystat(src, dst) 106 except OSError as e: 107 errors.extend((src, dst, e.strerror)) 108 if errors: 109 raise Error, errors 110
111 -class LiveCDCopyBackend(backend.AnacondaBackend):
112 - def __init__(self, anaconda):
113 backend.AnacondaBackend.__init__(self, anaconda) 114 flags.livecdInstall = True 115 self.supportsUpgrades = False 116 self.supportsPackageSelection = False 117 self.skipFormatRoot = True 118 119 osimg_path = anaconda.methodstr[9:] 120 if not stat.S_ISBLK(os.stat(osimg_path)[stat.ST_MODE]): 121 anaconda.intf.messageWindow(_("Unable to find image"), 122 _("The given location isn't a valid %s " 123 "live CD to use as an installation source.") 124 %(productName,), type = "custom", 125 custom_icon="error", 126 custom_buttons=[_("Exit installer")]) 127 sys.exit(0)
128
129 - def postAction(self, anaconda):
130 try: 131 anaconda.storage.umountFilesystems(swapoff = False) 132 os.rmdir(ROOT_PATH) 133 except Exception as e: 134 log.error("Unable to unmount filesystems: %s" % e)
135
136 - def doPreInstall(self, anaconda):
137 anaconda.storage.umountFilesystems(swapoff = False)
138
139 - def doInstall(self, anaconda):
140 log.info("Preparing to install packages") 141 142 progress = anaconda.intf.instProgress 143 progress.set_label(_("Copying live image to hard drive.")) 144 progress.processEvents() 145 146 osfd = os.open(self.anaconda.storage.liveImage.path, os.O_RDONLY) 147 148 rootDevice = anaconda.storage.rootDevice 149 rootDevice.setup() 150 rootfd = os.open(rootDevice.path, os.O_WRONLY) 151 152 readamt = 1024 * 1024 * 8 # 8 megs at a time 153 size = self.anaconda.storage.liveImage.format.currentSize * 1024 * 1024 154 copied = 0 155 done = False 156 while not done: 157 try: 158 buf = os.read(osfd, readamt) 159 written = os.write(rootfd, buf) 160 except (IOError, OSError): 161 rc = anaconda.intf.messageWindow(_("Error"), 162 _("There was an error installing the live image to " 163 "your hard drive. This could be due to bad media. " 164 "Please verify your installation media.\n\nIf you " 165 "exit, your system will be left in an inconsistent " 166 "state that will require reinstallation."), 167 type="custom", custom_icon="error", 168 custom_buttons=[_("_Exit installer"), _("_Retry")]) 169 170 if rc == 0: 171 sys.exit(0) 172 else: 173 os.lseek(osfd, 0, 0) 174 os.lseek(rootfd, 0, 0) 175 copied = 0 176 continue 177 178 if (written < readamt): 179 # Either something went wrong with the write 180 if (written < len(buf)): 181 raise RuntimeError, "error copying filesystem!" 182 else: 183 # Or we're done 184 done = True 185 copied += written 186 progress.set_fraction(pct = copied / float(size)) 187 progress.processEvents() 188 189 os.close(osfd) 190 os.close(rootfd) 191 192 anaconda.intf.setInstallProgressClass(None)
193
194 - def _doFilesystemMangling(self, anaconda):
195 log.info("doing post-install fs mangling") 196 wait = anaconda.intf.waitWindow(_("Post-Installation"), 197 _("Performing post-installation filesystem changes. This may take several minutes.")) 198 199 # resize rootfs first, since it is 100% full due to genMinInstDelta 200 rootDevice = anaconda.storage.rootDevice 201 rootDevice.setup() 202 rootDevice.format.targetSize = rootDevice.size 203 rootDevice.format.doResize(intf=anaconda.intf) 204 205 # ensure we have a random UUID on the rootfs 206 rootDevice.format.writeRandomUUID() 207 208 # remount filesystems 209 anaconda.storage.mountFilesystems() 210 211 # and now set the uuid in the storage layer 212 rootDevice.updateSysfsPath() 213 iutil.notify_kernel("/sys%s" %rootDevice.sysfsPath) 214 storage.udev.udev_settle() 215 rootDevice.updateSysfsPath() 216 info = storage.udev.udev_get_block_device(rootDevice.sysfsPath) 217 rootDevice.format.uuid = storage.udev.udev_device_get_uuid(info) 218 log.info("reset the rootdev (%s) to have a uuid of %s" %(rootDevice.sysfsPath, rootDevice.format.uuid)) 219 220 # for any filesystem that's _not_ on the root, we need to handle 221 # moving the bits from the livecd -> the real filesystems. 222 # this is pretty distasteful, but should work with things like 223 # having a separate /usr/local 224 225 def _setupFilesystems(mounts, chroot="", teardown=False): 226 """ Setup or teardown all filesystems except for "/" """ 227 mountpoints = sorted(mounts.keys(), 228 reverse=teardown is True) 229 if teardown: 230 method = "teardown" 231 kwargs = {} 232 else: 233 method = "setup" 234 kwargs = {"chroot": chroot} 235 236 mountpoints.remove("/") 237 for mountpoint in mountpoints: 238 device = mounts[mountpoint] 239 getattr(device.format, method)(**kwargs)
240 241 # Start by sorting the mountpoints in decreasing-depth order. 242 # Only include ones that exist on the original livecd filesystem 243 mountpoints = filter(os.path.exists, 244 sorted(anaconda.storage.mountpoints.keys(), 245 reverse=True)) 246 # We don't want to copy the root filesystem. 247 mountpoints.remove("/") 248 stats = {} # mountpoint: posix.stat_result 249 250 # unmount the filesystems, except for / 251 _setupFilesystems(anaconda.storage.mountpoints, teardown=True) 252 253 # mount all of the filesystems under /mnt so we can copy in content 254 _setupFilesystems(anaconda.storage.mountpoints, 255 chroot="/mnt") 256 257 # And now let's do the real copies 258 for tocopy in mountpoints: 259 device = anaconda.storage.mountpoints[tocopy] 260 source = "%s/%s" % (ROOT_PATH, tocopy) 261 dest = "/mnt/%s" % (tocopy,) 262 263 # FIXME: all calls to wait.refresh() are kind of a hack... we 264 # should do better about not doing blocking things in the 265 # main thread. but threading anaconda is a job for another 266 # time. 267 wait.refresh() 268 269 try: 270 log.info("Gathering stats on %s" % (source,)) 271 stats[tocopy]= os.stat(source) 272 except Exception as e: 273 log.info("failed to get stat info for mountpoint %s: %s" 274 % (source, e)) 275 276 log.info("Copying %s to %s" % (source, dest)) 277 copytree(source, dest, True, True, flags.selinux) 278 wait.refresh() 279 280 log.info("Removing %s" % (source,)) 281 shutil.rmtree(source) 282 wait.refresh() 283 284 # unmount the target filesystems and remount in their final locations 285 # so that post-install writes end up where they're supposed to end up 286 _setupFilesystems(anaconda.storage.mountpoints, teardown=True) 287 _setupFilesystems(anaconda.storage.mountpoints, 288 chroot=ROOT_PATH) 289 290 # restore stat info for each mountpoint 291 for mountpoint in reversed(mountpoints): 292 dest = "%s/%s" % (ROOT_PATH, mountpoint) 293 log.info("Restoring stats on %s" % (dest,)) 294 st = stats[mountpoint] 295 296 # restore the correct stat info for this mountpoint 297 os.utime(dest, (st.st_atime, st.st_mtime)) 298 os.chown(dest, st.st_uid, st.st_gid) 299 os.chmod(dest, stat.S_IMODE(st.st_mode)) 300 301 wait.pop()
302
303 - def doPostInstall(self, anaconda):
304 import rpm 305 306 self._doFilesystemMangling(anaconda) 307 308 storage.writeEscrowPackets(anaconda) 309 310 packages.rpmSetupGraphicalSystem(anaconda) 311 312 # now write out the "real" fstab and mtab 313 anaconda.storage.write() 314 315 # copy over the modprobe.conf 316 if os.path.exists("/etc/modprobe.conf"): 317 shutil.copyfile("/etc/modprobe.conf", 318 ROOT_PATH + "/etc/modprobe.conf") 319 # set the same keyboard the user selected in the keyboard dialog: 320 anaconda.keyboard.write(ROOT_PATH) 321 322 # rebuild the initrd(s) 323 vers = self.kernelVersionList() 324 for (n, arch, tag) in vers: 325 packages.recreateInitrd(n, ROOT_PATH)
326
327 - def kernelVersionList(self):
328 return packages.rpmKernelVersionList()
329
330 - def getMinimumSizeMB(self, part):
331 if part == "/": 332 return self.anaconda.storage.liveImage.format.size 333 return 0
334