Version 13.4
[livecd.git] / imgcreate / creator.py
blobb7436e6354b5cd369212bc10bfe988e48496085c
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):
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
65 """
66 self.ks = ks
67 """A pykickstart.KickstartParser instance."""
69 self.name = name
70 """A name for the image."""
72 self.releasever = releasever
74 self.tmpdir = "/var/tmp"
75 """The directory in which all temporary files will be created."""
77 self.__builddir = None
78 self.__bindmounts = []
80 self.__sanity_check()
82 def __del__(self):
83 self.cleanup()
86 # Properties
88 def __get_instroot(self):
89 if self.__builddir is None:
90 raise CreatorError("_instroot is not valid before calling mount()")
91 return self.__builddir + "/install_root"
92 _instroot = property(__get_instroot)
93 """The location of the install root directory.
95 This is the directory into which the system is installed. Subclasses may
96 mount a filesystem image here or copy files to/from here.
98 Note, this directory does not exist before ImageCreator.mount() is called.
100 Note also, this is a read-only attribute.
104 def __get_outdir(self):
105 if self.__builddir is None:
106 raise CreatorError("_outdir is not valid before calling mount()")
107 return self.__builddir + "/out"
108 _outdir = property(__get_outdir)
109 """The staging location for the final image.
111 This is where subclasses should stage any files that are part of the final
112 image. ImageCreator.package() will copy any files found here into the
113 requested destination directory.
115 Note, this directory does not exist before ImageCreator.mount() is called.
117 Note also, this is a read-only attribute.
122 # Hooks for subclasses
124 def _mount_instroot(self, base_on = None):
125 """Mount or prepare the install root directory.
127 This is the hook where subclasses may prepare the install root by e.g.
128 mounting creating and loopback mounting a filesystem image to
129 _instroot.
131 There is no default implementation.
133 base_on -- this is the value passed to mount() and can be interpreted
134 as the subclass wishes; it might e.g. be the location of
135 a previously created ISO containing a system image.
138 pass
140 def _unmount_instroot(self):
141 """Undo anything performed in _mount_instroot().
143 This is the hook where subclasses must undo anything which was done
144 in _mount_instroot(). For example, if a filesystem image was mounted
145 onto _instroot, it should be unmounted here.
147 There is no default implementation.
150 pass
152 def _create_bootconfig(self):
153 """Configure the image so that it's bootable.
155 This is the hook where subclasses may prepare the image for booting by
156 e.g. creating an initramfs and bootloader configuration.
158 This hook is called while the install root is still mounted, after the
159 packages have been installed and the kickstart configuration has been
160 applied, but before the %post scripts have been executed.
162 There is no default implementation.
165 pass
167 def _stage_final_image(self):
168 """Stage the final system image in _outdir.
170 This is the hook where subclasses should place the image in _outdir
171 so that package() can copy it to the requested destination directory.
173 By default, this moves the install root into _outdir.
176 shutil.move(self._instroot, self._outdir + "/" + self.name)
178 def _get_required_packages(self):
179 """Return a list of required packages.
181 This is the hook where subclasses may specify a set of packages which
182 it requires to be installed.
184 This returns an empty list by default.
186 Note, subclasses should usually chain up to the base class
187 implementation of this hook.
190 return []
192 def _get_excluded_packages(self):
193 """Return a list of excluded packages.
195 This is the hook where subclasses may specify a set of packages which
196 it requires _not_ 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_fstab(self):
207 """Return the desired contents of /etc/fstab.
209 This is the hook where subclasses may specify the contents of
210 /etc/fstab by returning a string containing the desired contents.
212 A sensible default implementation is provided.
215 s = "/dev/root / %s defaults,noatime 0 0\n" %(self._fstype)
216 s += self._get_fstab_special()
217 return s
219 def _get_fstab_special(self):
220 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
221 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
222 s += "proc /proc proc defaults 0 0\n"
223 s += "sysfs /sys sysfs defaults 0 0\n"
224 return s
226 def _get_post_scripts_env(self, in_chroot):
227 """Return an environment dict for %post scripts.
229 This is the hook where subclasses may specify some environment
230 variables for %post scripts by return a dict containing the desired
231 environment.
233 By default, this returns an empty dict.
235 in_chroot -- whether this %post script is to be executed chroot()ed
236 into _instroot.
239 return {}
241 def _get_kernel_versions(self):
242 """Return a dict detailing the available kernel types/versions.
244 This is the hook where subclasses may override what kernel types and
245 versions should be available for e.g. creating the booloader
246 configuration.
248 A dict should be returned mapping the available kernel types to a list
249 of the available versions for those kernels.
251 The default implementation uses rpm to iterate over everything
252 providing 'kernel', finds /boot/vmlinuz-* and returns the version
253 obtained from the vmlinuz filename. (This can differ from the kernel
254 RPM's n-v-r in the case of e.g. xen)
257 def get_version(header):
258 version = None
259 for f in header['filenames']:
260 if f.startswith('/boot/vmlinuz-'):
261 version = f[14:]
262 return version
264 ts = rpm.TransactionSet(self._instroot)
266 ret = {}
267 for header in ts.dbMatch('provides', 'kernel'):
268 version = get_version(header)
269 if version is None:
270 continue
272 name = header['name']
273 if not name in ret:
274 ret[name] = [version]
275 elif not version in ret[name]:
276 ret[name].append(version)
278 return ret
281 # Helpers for subclasses
283 def _do_bindmounts(self):
284 """Mount various system directories onto _instroot.
286 This method is called by mount(), but may also be used by subclasses
287 in order to re-mount the bindmounts after modifying the underlying
288 filesystem.
291 for b in self.__bindmounts:
292 b.mount()
294 def _undo_bindmounts(self):
295 """Unmount the bind-mounted system directories from _instroot.
297 This method is usually only called by unmount(), but may also be used
298 by subclasses in order to gain access to the filesystem obscured by
299 the bindmounts - e.g. in order to create device nodes on the image
300 filesystem.
303 self.__bindmounts.reverse()
304 for b in self.__bindmounts:
305 b.unmount()
307 def _chroot(self):
308 """Chroot into the install root.
310 This method may be used by subclasses when executing programs inside
311 the install root e.g.
313 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
316 os.chroot(self._instroot)
317 os.chdir("/")
319 def _mkdtemp(self, prefix = "tmp-"):
320 """Create a temporary directory.
322 This method may be used by subclasses to create a temporary directory
323 for use in building the final image - e.g. a subclass might create
324 a temporary directory in order to bundle a set of files into a package.
326 The subclass may delete this directory if it wishes, but it will be
327 automatically deleted by cleanup().
329 The absolute path to the temporary directory is returned.
331 Note, this method should only be called after mount() has been called.
333 prefix -- a prefix which should be used when creating the directory;
334 defaults to "tmp-".
337 self.__ensure_builddir()
338 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
340 def _mkstemp(self, prefix = "tmp-"):
341 """Create a temporary file.
343 This method may be used by subclasses to create a temporary file
344 for use in building the final image - e.g. a subclass might need
345 a temporary location to unpack a compressed file.
347 The subclass may delete this file if it wishes, but it will be
348 automatically deleted by cleanup().
350 A tuple containing a file descriptor (returned from os.open() and the
351 absolute path to the temporary directory is returned.
353 Note, this method should only be called after mount() has been called.
355 prefix -- a prefix which should be used when creating the file;
356 defaults to "tmp-".
359 self.__ensure_builddir()
360 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
362 def _mktemp(self, prefix = "tmp-"):
363 """Create a temporary file.
365 This method simply calls _mkstemp() and closes the returned file
366 descriptor.
368 The absolute path to the temporary file 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 file;
373 defaults to "tmp-".
377 (f, path) = self._mkstemp(prefix)
378 os.close(f)
379 return path
382 # Actual implementation
384 def __ensure_builddir(self):
385 if not self.__builddir is None:
386 return
388 try:
389 self.__builddir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir),
390 prefix = "imgcreate-")
391 except OSError, e:
392 raise CreatorError("Failed create build directory in %s: %s" %
393 (self.tmpdir, e.strerror))
395 def __sanity_check(self):
396 """Ensure that the config we've been given is sane."""
397 if not (kickstart.get_packages(self.ks) or
398 kickstart.get_groups(self.ks)):
399 raise CreatorError("No packages or groups specified")
401 kickstart.convert_method_to_repo(self.ks)
403 if not kickstart.get_repos(self.ks):
404 raise CreatorError("No repositories specified")
406 def __write_fstab(self):
407 fstab = open(self._instroot + "/etc/fstab", "w")
408 fstab.write(self._get_fstab())
409 fstab.close()
411 def __create_minimal_dev(self):
412 """Create a minimal /dev so that we don't corrupt the host /dev"""
413 origumask = os.umask(0000)
414 devices = (('null', 1, 3, 0666),
415 ('urandom',1, 9, 0666),
416 ('random', 1, 8, 0666),
417 ('full', 1, 7, 0666),
418 ('ptmx', 5, 2, 0666),
419 ('tty', 5, 0, 0666),
420 ('zero', 1, 5, 0666))
421 links = (("/proc/self/fd", "/dev/fd"),
422 ("/proc/self/fd/0", "/dev/stdin"),
423 ("/proc/self/fd/1", "/dev/stdout"),
424 ("/proc/self/fd/2", "/dev/stderr"))
426 for (node, major, minor, perm) in devices:
427 if not os.path.exists(self._instroot + "/dev/" + node):
428 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
429 for (src, dest) in links:
430 if not os.path.exists(self._instroot + dest):
431 os.symlink(src, self._instroot + dest)
432 os.umask(origumask)
434 def __getbooleans(self):
435 booleans = []
436 if not kickstart.selinux_enabled(self.ks) or not os.path.exists("/selinux/enforce"):
437 return booleans
438 for i in selinux.security_get_boolean_names()[1]:
439 on = selinux.security_get_boolean_active(i)
440 booleans.append(("/booleans/%s" % i, "%d %d" % (on, on)))
441 return booleans
443 def __create_selinuxfs(self):
444 # if selinux exists on the host we need to lie to the chroot
445 if os.path.exists("/selinux/enforce"):
446 selinux_dir = self._instroot + "/selinux"
448 # enforce=0 tells the chroot selinux is not enforcing
449 # policyvers=999 tell the chroot to make the highest version of policy it can
451 files = [('/enforce', '0'),
452 ('/policyvers', '999'),
453 ('/commit_pending_bools', ''),
454 ('/mls', str(selinux.is_selinux_mls_enabled()))]
456 for (file, value) in files + self.__getbooleans():
457 fd = os.open(selinux_dir + file, os.O_WRONLY | os.O_TRUNC | os.O_CREAT)
458 os.write(fd, value)
459 os.close(fd)
461 # we steal mls from the host system for now, might be best to always set it to 1????
462 # make /load -> /dev/null so chroot policy loads don't hurt anything
463 os.mknod(selinux_dir + "/load", 0666 | stat.S_IFCHR, os.makedev(1, 3))
465 # selinux is on in the kickstart, so clean up as best we can to start
466 if kickstart.selinux_enabled(self.ks):
467 # label the fs like it is a root before the bind mounting
468 arglist = ["/sbin/setfiles", "-F", "-r", self._instroot, selinux.selinux_file_context_path(), self._instroot]
469 subprocess.call(arglist, close_fds = True)
470 # these dumb things don't get magically fixed, so make the user generic
471 for f in ("/proc", "/sys", "/selinux"):
472 arglist = ["/usr/bin/chcon", "-u", "system_u", self._instroot + f]
473 subprocess.call(arglist, close_fds = True)
475 def __destroy_selinuxfs(self):
476 # if the system was running selinux clean up our lies
477 if os.path.exists("/selinux/enforce"):
478 for root, dirs, files in os.walk(self._instroot + "/selinux"):
479 for name in files:
480 try:
481 os.remove(os.path.join(root, name))
482 except OSError:
483 pass
484 for name in dirs:
485 if os.path.join(root, name) == self._instroot + "/selinux":
486 continue
487 try:
488 os.rmdir(os.path.join(root, name))
489 except OSError:
490 pass
492 def mount(self, base_on = None, cachedir = None):
493 """Setup the target filesystem in preparation for an install.
495 This function sets up the filesystem which the ImageCreator will
496 install into and configure. The ImageCreator class merely creates an
497 install root directory, bind mounts some system directories (e.g. /dev)
498 and writes out /etc/fstab. Other subclasses may also e.g. create a
499 sparse file, format it and loopback mount it to the install root.
501 base_on -- a previous install on which to base this install; defaults
502 to None, causing a new image to be created
504 cachedir -- a directory in which to store the Yum cache; defaults to
505 None, causing a new cache to be created; by setting this
506 to another directory, the same cache can be reused across
507 multiple installs.
510 self.__ensure_builddir()
512 makedirs(self._instroot)
513 makedirs(self._outdir)
515 self._mount_instroot(base_on)
517 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/selinux/booleans"):
518 makedirs(self._instroot + d)
520 cachesrc = cachedir or (self.__builddir + "/yum-cache")
521 makedirs(cachesrc)
523 # bind mount system directories into _instroot
524 for (f, dest) in [("/sys", None), ("/proc", None),
525 ("/dev/pts", None), ("/dev/shm", None),
526 (cachesrc, "/var/cache/yum")]:
527 self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))
529 self.__create_selinuxfs()
531 self._do_bindmounts()
533 self.__create_minimal_dev()
535 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
537 self.__write_fstab()
539 def unmount(self):
540 """Unmounts the target filesystem.
542 The ImageCreator class detaches the system from the install root, but
543 other subclasses may also detach the loopback mounted filesystem image
544 from the install root.
547 try:
548 os.unlink(self._instroot + "/etc/mtab")
549 except OSError:
550 pass
552 self.__destroy_selinuxfs()
554 self._undo_bindmounts()
556 self._unmount_instroot()
558 def cleanup(self):
559 """Unmounts the target filesystem and deletes temporary files.
561 This method calls unmount() and then deletes any temporary files and
562 directories that were created on the host system while building the
563 image.
565 Note, make sure to call this method once finished with the creator
566 instance in order to ensure no stale files are left on the host e.g.:
568 creator = ImageCreator(ks, name)
569 try:
570 creator.create()
571 finally:
572 creator.cleanup()
575 if not self.__builddir:
576 return
578 self.unmount()
580 shutil.rmtree(self.__builddir, ignore_errors = True)
581 self.__builddir = None
583 def __select_packages(self, ayum):
584 skipped_pkgs = []
585 for pkg in kickstart.get_packages(self.ks,
586 self._get_required_packages()):
587 try:
588 ayum.selectPackage(pkg)
589 except yum.Errors.InstallError, e:
590 if kickstart.ignore_missing(self.ks):
591 skipped_pkgs.append(pkg)
592 else:
593 raise CreatorError("Failed to find package '%s' : %s" %
594 (pkg, e))
596 for pkg in skipped_pkgs:
597 logging.warn("Skipping missing package '%s'" % (pkg,))
599 def __select_groups(self, ayum):
600 skipped_groups = []
601 for group in kickstart.get_groups(self.ks):
602 try:
603 ayum.selectGroup(group.name, group.include)
604 except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
605 if kickstart.ignore_missing(self.ks):
606 raise CreatorError("Failed to find group '%s' : %s" %
607 (group.name, e))
608 else:
609 skipped_groups.append(group)
611 for group in skipped_groups:
612 logging.warn("Skipping missing group '%s'" % (group.name,))
614 def __deselect_packages(self, ayum):
615 for pkg in kickstart.get_excluded(self.ks,
616 self._get_excluded_packages()):
617 ayum.deselectPackage(pkg)
619 # if the system is running selinux and the kickstart wants it disabled
620 # we need /usr/sbin/lokkit
621 def __can_handle_selinux(self, ayum):
622 file = "/usr/sbin/lokkit"
623 if not kickstart.selinux_enabled(self.ks) and os.path.exists("/selinux/enforce") and not ayum.installHasFile(file):
624 raise CreatorError("Unable to disable SELinux because the installed package set did not include the file %s" % (file))
626 def install(self, repo_urls = {}):
627 """Install packages into the install root.
629 This function installs the packages listed in the supplied kickstart
630 into the install root. By default, the packages are installed from the
631 repository URLs specified in the kickstart.
633 repo_urls -- a dict which maps a repository name to a repository URL;
634 if supplied, this causes any repository URLs specified in
635 the kickstart to be overridden.
638 yum_conf = self._mktemp(prefix = "yum.conf-")
640 ayum = LiveCDYum(releasever=self.releasever)
641 ayum.setup(yum_conf, self._instroot)
643 for repo in kickstart.get_repos(self.ks, repo_urls):
644 (name, baseurl, mirrorlist, inc, exc) = repo
646 yr = ayum.addRepository(name, baseurl, mirrorlist)
647 if inc:
648 yr.includepkgs = inc
649 if exc:
650 yr.exclude = exc
652 if kickstart.exclude_docs(self.ks):
653 rpm.addMacro("_excludedocs", "1")
654 if not kickstart.selinux_enabled(self.ks):
655 rpm.addMacro("__file_context_path", "%{nil}")
656 if kickstart.inst_langs(self.ks) != None:
657 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
659 try:
660 self.__select_packages(ayum)
661 self.__select_groups(ayum)
662 self.__deselect_packages(ayum)
664 self.__can_handle_selinux(ayum)
666 ayum.runInstall()
667 except yum.Errors.RepoError, e:
668 raise CreatorError("Unable to download from repo : %s" % (e,))
669 except yum.Errors.YumBaseError, e:
670 raise CreatorError("Unable to install: %s" % (e,))
671 finally:
672 ayum.closeRpmDB()
673 ayum.close()
674 os.unlink(yum_conf)
676 # do some clean up to avoid lvm info leakage. this sucks.
677 for subdir in ("cache", "backup", "archive"):
678 lvmdir = self._instroot + "/etc/lvm/" + subdir
679 try:
680 for f in os.listdir(lvmdir):
681 os.unlink(lvmdir + "/" + f)
682 except:
683 pass
685 def __run_post_scripts(self):
686 for s in kickstart.get_post_scripts(self.ks):
687 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
688 dir = self._instroot + "/tmp")
690 os.write(fd, s.script)
691 os.close(fd)
692 os.chmod(path, 0700)
694 env = self._get_post_scripts_env(s.inChroot)
696 if not s.inChroot:
697 env["INSTALL_ROOT"] = self._instroot
698 preexec = None
699 script = path
700 else:
701 preexec = self._chroot
702 script = "/tmp/" + os.path.basename(path)
704 try:
705 subprocess.check_call([s.interp, script],
706 preexec_fn = preexec, env = env)
707 except OSError, e:
708 raise CreatorError("Failed to execute %%post script "
709 "with '%s' : %s" % (s.interp, e.strerror))
710 except subprocess.CalledProcessError, err:
711 if s.errorOnFail:
712 raise CreatorError("%%post script failed with code %d "
713 % err.returncode)
714 logging.warning("ignoring %%post failure (code %d)"
715 % err.returncode)
716 finally:
717 os.unlink(path)
719 def configure(self):
720 """Configure the system image according to the kickstart.
722 This method applies the (e.g. keyboard or network) configuration
723 specified in the kickstart and executes the kickstart %post scripts.
725 If neccessary, it also prepares the image to be bootable by e.g.
726 creating an initrd and bootloader configuration.
729 ksh = self.ks.handler
731 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
732 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
733 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
734 kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
735 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
736 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
737 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
738 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
739 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
740 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
742 self._create_bootconfig()
744 self.__run_post_scripts()
745 kickstart.SelinuxConfig(self._instroot).apply(ksh.selinux)
747 def launch_shell(self):
748 """Launch a shell in the install root.
750 This method is launches a bash shell chroot()ed in the install root;
751 this can be useful for debugging.
754 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
756 def package(self, destdir = "."):
757 """Prepares the created image for final delivery.
759 In its simplest form, this method merely copies the install root to the
760 supplied destination directory; other subclasses may choose to package
761 the image by e.g. creating a bootable ISO containing the image and
762 bootloader configuration.
764 destdir -- the directory into which the final image should be moved;
765 this defaults to the current directory.
768 self._stage_final_image()
770 for f in os.listdir(self._outdir):
771 shutil.move(os.path.join(self._outdir, f),
772 os.path.join(destdir, f))
774 def create(self):
775 """Install, configure and package an image.
777 This method is a utility method which creates and image by calling some
778 of the other methods in the following order - mount(), install(),
779 configure(), unmount and package().
782 self.mount()
783 self.install()
784 self.configure()
785 self.unmount()
786 self.package()
788 class LoopImageCreator(ImageCreator):
789 """Installs a system into a loopback-mountable filesystem image.
791 LoopImageCreator is a straightforward ImageCreator subclass; the system
792 is installed into an ext3 filesystem on a sparse file which can be
793 subsequently loopback-mounted.
797 def __init__(self, ks, name, fslabel=None, releasever=None):
798 """Initialize a LoopImageCreator instance.
800 This method takes the same arguments as ImageCreator.__init__() with
801 the addition of:
803 fslabel -- A string used as a label for any filesystems created.
806 ImageCreator.__init__(self, ks, name, releasever=releasever)
808 self.__fslabel = None
809 self.fslabel = fslabel
811 self.__minsize_KB = 0
812 self.__blocksize = 4096
813 self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
815 self.__instloop = None
816 self.__imgdir = None
818 self.__image_size = kickstart.get_image_size(self.ks,
819 4096L * 1024 * 1024)
822 # Properties
824 def __get_fslabel(self):
825 if self.__fslabel is None:
826 return self.name
827 else:
828 return self.__fslabel
829 def __set_fslabel(self, val):
830 if val is None:
831 self.__fslabel = None
832 else:
833 self.__fslabel = val[:FSLABEL_MAXLEN]
834 fslabel = property(__get_fslabel, __set_fslabel)
835 """A string used to label any filesystems created.
837 Some filesystems impose a constraint on the maximum allowed size of the
838 filesystem label. In the case of ext3 it's 16 characters, but in the case
839 of ISO9660 it's 32 characters.
841 mke2fs silently truncates the label, but mkisofs aborts if the label is too
842 long. So, for convenience sake, any string assigned to this attribute is
843 silently truncated to FSLABEL_MAXLEN (32) characters.
847 def __get_image(self):
848 if self.__imgdir is None:
849 raise CreatorError("_image is not valid before calling mount()")
850 return self.__imgdir + "/ext3fs.img"
851 _image = property(__get_image)
852 """The location of the image file.
854 This is the path to the filesystem image. Subclasses may use this path
855 in order to package the image in _stage_final_image().
857 Note, this directory does not exist before ImageCreator.mount() is called.
859 Note also, this is a read-only attribute.
863 def __get_blocksize(self):
864 return self.__blocksize
865 def __set_blocksize(self, val):
866 if self.__instloop:
867 raise CreatorError("_blocksize must be set before calling mount()")
868 try:
869 self.__blocksize = int(val)
870 except ValueError:
871 raise CreatorError("'%s' is not a valid integer value "
872 "for _blocksize" % val)
873 _blocksize = property(__get_blocksize, __set_blocksize)
874 """The block size used by the image's filesystem.
876 This is the block size used when creating the filesystem image. Subclasses
877 may change this if they wish to use something other than a 4k block size.
879 Note, this attribute may only be set before calling mount().
883 def __get_fstype(self):
884 return self.__fstype
885 def __set_fstype(self, val):
886 if val not in ("ext2", "ext3", "ext4"):
887 raise CreatorError("Unknown _fstype '%s' supplied" % val)
888 self.__fstype = val
889 _fstype = property(__get_fstype, __set_fstype)
890 """The type of filesystem used for the image.
892 This is the filesystem type used when creating the filesystem image.
893 Subclasses may change this if they wish to use something other ext3.
895 Note, only ext2, ext3, ext4 are currently supported.
897 Note also, this attribute may only be set before calling mount().
902 # Helpers for subclasses
904 def _resparse(self, size = None):
905 """Rebuild the filesystem image to be as sparse as possible.
907 This method should be used by subclasses when staging the final image
908 in order to reduce the actual space taken up by the sparse image file
909 to be as little as possible.
911 This is done by resizing the filesystem to the minimal size (thereby
912 eliminating any space taken up by deleted files) and then resizing it
913 back to the supplied size.
915 size -- the size in, in bytes, which the filesystem image should be
916 resized to after it has been minimized; this defaults to None,
917 causing the original size specified by the kickstart file to
918 be used (or 4GiB if not specified in the kickstart).
921 return self.__instloop.resparse(size)
923 def _base_on(self, base_on):
924 shutil.copyfile(base_on, self._image)
927 # Actual implementation
929 def _mount_instroot(self, base_on = None):
930 self.__imgdir = self._mkdtemp()
932 if not base_on is None:
933 self._base_on(base_on)
935 self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image, self.__image_size),
936 self._instroot,
937 self.__fstype,
938 self.__blocksize,
939 self.fslabel)
941 try:
942 self.__instloop.mount()
943 except MountError, e:
944 raise CreatorError("Failed to loopback mount '%s' : %s" %
945 (self._image, e))
947 def _unmount_instroot(self):
948 if not self.__instloop is None:
949 self.__instloop.cleanup()
951 def _stage_final_image(self):
952 self._resparse()
953 shutil.move(self._image, self._outdir + "/" + self.name + ".img")