Ignore case when looking for UEFI boot*efi file (#1156380)
[livecd.git] / imgcreate / creator.py
bloba06f3df98a5f9683a4f1b3710285f5134d7e6caf
2 # creator.py : ImageCreator and LoopImageCreator base classes
4 # Copyright 2007, Red Hat Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 import os
20 import os.path
21 import stat
22 import sys
23 import tempfile
24 import shutil
25 import logging
26 import subprocess
28 import selinux
29 import yum
30 import rpm
32 from imgcreate.errors import *
33 from imgcreate.fs import *
34 from imgcreate.yuminst import *
35 from imgcreate import kickstart
37 FSLABEL_MAXLEN = 32
38 """The maximum string length supported for LoopImageCreator.fslabel."""
40 class ImageCreator(object):
41 """Installs a system to a chroot directory.
43 ImageCreator is the simplest creator class available; it will install and
44 configure a system image according to the supplied kickstart file.
46 e.g.
48 import imgcreate
49 ks = imgcreate.read_kickstart("foo.ks")
50 imgcreate.ImageCreator(ks, "foo").create()
52 """
54 def __init__(self, ks, name, releasever=None, tmpdir="/tmp", useplugins=False,
55 cacheonly=False, docleanup=True):
56 """Initialize an ImageCreator instance.
58 ks -- a pykickstart.KickstartParser instance; this instance will be
59 used to drive the install by e.g. providing the list of packages
60 to be installed, the system configuration and %post scripts
62 name -- a name for the image; used for e.g. image filenames or
63 filesystem labels
65 releasever -- Value to substitute for $releasever in repo urls
67 tmpdir -- Top level directory to use for temporary files and dirs
69 cacheonly -- Only read from cache, work offline
70 """
71 self.ks = ks
72 """A pykickstart.KickstartParser instance."""
74 self.name = name
75 """A name for the image."""
77 self.releasever = releasever
78 self.useplugins = useplugins
80 self.tmpdir = tmpdir
81 """The directory in which all temporary files will be created."""
82 if not os.path.exists(self.tmpdir):
83 makedirs(self.tmpdir)
85 self.cacheonly = cacheonly
86 self.docleanup = docleanup
88 self.__builddir = None
89 self.__bindmounts = []
90 self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
92 self.__sanity_check()
94 # get selinuxfs mountpoint
95 self.__selinux_mountpoint = "/sys/fs/selinux"
96 with open("/proc/self/mountinfo", "r") as f:
97 for line in f.readlines():
98 fields = line.split()
99 if fields[-2] == "selinuxfs":
100 self.__selinux_mountpoint = fields[4]
101 break
103 def __del__(self):
104 self.cleanup()
107 # Properties
109 def __get_instroot(self):
110 if self.__builddir is None:
111 raise CreatorError("_instroot is not valid before calling mount()")
112 return self.__builddir + "/install_root"
113 _instroot = property(__get_instroot)
114 """The location of the install root directory.
116 This is the directory into which the system is installed. Subclasses may
117 mount a filesystem image here or copy files to/from here.
119 Note, this directory does not exist before ImageCreator.mount() is called.
121 Note also, this is a read-only attribute.
125 def __get_outdir(self):
126 if self.__builddir is None:
127 raise CreatorError("_outdir is not valid before calling mount()")
128 return self.__builddir + "/out"
129 _outdir = property(__get_outdir)
130 """The staging location for the final image.
132 This is where subclasses should stage any files that are part of the final
133 image. ImageCreator.package() will copy any files found here into the
134 requested destination directory.
136 Note, this directory does not exist before ImageCreator.mount() is called.
138 Note also, this is a read-only attribute.
142 def __get_fstype(self):
143 return self.__fstype
144 def __set_fstype(self, val):
145 if val not in ("ext2", "ext3", "ext4"):
146 raise CreatorError("Unknown _fstype '%s' supplied" % val)
147 self.__fstype = val
148 _fstype = property(__get_fstype, __set_fstype)
149 """The type of filesystem used for the image.
151 This is the filesystem type used when creating the filesystem image.
152 Subclasses may change this if they wish to use something other ext3.
154 Note, only ext2, ext3, ext4 are currently supported.
156 Note also, this attribute may only be set before calling mount().
161 # Hooks for subclasses
163 def _mount_instroot(self, base_on = None):
164 """Mount or prepare the install root directory.
166 This is the hook where subclasses may prepare the install root by e.g.
167 mounting creating and loopback mounting a filesystem image to
168 _instroot.
170 There is no default implementation.
172 base_on -- this is the value passed to mount() and can be interpreted
173 as the subclass wishes; it might e.g. be the location of
174 a previously created ISO containing a system image.
177 pass
179 def _unmount_instroot(self):
180 """Undo anything performed in _mount_instroot().
182 This is the hook where subclasses must undo anything which was done
183 in _mount_instroot(). For example, if a filesystem image was mounted
184 onto _instroot, it should be unmounted here.
186 There is no default implementation.
189 pass
191 def _create_bootconfig(self):
192 """Configure the image so that it's bootable.
194 This is the hook where subclasses may prepare the image for booting by
195 e.g. creating an initramfs and bootloader configuration.
197 This hook is called while the install root is still mounted, after the
198 packages have been installed and the kickstart configuration has been
199 applied, but before the %post scripts have been executed.
201 There is no default implementation.
204 pass
206 def _stage_final_image(self):
207 """Stage the final system image in _outdir.
209 This is the hook where subclasses should place the image in _outdir
210 so that package() can copy it to the requested destination directory.
212 By default, this moves the install root into _outdir.
215 shutil.move(self._instroot, self._outdir + "/" + self.name)
217 def _get_required_packages(self):
218 """Return a list of required packages.
220 This is the hook where subclasses may specify a set of packages which
221 it requires to be installed.
223 This returns an empty list by default.
225 Note, subclasses should usually chain up to the base class
226 implementation of this hook.
229 return []
231 def _get_excluded_packages(self):
232 """Return a list of excluded packages.
234 This is the hook where subclasses may specify a set of packages which
235 it requires _not_ to be installed.
237 This returns an empty list by default.
239 Note, subclasses should usually chain up to the base class
240 implementation of this hook.
243 return []
245 def _get_fstab(self):
246 """Return the desired contents of /etc/fstab.
248 This is the hook where subclasses may specify the contents of
249 /etc/fstab by returning a string containing the desired contents.
251 A sensible default implementation is provided.
254 s = "/dev/root / %s defaults,noatime 0 0\n" %(self._fstype)
255 s += self._get_fstab_special()
256 return s
258 def _get_fstab_special(self):
259 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
260 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
261 s += "proc /proc proc defaults 0 0\n"
262 s += "sysfs /sys sysfs defaults 0 0\n"
263 return s
265 def _get_post_scripts_env(self, in_chroot):
266 """Return an environment dict for %post scripts.
268 This is the hook where subclasses may specify some environment
269 variables for %post scripts by return a dict containing the desired
270 environment.
272 By default, this returns an empty dict.
274 in_chroot -- whether this %post script is to be executed chroot()ed
275 into _instroot.
278 return {}
280 def _get_kernel_versions(self):
281 """Return a dict detailing the available kernel types/versions.
283 This is the hook where subclasses may override what kernel types and
284 versions should be available for e.g. creating the booloader
285 configuration.
287 A dict should be returned mapping the available kernel types to a list
288 of the available versions for those kernels.
290 The default implementation uses rpm to iterate over everything
291 providing 'kernel', finds /boot/vmlinuz-* and returns the version
292 obtained from the vmlinuz filename. (This can differ from the kernel
293 RPM's n-v-r in the case of e.g. xen)
296 def get_version(header):
297 version = None
298 for f in header['filenames']:
299 if f.startswith('/boot/vmlinuz-'):
300 version = f[14:]
301 return version
303 ts = rpm.TransactionSet(self._instroot)
305 ret = {}
306 for header in ts.dbMatch('provides', 'kernel'):
307 version = get_version(header)
308 if version is None:
309 continue
311 name = header['name']
312 if not name in ret:
313 ret[name] = [version]
314 elif not version in ret[name]:
315 ret[name].append(version)
317 return ret
320 # Helpers for subclasses
322 def _do_bindmounts(self):
323 """Mount various system directories onto _instroot.
325 This method is called by mount(), but may also be used by subclasses
326 in order to re-mount the bindmounts after modifying the underlying
327 filesystem.
330 for b in self.__bindmounts:
331 b.mount()
333 def _undo_bindmounts(self):
334 """Unmount the bind-mounted system directories from _instroot.
336 This method is usually only called by unmount(), but may also be used
337 by subclasses in order to gain access to the filesystem obscured by
338 the bindmounts - e.g. in order to create device nodes on the image
339 filesystem.
342 self.__bindmounts.reverse()
343 for b in self.__bindmounts:
344 b.unmount()
346 def _chroot(self):
347 """Chroot into the install root.
349 This method may be used by subclasses when executing programs inside
350 the install root e.g.
352 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
355 os.chroot(self._instroot)
356 os.chdir("/")
358 def _mkdtemp(self, prefix = "tmp-"):
359 """Create a temporary directory.
361 This method may be used by subclasses to create a temporary directory
362 for use in building the final image - e.g. a subclass might create
363 a temporary directory in order to bundle a set of files into a package.
365 The subclass may delete this directory if it wishes, but it will be
366 automatically deleted by cleanup().
368 The absolute path to the temporary directory is returned.
370 Note, this method should only be called after mount() has been called.
372 prefix -- a prefix which should be used when creating the directory;
373 defaults to "tmp-".
376 self.__ensure_builddir()
377 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
379 def _mkstemp(self, prefix = "tmp-"):
380 """Create a temporary file.
382 This method may be used by subclasses to create a temporary file
383 for use in building the final image - e.g. a subclass might need
384 a temporary location to unpack a compressed file.
386 The subclass may delete this file if it wishes, but it will be
387 automatically deleted by cleanup().
389 A tuple containing a file descriptor (returned from os.open() and the
390 absolute path to the temporary directory is returned.
392 Note, this method should only be called after mount() has been called.
394 prefix -- a prefix which should be used when creating the file;
395 defaults to "tmp-".
398 self.__ensure_builddir()
399 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
401 def _mktemp(self, prefix = "tmp-"):
402 """Create a temporary file.
404 This method simply calls _mkstemp() and closes the returned file
405 descriptor.
407 The absolute path to the temporary file is returned.
409 Note, this method should only be called after mount() has been called.
411 prefix -- a prefix which should be used when creating the file;
412 defaults to "tmp-".
416 (f, path) = self._mkstemp(prefix)
417 os.close(f)
418 return path
421 # Actual implementation
423 def __ensure_builddir(self):
424 if not self.__builddir is None:
425 return
427 try:
428 self.__builddir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir),
429 prefix = "imgcreate-")
430 except OSError, e:
431 raise CreatorError("Failed create build directory in %s: %s" %
432 (self.tmpdir, e.strerror))
434 def __sanity_check(self):
435 """Ensure that the config we've been given is sane."""
436 if not (kickstart.get_packages(self.ks) or
437 kickstart.get_groups(self.ks)):
438 raise CreatorError("No packages or groups specified")
440 kickstart.convert_method_to_repo(self.ks)
442 if not kickstart.get_repos(self.ks):
443 raise CreatorError("No repositories specified")
445 def __write_fstab(self):
446 fstab = open(self._instroot + "/etc/fstab", "w")
447 fstab.write(self._get_fstab())
448 fstab.close()
450 def __create_minimal_dev(self):
451 """Create a minimal /dev so that we don't corrupt the host /dev"""
452 origumask = os.umask(0000)
453 devices = (('null', 1, 3, 0666),
454 ('urandom',1, 9, 0666),
455 ('random', 1, 8, 0666),
456 ('full', 1, 7, 0666),
457 ('ptmx', 5, 2, 0666),
458 ('tty', 5, 0, 0666),
459 ('zero', 1, 5, 0666))
460 links = (("/proc/self/fd", "/dev/fd"),
461 ("/proc/self/fd/0", "/dev/stdin"),
462 ("/proc/self/fd/1", "/dev/stdout"),
463 ("/proc/self/fd/2", "/dev/stderr"))
465 for (node, major, minor, perm) in devices:
466 if not os.path.exists(self._instroot + "/dev/" + node):
467 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
468 for (src, dest) in links:
469 if not os.path.exists(self._instroot + dest):
470 os.symlink(src, self._instroot + dest)
471 os.umask(origumask)
473 def __create_selinuxfs(self):
474 if not os.path.exists(self.__selinux_mountpoint):
475 return
477 arglist = ["/bin/mount", "--bind", "/dev/null", self._instroot + self.__selinux_mountpoint + "/load"]
478 subprocess.call(arglist, close_fds = True)
480 if kickstart.selinux_enabled(self.ks):
481 # label the fs like it is a root before the bind mounting
482 arglist = ["/sbin/setfiles", "-F", "-r", self._instroot, selinux.selinux_file_context_path(), self._instroot]
483 subprocess.call(arglist, close_fds = True)
484 # these dumb things don't get magically fixed, so make the user generic
485 # if selinux exists on the host we need to lie to the chroot
486 if selinux.is_selinux_enabled():
487 for f in ("/proc", "/sys"):
488 arglist = ["/usr/bin/chcon", "-u", "system_u", self._instroot + f]
489 subprocess.call(arglist, close_fds = True)
491 def __destroy_selinuxfs(self):
492 if not os.path.exists(self.__selinux_mountpoint):
493 return
495 # if the system was running selinux clean up our lies
496 path = self._instroot + self.__selinux_mountpoint + "/load"
497 if os.path.exists(path):
498 arglist = ["/bin/umount", path]
499 subprocess.call(arglist, close_fds = True)
501 def mount(self, base_on = None, cachedir = None):
502 """Setup the target filesystem in preparation for an install.
504 This function sets up the filesystem which the ImageCreator will
505 install into and configure. The ImageCreator class merely creates an
506 install root directory, bind mounts some system directories (e.g. /dev)
507 and writes out /etc/fstab. Other subclasses may also e.g. create a
508 sparse file, format it and loopback mount it to the install root.
510 base_on -- a previous install on which to base this install; defaults
511 to None, causing a new image to be created
513 cachedir -- a directory in which to store the Yum cache; defaults to
514 None, causing a new cache to be created; by setting this
515 to another directory, the same cache can be reused across
516 multiple installs.
519 self.__ensure_builddir()
521 makedirs(self._instroot)
522 makedirs(self._outdir)
524 self._mount_instroot(base_on)
526 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc"):
527 makedirs(self._instroot + d)
529 cachesrc = cachedir or (self.__builddir + "/yum-cache")
530 makedirs(cachesrc)
532 # bind mount system directories into _instroot
533 for (f, dest) in [("/sys", None), ("/proc", None),
534 ("/dev/pts", None), ("/dev/shm", None),
535 (self.__selinux_mountpoint, self.__selinux_mountpoint),
536 (cachesrc, "/var/cache/yum")]:
537 if os.path.exists(f):
538 self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))
539 else:
540 logging.warn("Skipping (%s,%s) because source doesn't exist." % (f, dest))
542 self._do_bindmounts()
544 self.__create_selinuxfs()
546 self.__create_minimal_dev()
548 os.symlink("/proc/self/mounts", self._instroot + "/etc/mtab")
550 self.__write_fstab()
552 def unmount(self):
553 """Unmounts the target filesystem.
555 The ImageCreator class detaches the system from the install root, but
556 other subclasses may also detach the loopback mounted filesystem image
557 from the install root.
560 self.__destroy_selinuxfs()
562 self._undo_bindmounts()
564 self._unmount_instroot()
566 def cleanup(self):
567 """Unmounts the target filesystem and deletes temporary files.
569 This method calls unmount() and then deletes any temporary files and
570 directories that were created on the host system while building the
571 image.
573 Note, make sure to call this method once finished with the creator
574 instance in order to ensure no stale files are left on the host e.g.:
576 creator = ImageCreator(ks, name)
577 try:
578 creator.create()
579 finally:
580 creator.cleanup()
583 if not self.docleanup:
584 logging.warn("Skipping cleanup of temporary files")
585 return
587 if not self.__builddir:
588 return
590 self.unmount()
592 shutil.rmtree(self.__builddir, ignore_errors = True)
593 self.__builddir = None
595 def __select_packages(self, ayum):
596 skipped_pkgs = []
597 for pkg in kickstart.get_packages(self.ks,
598 self._get_required_packages()):
599 try:
600 ayum.selectPackage(pkg)
601 except yum.Errors.InstallError, e:
602 if kickstart.ignore_missing(self.ks):
603 skipped_pkgs.append(pkg)
604 else:
605 raise CreatorError("Failed to find package '%s' : %s" %
606 (pkg, e))
608 for pkg in skipped_pkgs:
609 logging.warn("Skipping missing package '%s'" % (pkg,))
611 def __select_groups(self, ayum):
612 skipped_groups = []
613 for group in kickstart.get_groups(self.ks):
614 try:
615 ayum.selectGroup(group.name, group.include)
616 except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
617 if kickstart.ignore_missing(self.ks):
618 raise CreatorError("Failed to find group '%s' : %s" %
619 (group.name, e))
620 else:
621 skipped_groups.append(group)
623 for group in skipped_groups:
624 logging.warn("Skipping missing group '%s'" % (group.name,))
626 def __deselect_packages(self, ayum):
627 for pkg in kickstart.get_excluded(self.ks,
628 self._get_excluded_packages()):
629 ayum.deselectPackage(pkg)
631 def install(self, repo_urls = {}):
632 """Install packages into the install root.
634 This function installs the packages listed in the supplied kickstart
635 into the install root. By default, the packages are installed from the
636 repository URLs specified in the kickstart.
638 repo_urls -- a dict which maps a repository name to a repository URL;
639 if supplied, this causes any repository URLs specified in
640 the kickstart to be overridden.
643 yum_conf = self._mktemp(prefix = "yum.conf-")
645 ayum = LiveCDYum(releasever=self.releasever, useplugins=self.useplugins)
646 ayum.setup(yum_conf, self._instroot, cacheonly=self.cacheonly)
648 for repo in kickstart.get_repos(self.ks, repo_urls):
649 (name, baseurl, mirrorlist, proxy, inc, exc, cost, sslverify) = repo
651 yr = ayum.addRepository(name, baseurl, mirrorlist)
652 if inc:
653 yr.includepkgs = inc
654 if exc:
655 yr.exclude = exc
656 if proxy:
657 yr.proxy = proxy
658 if cost is not None:
659 yr.cost = cost
660 yr.sslverify = sslverify
661 ayum.setup(yum_conf, self._instroot)
663 if kickstart.exclude_docs(self.ks):
664 rpm.addMacro("_excludedocs", "1")
665 if not kickstart.selinux_enabled(self.ks):
666 rpm.addMacro("__file_context_path", "%{nil}")
667 if kickstart.inst_langs(self.ks) != None:
668 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
670 try:
671 self.__select_packages(ayum)
672 self.__select_groups(ayum)
673 self.__deselect_packages(ayum)
675 ayum.runInstall()
676 except yum.Errors.RepoError, e:
677 raise CreatorError("Unable to download from repo : %s" % (e,))
678 except yum.Errors.YumBaseError, e:
679 raise CreatorError("Unable to install: %s" % (e,))
680 finally:
681 ayum.closeRpmDB()
682 ayum.close()
683 os.unlink(yum_conf)
685 # do some clean up to avoid lvm info leakage. this sucks.
686 for subdir in ("cache", "backup", "archive"):
687 lvmdir = self._instroot + "/etc/lvm/" + subdir
688 try:
689 for f in os.listdir(lvmdir):
690 os.unlink(lvmdir + "/" + f)
691 except:
692 pass
694 def _run_post_scripts(self):
695 for s in kickstart.get_post_scripts(self.ks):
696 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
697 dir = self._instroot + "/tmp")
699 os.write(fd, s.script)
700 os.close(fd)
701 os.chmod(path, 0700)
703 env = self._get_post_scripts_env(s.inChroot)
705 if not s.inChroot:
706 env["INSTALL_ROOT"] = self._instroot
707 preexec = None
708 script = path
709 else:
710 preexec = self._chroot
711 script = "/tmp/" + os.path.basename(path)
713 try:
714 subprocess.check_call([s.interp, script],
715 preexec_fn = preexec, env = env)
716 except OSError, e:
717 raise CreatorError("Failed to execute %%post script "
718 "with '%s' : %s" % (s.interp, e.strerror))
719 except subprocess.CalledProcessError, err:
720 if s.errorOnFail:
721 raise CreatorError("%%post script failed with code %d "
722 % err.returncode)
723 logging.warning("ignoring %%post failure (code %d)"
724 % err.returncode)
725 finally:
726 os.unlink(path)
728 def configure(self):
729 """Configure the system image according to the kickstart.
731 This method applies the (e.g. keyboard or network) configuration
732 specified in the kickstart and executes the kickstart %post scripts.
734 If neccessary, it also prepares the image to be bootable by e.g.
735 creating an initrd and bootloader configuration.
738 ksh = self.ks.handler
740 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
741 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
742 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
743 kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
744 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
745 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
746 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
747 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
748 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
749 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
751 self._create_bootconfig()
753 self._run_post_scripts()
754 kickstart.SelinuxConfig(self._instroot).apply(ksh.selinux)
756 def launch_shell(self):
757 """Launch a shell in the install root.
759 This method is launches a bash shell chroot()ed in the install root;
760 this can be useful for debugging.
763 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
765 def package(self, destdir = "."):
766 """Prepares the created image for final delivery.
768 In its simplest form, this method merely copies the install root to the
769 supplied destination directory; other subclasses may choose to package
770 the image by e.g. creating a bootable ISO containing the image and
771 bootloader configuration.
773 destdir -- the directory into which the final image should be moved;
774 this defaults to the current directory.
777 self._stage_final_image()
779 for f in os.listdir(self._outdir):
780 shutil.move(os.path.join(self._outdir, f),
781 os.path.join(destdir, f))
783 def create(self):
784 """Install, configure and package an image.
786 This method is a utility method which creates and image by calling some
787 of the other methods in the following order - mount(), install(),
788 configure(), unmount and package().
791 self.mount()
792 self.install()
793 self.configure()
794 self.unmount()
795 self.package()
797 class LoopImageCreator(ImageCreator):
798 """Installs a system into a loopback-mountable filesystem image.
800 LoopImageCreator is a straightforward ImageCreator subclass; the system
801 is installed into an ext3 filesystem on a sparse file which can be
802 subsequently loopback-mounted.
806 def __init__(self, ks, name, fslabel=None, releasever=None, tmpdir="/tmp",
807 useplugins=False, cacheonly=False, docleanup=True):
808 """Initialize a LoopImageCreator instance.
810 This method takes the same arguments as ImageCreator.__init__() with
811 the addition of:
813 fslabel -- A string used as a label for any filesystems created.
816 ImageCreator.__init__(self, ks, name, releasever=releasever, tmpdir=tmpdir,
817 useplugins=useplugins, cacheonly=cacheonly, docleanup=docleanup)
819 self.__fslabel = None
820 self.fslabel = fslabel
822 self.__minsize_KB = 0
823 self.__blocksize = 4096
825 self.__instloop = None
826 self.__imgdir = None
828 self.__image_size = kickstart.get_image_size(self.ks,
829 4096L * 1024 * 1024)
832 # Properties
834 def __get_fslabel(self):
835 if self.__fslabel is None:
836 return self.name
837 else:
838 return self.__fslabel
839 def __set_fslabel(self, val):
840 if val is None:
841 self.__fslabel = None
842 else:
843 self.__fslabel = val[:FSLABEL_MAXLEN]
844 fslabel = property(__get_fslabel, __set_fslabel)
845 """A string used to label any filesystems created.
847 Some filesystems impose a constraint on the maximum allowed size of the
848 filesystem label. In the case of ext3 it's 16 characters, but in the case
849 of ISO9660 it's 32 characters.
851 mke2fs silently truncates the label, but mkisofs aborts if the label is too
852 long. So, for convenience sake, any string assigned to this attribute is
853 silently truncated to FSLABEL_MAXLEN (32) characters.
857 def __get_image(self):
858 if self.__imgdir is None:
859 raise CreatorError("_image is not valid before calling mount()")
860 return self.__imgdir + "/ext3fs.img"
861 _image = property(__get_image)
862 """The location of the image file.
864 This is the path to the filesystem image. Subclasses may use this path
865 in order to package the image in _stage_final_image().
867 Note, this directory does not exist before ImageCreator.mount() is called.
869 Note also, this is a read-only attribute.
873 def __get_blocksize(self):
874 return self.__blocksize
875 def __set_blocksize(self, val):
876 if self.__instloop:
877 raise CreatorError("_blocksize must be set before calling mount()")
878 try:
879 self.__blocksize = int(val)
880 except ValueError:
881 raise CreatorError("'%s' is not a valid integer value "
882 "for _blocksize" % val)
883 _blocksize = property(__get_blocksize, __set_blocksize)
884 """The block size used by the image's filesystem.
886 This is the block size used when creating the filesystem image. Subclasses
887 may change this if they wish to use something other than a 4k block size.
889 Note, this attribute may only be set before calling mount().
894 # Helpers for subclasses
896 def _resparse(self, size = None):
897 """Rebuild the filesystem image to be as sparse as possible.
899 This method should be used by subclasses when staging the final image
900 in order to reduce the actual space taken up by the sparse image file
901 to be as little as possible.
903 This is done by resizing the filesystem to the minimal size (thereby
904 eliminating any space taken up by deleted files) and then resizing it
905 back to the supplied size.
907 size -- the size in, in bytes, which the filesystem image should be
908 resized to after it has been minimized; this defaults to None,
909 causing the original size specified by the kickstart file to
910 be used (or 4GiB if not specified in the kickstart).
913 return self.__instloop.resparse(size)
915 def _base_on(self, base_on):
916 shutil.copyfile(base_on, self._image)
919 # Actual implementation
921 def _mount_instroot(self, base_on = None):
922 self.__imgdir = self._mkdtemp()
924 if not base_on is None:
925 self._base_on(base_on)
927 self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image,
928 self.__image_size),
929 self._instroot,
930 self._fstype,
931 self.__blocksize,
932 self.fslabel,
933 self.tmpdir)
935 try:
936 self.__instloop.mount()
937 except MountError, e:
938 raise CreatorError("Failed to loopback mount '%s' : %s" %
939 (self._image, e))
941 def _unmount_instroot(self):
942 if not self.__instloop is None:
943 self.__instloop.cleanup()
945 def _stage_final_image(self):
946 self._resparse()
947 shutil.move(self._image, self._outdir + "/" + self.name + ".img")