if mounting squashfs add ro mount option
[livecd.git] / imgcreate / creator.py
blobd8249cba45d3192e3c4f246ebc2404c6c945fc0c
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 """Initialize an ImageCreator instance.
57 ks -- a pykickstart.KickstartParser instance; this instance will be
58 used to drive the install by e.g. providing the list of packages
59 to be installed, the system configuration and %post scripts
61 name -- a name for the image; used for e.g. image filenames or
62 filesystem labels
64 releasever -- Value to substitute for $releasever in repo urls
66 tmpdir -- Top level directory to use for temporary files and dirs
67 """
68 self.ks = ks
69 """A pykickstart.KickstartParser instance."""
71 self.name = name
72 """A name for the image."""
74 self.releasever = releasever
75 self.useplugins = useplugins
77 self.tmpdir = tmpdir
78 """The directory in which all temporary files will be created."""
79 if not os.path.exists(self.tmpdir):
80 makedirs(self.tmpdir)
82 self.__builddir = None
83 self.__bindmounts = []
85 self.__sanity_check()
87 # get selinuxfs mountpoint
88 self.__selinux_mountpoint = "/sys/fs/selinux"
89 with open("/proc/self/mountinfo", "r") as f:
90 for line in f.readlines():
91 fields = line.split()
92 if fields[-2] == "selinuxfs":
93 self.__selinux_mountpoint = fields[4]
94 break
96 def __del__(self):
97 self.cleanup()
100 # Properties
102 def __get_instroot(self):
103 if self.__builddir is None:
104 raise CreatorError("_instroot is not valid before calling mount()")
105 return self.__builddir + "/install_root"
106 _instroot = property(__get_instroot)
107 """The location of the install root directory.
109 This is the directory into which the system is installed. Subclasses may
110 mount a filesystem image here or copy files to/from here.
112 Note, this directory does not exist before ImageCreator.mount() is called.
114 Note also, this is a read-only attribute.
118 def __get_outdir(self):
119 if self.__builddir is None:
120 raise CreatorError("_outdir is not valid before calling mount()")
121 return self.__builddir + "/out"
122 _outdir = property(__get_outdir)
123 """The staging location for the final image.
125 This is where subclasses should stage any files that are part of the final
126 image. ImageCreator.package() will copy any files found here into the
127 requested destination directory.
129 Note, this directory does not exist before ImageCreator.mount() is called.
131 Note also, this is a read-only attribute.
136 # Hooks for subclasses
138 def _mount_instroot(self, base_on = None):
139 """Mount or prepare the install root directory.
141 This is the hook where subclasses may prepare the install root by e.g.
142 mounting creating and loopback mounting a filesystem image to
143 _instroot.
145 There is no default implementation.
147 base_on -- this is the value passed to mount() and can be interpreted
148 as the subclass wishes; it might e.g. be the location of
149 a previously created ISO containing a system image.
152 pass
154 def _unmount_instroot(self):
155 """Undo anything performed in _mount_instroot().
157 This is the hook where subclasses must undo anything which was done
158 in _mount_instroot(). For example, if a filesystem image was mounted
159 onto _instroot, it should be unmounted here.
161 There is no default implementation.
164 pass
166 def _create_bootconfig(self):
167 """Configure the image so that it's bootable.
169 This is the hook where subclasses may prepare the image for booting by
170 e.g. creating an initramfs and bootloader configuration.
172 This hook is called while the install root is still mounted, after the
173 packages have been installed and the kickstart configuration has been
174 applied, but before the %post scripts have been executed.
176 There is no default implementation.
179 pass
181 def _stage_final_image(self):
182 """Stage the final system image in _outdir.
184 This is the hook where subclasses should place the image in _outdir
185 so that package() can copy it to the requested destination directory.
187 By default, this moves the install root into _outdir.
190 shutil.move(self._instroot, self._outdir + "/" + self.name)
192 def _get_required_packages(self):
193 """Return a list of required packages.
195 This is the hook where subclasses may specify a set of packages which
196 it requires to be installed.
198 This returns an empty list by default.
200 Note, subclasses should usually chain up to the base class
201 implementation of this hook.
204 return []
206 def _get_excluded_packages(self):
207 """Return a list of excluded packages.
209 This is the hook where subclasses may specify a set of packages which
210 it requires _not_ to be installed.
212 This returns an empty list by default.
214 Note, subclasses should usually chain up to the base class
215 implementation of this hook.
218 return []
220 def _get_fstab(self):
221 """Return the desired contents of /etc/fstab.
223 This is the hook where subclasses may specify the contents of
224 /etc/fstab by returning a string containing the desired contents.
226 A sensible default implementation is provided.
229 s = "/dev/root / %s defaults,noatime 0 0\n" %(self._fstype)
230 s += self._get_fstab_special()
231 return s
233 def _get_fstab_special(self):
234 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
235 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
236 s += "proc /proc proc defaults 0 0\n"
237 s += "sysfs /sys sysfs defaults 0 0\n"
238 return s
240 def _get_post_scripts_env(self, in_chroot):
241 """Return an environment dict for %post scripts.
243 This is the hook where subclasses may specify some environment
244 variables for %post scripts by return a dict containing the desired
245 environment.
247 By default, this returns an empty dict.
249 in_chroot -- whether this %post script is to be executed chroot()ed
250 into _instroot.
253 return {}
255 def _get_kernel_versions(self):
256 """Return a dict detailing the available kernel types/versions.
258 This is the hook where subclasses may override what kernel types and
259 versions should be available for e.g. creating the booloader
260 configuration.
262 A dict should be returned mapping the available kernel types to a list
263 of the available versions for those kernels.
265 The default implementation uses rpm to iterate over everything
266 providing 'kernel', finds /boot/vmlinuz-* and returns the version
267 obtained from the vmlinuz filename. (This can differ from the kernel
268 RPM's n-v-r in the case of e.g. xen)
271 def get_version(header):
272 version = None
273 for f in header['filenames']:
274 if f.startswith('/boot/vmlinuz-'):
275 version = f[14:]
276 return version
278 ts = rpm.TransactionSet(self._instroot)
280 ret = {}
281 for header in ts.dbMatch('provides', 'kernel'):
282 version = get_version(header)
283 if version is None:
284 continue
286 name = header['name']
287 if not name in ret:
288 ret[name] = [version]
289 elif not version in ret[name]:
290 ret[name].append(version)
292 return ret
295 # Helpers for subclasses
297 def _do_bindmounts(self):
298 """Mount various system directories onto _instroot.
300 This method is called by mount(), but may also be used by subclasses
301 in order to re-mount the bindmounts after modifying the underlying
302 filesystem.
305 for b in self.__bindmounts:
306 b.mount()
308 def _undo_bindmounts(self):
309 """Unmount the bind-mounted system directories from _instroot.
311 This method is usually only called by unmount(), but may also be used
312 by subclasses in order to gain access to the filesystem obscured by
313 the bindmounts - e.g. in order to create device nodes on the image
314 filesystem.
317 self.__bindmounts.reverse()
318 for b in self.__bindmounts:
319 b.unmount()
321 def _chroot(self):
322 """Chroot into the install root.
324 This method may be used by subclasses when executing programs inside
325 the install root e.g.
327 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
330 os.chroot(self._instroot)
331 os.chdir("/")
333 def _mkdtemp(self, prefix = "tmp-"):
334 """Create a temporary directory.
336 This method may be used by subclasses to create a temporary directory
337 for use in building the final image - e.g. a subclass might create
338 a temporary directory in order to bundle a set of files into a package.
340 The subclass may delete this directory if it wishes, but it will be
341 automatically deleted by cleanup().
343 The absolute path to the temporary directory is returned.
345 Note, this method should only be called after mount() has been called.
347 prefix -- a prefix which should be used when creating the directory;
348 defaults to "tmp-".
351 self.__ensure_builddir()
352 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
354 def _mkstemp(self, prefix = "tmp-"):
355 """Create a temporary file.
357 This method may be used by subclasses to create a temporary file
358 for use in building the final image - e.g. a subclass might need
359 a temporary location to unpack a compressed file.
361 The subclass may delete this file if it wishes, but it will be
362 automatically deleted by cleanup().
364 A tuple containing a file descriptor (returned from os.open() and the
365 absolute path to the temporary directory is returned.
367 Note, this method should only be called after mount() has been called.
369 prefix -- a prefix which should be used when creating the file;
370 defaults to "tmp-".
373 self.__ensure_builddir()
374 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
376 def _mktemp(self, prefix = "tmp-"):
377 """Create a temporary file.
379 This method simply calls _mkstemp() and closes the returned file
380 descriptor.
382 The absolute path to the temporary file is returned.
384 Note, this method should only be called after mount() has been called.
386 prefix -- a prefix which should be used when creating the file;
387 defaults to "tmp-".
391 (f, path) = self._mkstemp(prefix)
392 os.close(f)
393 return path
396 # Actual implementation
398 def __ensure_builddir(self):
399 if not self.__builddir is None:
400 return
402 try:
403 self.__builddir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir),
404 prefix = "imgcreate-")
405 except OSError, e:
406 raise CreatorError("Failed create build directory in %s: %s" %
407 (self.tmpdir, e.strerror))
409 def __sanity_check(self):
410 """Ensure that the config we've been given is sane."""
411 if not (kickstart.get_packages(self.ks) or
412 kickstart.get_groups(self.ks)):
413 raise CreatorError("No packages or groups specified")
415 kickstart.convert_method_to_repo(self.ks)
417 if not kickstart.get_repos(self.ks):
418 raise CreatorError("No repositories specified")
420 def __write_fstab(self):
421 fstab = open(self._instroot + "/etc/fstab", "w")
422 fstab.write(self._get_fstab())
423 fstab.close()
425 def __create_minimal_dev(self):
426 """Create a minimal /dev so that we don't corrupt the host /dev"""
427 origumask = os.umask(0000)
428 devices = (('null', 1, 3, 0666),
429 ('urandom',1, 9, 0666),
430 ('random', 1, 8, 0666),
431 ('full', 1, 7, 0666),
432 ('ptmx', 5, 2, 0666),
433 ('tty', 5, 0, 0666),
434 ('zero', 1, 5, 0666))
435 links = (("/proc/self/fd", "/dev/fd"),
436 ("/proc/self/fd/0", "/dev/stdin"),
437 ("/proc/self/fd/1", "/dev/stdout"),
438 ("/proc/self/fd/2", "/dev/stderr"))
440 for (node, major, minor, perm) in devices:
441 if not os.path.exists(self._instroot + "/dev/" + node):
442 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
443 for (src, dest) in links:
444 if not os.path.exists(self._instroot + dest):
445 os.symlink(src, self._instroot + dest)
446 os.umask(origumask)
448 def __create_selinuxfs(self):
449 if not os.path.exists(self.__selinux_mountpoint):
450 return
452 arglist = ["/bin/mount", "--bind", "/dev/null", self._instroot + self.__selinux_mountpoint + "/load"]
453 subprocess.call(arglist, close_fds = True)
455 if kickstart.selinux_enabled(self.ks):
456 # label the fs like it is a root before the bind mounting
457 arglist = ["/sbin/setfiles", "-F", "-r", self._instroot, selinux.selinux_file_context_path(), self._instroot]
458 subprocess.call(arglist, close_fds = True)
459 # these dumb things don't get magically fixed, so make the user generic
460 # if selinux exists on the host we need to lie to the chroot
461 if selinux.is_selinux_enabled():
462 for f in ("/proc", "/sys"):
463 arglist = ["/usr/bin/chcon", "-u", "system_u", self._instroot + f]
464 subprocess.call(arglist, close_fds = True)
466 def __destroy_selinuxfs(self):
467 if not os.path.exists(self.__selinux_mountpoint):
468 return
470 # if the system was running selinux clean up our lies
471 arglist = ["/bin/umount", self._instroot + self.__selinux_mountpoint + "/load"]
472 subprocess.call(arglist, close_fds = True)
474 def mount(self, base_on = None, cachedir = None):
475 """Setup the target filesystem in preparation for an install.
477 This function sets up the filesystem which the ImageCreator will
478 install into and configure. The ImageCreator class merely creates an
479 install root directory, bind mounts some system directories (e.g. /dev)
480 and writes out /etc/fstab. Other subclasses may also e.g. create a
481 sparse file, format it and loopback mount it to the install root.
483 base_on -- a previous install on which to base this install; defaults
484 to None, causing a new image to be created
486 cachedir -- a directory in which to store the Yum cache; defaults to
487 None, causing a new cache to be created; by setting this
488 to another directory, the same cache can be reused across
489 multiple installs.
492 self.__ensure_builddir()
494 makedirs(self._instroot)
495 makedirs(self._outdir)
497 self._mount_instroot(base_on)
499 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc"):
500 makedirs(self._instroot + d)
502 cachesrc = cachedir or (self.__builddir + "/yum-cache")
503 makedirs(cachesrc)
505 # bind mount system directories into _instroot
506 for (f, dest) in [("/sys", None), ("/proc", None),
507 ("/dev/pts", None), ("/dev/shm", None),
508 (self.__selinux_mountpoint, self.__selinux_mountpoint),
509 (cachesrc, "/var/cache/yum")]:
510 if os.path.exists(f):
511 self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))
512 else:
513 logging.warn("Skipping (%s,%s) because source doesn't exist." % (f, dest))
515 self._do_bindmounts()
517 self.__create_selinuxfs()
519 self.__create_minimal_dev()
521 os.symlink("/proc/self/mounts", self._instroot + "/etc/mtab")
523 self.__write_fstab()
525 def unmount(self):
526 """Unmounts the target filesystem.
528 The ImageCreator class detaches the system from the install root, but
529 other subclasses may also detach the loopback mounted filesystem image
530 from the install root.
533 self.__destroy_selinuxfs()
535 self._undo_bindmounts()
537 self._unmount_instroot()
539 def cleanup(self):
540 """Unmounts the target filesystem and deletes temporary files.
542 This method calls unmount() and then deletes any temporary files and
543 directories that were created on the host system while building the
544 image.
546 Note, make sure to call this method once finished with the creator
547 instance in order to ensure no stale files are left on the host e.g.:
549 creator = ImageCreator(ks, name)
550 try:
551 creator.create()
552 finally:
553 creator.cleanup()
556 if not self.__builddir:
557 return
559 self.unmount()
561 shutil.rmtree(self.__builddir, ignore_errors = True)
562 self.__builddir = None
564 def __select_packages(self, ayum):
565 skipped_pkgs = []
566 for pkg in kickstart.get_packages(self.ks,
567 self._get_required_packages()):
568 try:
569 ayum.selectPackage(pkg)
570 except yum.Errors.InstallError, e:
571 if kickstart.ignore_missing(self.ks):
572 skipped_pkgs.append(pkg)
573 else:
574 raise CreatorError("Failed to find package '%s' : %s" %
575 (pkg, e))
577 for pkg in skipped_pkgs:
578 logging.warn("Skipping missing package '%s'" % (pkg,))
580 def __select_groups(self, ayum):
581 skipped_groups = []
582 for group in kickstart.get_groups(self.ks):
583 try:
584 ayum.selectGroup(group.name, group.include)
585 except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
586 if kickstart.ignore_missing(self.ks):
587 raise CreatorError("Failed to find group '%s' : %s" %
588 (group.name, e))
589 else:
590 skipped_groups.append(group)
592 for group in skipped_groups:
593 logging.warn("Skipping missing group '%s'" % (group.name,))
595 def __deselect_packages(self, ayum):
596 for pkg in kickstart.get_excluded(self.ks,
597 self._get_excluded_packages()):
598 ayum.deselectPackage(pkg)
600 # if the system is running selinux and the kickstart wants it disabled
601 # we need /usr/sbin/lokkit
602 def __can_handle_selinux(self, ayum):
603 file = "/usr/sbin/lokkit"
604 if not kickstart.selinux_enabled(self.ks) and selinux.is_selinux_enabled() and not ayum.installHasFile(file):
605 raise CreatorError("Unable to disable SELinux because the installed package set did not include the file %s" % (file))
607 def install(self, repo_urls = {}):
608 """Install packages into the install root.
610 This function installs the packages listed in the supplied kickstart
611 into the install root. By default, the packages are installed from the
612 repository URLs specified in the kickstart.
614 repo_urls -- a dict which maps a repository name to a repository URL;
615 if supplied, this causes any repository URLs specified in
616 the kickstart to be overridden.
619 yum_conf = self._mktemp(prefix = "yum.conf-")
621 ayum = LiveCDYum(releasever=self.releasever, useplugins=self.useplugins)
622 ayum.setup(yum_conf, self._instroot)
624 for repo in kickstart.get_repos(self.ks, repo_urls):
625 (name, baseurl, mirrorlist, proxy, inc, exc, cost) = repo
627 yr = ayum.addRepository(name, baseurl, mirrorlist)
628 if inc:
629 yr.includepkgs = inc
630 if exc:
631 yr.exclude = exc
632 if proxy:
633 yr.proxy = proxy
634 if cost is not None:
635 yr.cost = cost
636 ayum.setup(yum_conf, self._instroot)
638 if kickstart.exclude_docs(self.ks):
639 rpm.addMacro("_excludedocs", "1")
640 if not kickstart.selinux_enabled(self.ks):
641 rpm.addMacro("__file_context_path", "%{nil}")
642 if kickstart.inst_langs(self.ks) != None:
643 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
645 try:
646 self.__select_packages(ayum)
647 self.__select_groups(ayum)
648 self.__deselect_packages(ayum)
650 self.__can_handle_selinux(ayum)
652 ayum.runInstall()
653 except yum.Errors.RepoError, e:
654 raise CreatorError("Unable to download from repo : %s" % (e,))
655 except yum.Errors.YumBaseError, e:
656 raise CreatorError("Unable to install: %s" % (e,))
657 finally:
658 ayum.closeRpmDB()
659 ayum.close()
660 os.unlink(yum_conf)
662 # do some clean up to avoid lvm info leakage. this sucks.
663 for subdir in ("cache", "backup", "archive"):
664 lvmdir = self._instroot + "/etc/lvm/" + subdir
665 try:
666 for f in os.listdir(lvmdir):
667 os.unlink(lvmdir + "/" + f)
668 except:
669 pass
671 def _run_post_scripts(self):
672 for s in kickstart.get_post_scripts(self.ks):
673 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
674 dir = self._instroot + "/tmp")
676 os.write(fd, s.script)
677 os.close(fd)
678 os.chmod(path, 0700)
680 env = self._get_post_scripts_env(s.inChroot)
682 if not s.inChroot:
683 env["INSTALL_ROOT"] = self._instroot
684 preexec = None
685 script = path
686 else:
687 preexec = self._chroot
688 script = "/tmp/" + os.path.basename(path)
690 try:
691 subprocess.check_call([s.interp, script],
692 preexec_fn = preexec, env = env)
693 except OSError, e:
694 raise CreatorError("Failed to execute %%post script "
695 "with '%s' : %s" % (s.interp, e.strerror))
696 except subprocess.CalledProcessError, err:
697 if s.errorOnFail:
698 raise CreatorError("%%post script failed with code %d "
699 % err.returncode)
700 logging.warning("ignoring %%post failure (code %d)"
701 % err.returncode)
702 finally:
703 os.unlink(path)
705 def configure(self):
706 """Configure the system image according to the kickstart.
708 This method applies the (e.g. keyboard or network) configuration
709 specified in the kickstart and executes the kickstart %post scripts.
711 If neccessary, it also prepares the image to be bootable by e.g.
712 creating an initrd and bootloader configuration.
715 ksh = self.ks.handler
717 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
718 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
719 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
720 kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
721 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
722 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
723 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
724 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
725 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
726 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
728 self._create_bootconfig()
730 self._run_post_scripts()
731 kickstart.SelinuxConfig(self._instroot).apply(ksh.selinux)
733 def launch_shell(self):
734 """Launch a shell in the install root.
736 This method is launches a bash shell chroot()ed in the install root;
737 this can be useful for debugging.
740 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
742 def package(self, destdir = "."):
743 """Prepares the created image for final delivery.
745 In its simplest form, this method merely copies the install root to the
746 supplied destination directory; other subclasses may choose to package
747 the image by e.g. creating a bootable ISO containing the image and
748 bootloader configuration.
750 destdir -- the directory into which the final image should be moved;
751 this defaults to the current directory.
754 self._stage_final_image()
756 for f in os.listdir(self._outdir):
757 shutil.move(os.path.join(self._outdir, f),
758 os.path.join(destdir, f))
760 def create(self):
761 """Install, configure and package an image.
763 This method is a utility method which creates and image by calling some
764 of the other methods in the following order - mount(), install(),
765 configure(), unmount and package().
768 self.mount()
769 self.install()
770 self.configure()
771 self.unmount()
772 self.package()
774 class LoopImageCreator(ImageCreator):
775 """Installs a system into a loopback-mountable filesystem image.
777 LoopImageCreator is a straightforward ImageCreator subclass; the system
778 is installed into an ext3 filesystem on a sparse file which can be
779 subsequently loopback-mounted.
783 def __init__(self, ks, name, fslabel=None, releasever=None, tmpdir="/tmp", useplugins=False):
784 """Initialize a LoopImageCreator instance.
786 This method takes the same arguments as ImageCreator.__init__() with
787 the addition of:
789 fslabel -- A string used as a label for any filesystems created.
792 ImageCreator.__init__(self, ks, name, releasever=releasever, tmpdir=tmpdir, useplugins=useplugins)
794 self.__fslabel = None
795 self.fslabel = fslabel
797 self.__minsize_KB = 0
798 self.__blocksize = 4096
799 self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
801 self.__instloop = None
802 self.__imgdir = None
804 self.__image_size = kickstart.get_image_size(self.ks,
805 4096L * 1024 * 1024)
808 # Properties
810 def __get_fslabel(self):
811 if self.__fslabel is None:
812 return self.name
813 else:
814 return self.__fslabel
815 def __set_fslabel(self, val):
816 if val is None:
817 self.__fslabel = None
818 else:
819 self.__fslabel = val[:FSLABEL_MAXLEN]
820 fslabel = property(__get_fslabel, __set_fslabel)
821 """A string used to label any filesystems created.
823 Some filesystems impose a constraint on the maximum allowed size of the
824 filesystem label. In the case of ext3 it's 16 characters, but in the case
825 of ISO9660 it's 32 characters.
827 mke2fs silently truncates the label, but mkisofs aborts if the label is too
828 long. So, for convenience sake, any string assigned to this attribute is
829 silently truncated to FSLABEL_MAXLEN (32) characters.
833 def __get_image(self):
834 if self.__imgdir is None:
835 raise CreatorError("_image is not valid before calling mount()")
836 return self.__imgdir + "/ext3fs.img"
837 _image = property(__get_image)
838 """The location of the image file.
840 This is the path to the filesystem image. Subclasses may use this path
841 in order to package the image in _stage_final_image().
843 Note, this directory does not exist before ImageCreator.mount() is called.
845 Note also, this is a read-only attribute.
849 def __get_blocksize(self):
850 return self.__blocksize
851 def __set_blocksize(self, val):
852 if self.__instloop:
853 raise CreatorError("_blocksize must be set before calling mount()")
854 try:
855 self.__blocksize = int(val)
856 except ValueError:
857 raise CreatorError("'%s' is not a valid integer value "
858 "for _blocksize" % val)
859 _blocksize = property(__get_blocksize, __set_blocksize)
860 """The block size used by the image's filesystem.
862 This is the block size used when creating the filesystem image. Subclasses
863 may change this if they wish to use something other than a 4k block size.
865 Note, this attribute may only be set before calling mount().
869 def __get_fstype(self):
870 return self.__fstype
871 def __set_fstype(self, val):
872 if val not in ("ext2", "ext3", "ext4"):
873 raise CreatorError("Unknown _fstype '%s' supplied" % val)
874 self.__fstype = val
875 _fstype = property(__get_fstype, __set_fstype)
876 """The type of filesystem used for the image.
878 This is the filesystem type used when creating the filesystem image.
879 Subclasses may change this if they wish to use something other ext3.
881 Note, only ext2, ext3, ext4 are currently supported.
883 Note also, this attribute may only be set before calling mount().
888 # Helpers for subclasses
890 def _resparse(self, size = None):
891 """Rebuild the filesystem image to be as sparse as possible.
893 This method should be used by subclasses when staging the final image
894 in order to reduce the actual space taken up by the sparse image file
895 to be as little as possible.
897 This is done by resizing the filesystem to the minimal size (thereby
898 eliminating any space taken up by deleted files) and then resizing it
899 back to the supplied size.
901 size -- the size in, in bytes, which the filesystem image should be
902 resized to after it has been minimized; this defaults to None,
903 causing the original size specified by the kickstart file to
904 be used (or 4GiB if not specified in the kickstart).
907 return self.__instloop.resparse(size)
909 def _base_on(self, base_on):
910 shutil.copyfile(base_on, self._image)
913 # Actual implementation
915 def _mount_instroot(self, base_on = None):
916 self.__imgdir = self._mkdtemp()
918 if not base_on is None:
919 self._base_on(base_on)
921 self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image,
922 self.__image_size),
923 self._instroot,
924 self.__fstype,
925 self.__blocksize,
926 self.fslabel,
927 self.tmpdir)
929 try:
930 self.__instloop.mount()
931 except MountError, e:
932 raise CreatorError("Failed to loopback mount '%s' : %s" %
933 (self._image, e))
935 def _unmount_instroot(self):
936 if not self.__instloop is None:
937 self.__instloop.cleanup()
939 def _stage_final_image(self):
940 self._resparse()
941 shutil.move(self._image, self._outdir + "/" + self.name + ".img")