Fix, --tmpdir relative path
[livecd/EL-5.git] / imgcreate / creator.py
blob82db284d16ab9a6cabca28a55d25a5f1cd08a51a
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
27 import selinux
28 import yum
29 import rpm
31 from imgcreate.errors import *
32 from imgcreate.fs import *
33 from imgcreate.yuminst import *
34 from imgcreate import kickstart
36 FSLABEL_MAXLEN = 32
37 """The maximum string length supported for LoopImageCreator.fslabel."""
39 class ImageCreator(object):
40 """Installs a system to a chroot directory.
42 ImageCreator is the simplest creator class available; it will install and
43 configure a system image according to the supplied kickstart file.
45 e.g.
47 import imgcreate
48 ks = imgcreate.read_kickstart("foo.ks")
49 imgcreate.ImageCreator(ks, "foo").create()
51 """
53 def __init__(self, ks, name):
54 """Initialize an ImageCreator instance.
56 ks -- a pykickstart.KickstartParser instance; this instance will be
57 used to drive the install by e.g. providing the list of packages
58 to be installed, the system configuration and %post scripts
60 name -- a name for the image; used for e.g. image filenames or
61 filesystem labels
63 """
64 self.ks = ks
65 """A pykickstart.KickstartParser instance."""
67 self.name = name
68 """A name for the image."""
70 self.tmpdir = "/var/tmp"
71 """The directory in which all temporary files will be created."""
73 self.__builddir = None
74 self.__bindmounts = []
76 self.__sanity_check()
78 def __del__(self):
79 self.cleanup()
82 # Properties
84 def __get_instroot(self):
85 if self.__builddir is None:
86 raise CreatorError("_instroot is not valid before calling mount()")
87 return self.__builddir + "/install_root"
88 _instroot = property(__get_instroot)
89 """The location of the install root directory.
91 This is the directory into which the system is installed. Subclasses may
92 mount a filesystem image here or copy files to/from here.
94 Note, this directory does not exist before ImageCreator.mount() is called.
96 Note also, this is a read-only attribute.
98 """
100 def __get_outdir(self):
101 if self.__builddir is None:
102 raise CreatorError("_outdir is not valid before calling mount()")
103 return self.__builddir + "/out"
104 _outdir = property(__get_outdir)
105 """The staging location for the final image.
107 This is where subclasses should stage any files that are part of the final
108 image. ImageCreator.package() will copy any files found here into the
109 requested destination directory.
111 Note, this directory does not exist before ImageCreator.mount() is called.
113 Note also, this is a read-only attribute.
118 # Hooks for subclasses
120 def _mount_instroot(self, base_on = None):
121 """Mount or prepare the install root directory.
123 This is the hook where subclasses may prepare the install root by e.g.
124 mounting creating and loopback mounting a filesystem image to
125 _instroot.
127 There is no default implementation.
129 base_on -- this is the value passed to mount() and can be interpreted
130 as the subclass wishes; it might e.g. be the location of
131 a previously created ISO containing a system image.
134 pass
136 def _unmount_instroot(self):
137 """Undo anything performed in _mount_instroot().
139 This is the hook where subclasses must undo anything which was done
140 in _mount_instroot(). For example, if a filesystem image was mounted
141 onto _instroot, it should be unmounted here.
143 There is no default implementation.
146 pass
148 def _create_bootconfig(self):
149 """Configure the image so that it's bootable.
151 This is the hook where subclasses may prepare the image for booting by
152 e.g. creating an initramfs and bootloader configuration.
154 This hook is called while the install root is still mounted, after the
155 packages have been installed and the kickstart configuration has been
156 applied, but before the %post scripts have been executed.
158 There is no default implementation.
161 pass
163 def _stage_final_image(self):
164 """Stage the final system image in _outdir.
166 This is the hook where subclasses should place the image in _outdir
167 so that package() can copy it to the requested destination directory.
169 By default, this moves the install root into _outdir.
172 shutil.move(self._instroot, self._outdir + "/" + self.name)
174 def _get_required_packages(self):
175 """Return a list of required packages.
177 This is the hook where subclasses may specify a set of packages which
178 it requires to be installed.
180 This returns an empty list by default.
182 Note, subclasses should usually chain up to the base class
183 implementation of this hook.
186 return []
188 def _get_excluded_packages(self):
189 """Return a list of excluded packages.
191 This is the hook where subclasses may specify a set of packages which
192 it requires _not_ to be installed.
194 This returns an empty list by default.
196 Note, subclasses should usually chain up to the base class
197 implementation of this hook.
200 return []
202 def _get_fstab(self):
203 """Return the desired contents of /etc/fstab.
205 This is the hook where subclasses may specify the contents of
206 /etc/fstab by returning a string containing the desired contents.
208 A sensible default implementation is provided.
211 s = "/dev/root / %s defaults,noatime 0 0\n" %(self._fstype)
212 s += self._get_fstab_special()
213 return s
215 def _get_fstab_special(self):
216 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
217 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
218 s += "proc /proc proc defaults 0 0\n"
219 s += "sysfs /sys sysfs defaults 0 0\n"
220 return s
222 def _get_post_scripts_env(self, in_chroot):
223 """Return an environment dict for %post scripts.
225 This is the hook where subclasses may specify some environment
226 variables for %post scripts by return a dict containing the desired
227 environment.
229 By default, this returns an empty dict.
231 in_chroot -- whether this %post script is to be executed chroot()ed
232 into _instroot.
235 return {}
237 def _get_kernel_versions(self):
238 """Return a dict detailing the available kernel types/versions.
240 This is the hook where subclasses may override what kernel types and
241 versions should be available for e.g. creating the booloader
242 configuration.
244 A dict should be returned mapping the available kernel types to a list
245 of the available versions for those kernels.
247 The default implementation uses rpm to iterate over everything
248 providing 'kernel', finds /boot/vmlinuz-* and returns the version
249 obtained from the vmlinuz filename. (This can differ from the kernel
250 RPM's n-v-r in the case of e.g. xen)
253 def get_version(header):
254 version = None
255 for f in header['filenames']:
256 if f.startswith('/boot/vmlinuz-'):
257 version = f[14:]
258 return version
260 ts = rpm.TransactionSet(self._instroot)
262 ret = {}
263 for header in ts.dbMatch('provides', 'kernel'):
264 version = get_version(header)
265 if version is None:
266 continue
268 name = header['name']
269 if not name in ret:
270 ret[name] = [version]
271 elif not version in ret[name]:
272 ret[name].append(version)
274 return ret
277 # Helpers for subclasses
279 def _do_bindmounts(self):
280 """Mount various system directories onto _instroot.
282 This method is called by mount(), but may also be used by subclasses
283 in order to re-mount the bindmounts after modifying the underlying
284 filesystem.
287 for b in self.__bindmounts:
288 b.mount()
290 def _undo_bindmounts(self):
291 """Unmount the bind-mounted system directories from _instroot.
293 This method is usually only called by unmount(), but may also be used
294 by subclasses in order to gain access to the filesystem obscured by
295 the bindmounts - e.g. in order to create device nodes on the image
296 filesystem.
299 self.__bindmounts.reverse()
300 for b in self.__bindmounts:
301 b.unmount()
303 def _chroot(self):
304 """Chroot into the install root.
306 This method may be used by subclasses when executing programs inside
307 the install root e.g.
309 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
312 os.chroot(self._instroot)
313 os.chdir("/")
315 def _mkdtemp(self, prefix = "tmp-"):
316 """Create a temporary directory.
318 This method may be used by subclasses to create a temporary directory
319 for use in building the final image - e.g. a subclass might create
320 a temporary directory in order to bundle a set of files into a package.
322 The subclass may delete this directory if it wishes, but it will be
323 automatically deleted by cleanup().
325 The absolute path to the temporary directory is returned.
327 Note, this method should only be called after mount() has been called.
329 prefix -- a prefix which should be used when creating the directory;
330 defaults to "tmp-".
333 self.__ensure_builddir()
334 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
336 def _mkstemp(self, prefix = "tmp-"):
337 """Create a temporary file.
339 This method may be used by subclasses to create a temporary file
340 for use in building the final image - e.g. a subclass might need
341 a temporary location to unpack a compressed file.
343 The subclass may delete this file if it wishes, but it will be
344 automatically deleted by cleanup().
346 A tuple containing a file descriptor (returned from os.open() and the
347 absolute path to the temporary directory is returned.
349 Note, this method should only be called after mount() has been called.
351 prefix -- a prefix which should be used when creating the file;
352 defaults to "tmp-".
355 self.__ensure_builddir()
356 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
358 def _mktemp(self, prefix = "tmp-"):
359 """Create a temporary file.
361 This method simply calls _mkstemp() and closes the returned file
362 descriptor.
364 The absolute path to the temporary file is returned.
366 Note, this method should only be called after mount() has been called.
368 prefix -- a prefix which should be used when creating the file;
369 defaults to "tmp-".
373 (f, path) = self._mkstemp(prefix)
374 os.close(f)
375 return path
378 # Actual implementation
380 def __ensure_builddir(self):
381 if not self.__builddir is None:
382 return
384 try:
385 self.__builddir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir),
386 prefix = "imgcreate-")
387 except OSError, (err, msg):
388 raise CreatorError("Failed create build directory in %s: %s" %
389 (self.tmpdir, msg))
391 def __sanity_check(self):
392 """Ensure that the config we've been given is sane."""
393 if not (kickstart.get_packages(self.ks) or
394 kickstart.get_groups(self.ks)):
395 raise CreatorError("No packages or groups specified")
397 kickstart.convert_method_to_repo(self.ks)
399 if not kickstart.get_repos(self.ks):
400 raise CreatorError("No repositories specified")
402 if (kickstart.selinux_enabled(self.ks) and
403 not os.path.exists("/selinux/enforce")):
404 raise CreatorError("SELinux requested but not enabled on host")
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 __create_selinuxfs(self):
435 # if selinux exists on the host we need to lie to the chroot
436 if os.path.exists("/selinux/enforce"):
437 selinux_dir = self._instroot + "/selinux"
439 # enforce=0 tells the chroot selinux is not enforcing
440 # policyvers=999 tell the chroot to make the highest version of policy it can
441 files = (('/enforce', '0'),
442 ('/policyvers', '999'))
443 for (file, value) in files:
444 fd = os.open(selinux_dir + file, os.O_WRONLY | os.O_TRUNC | os.O_CREAT)
445 os.write(fd, value)
446 os.close(fd)
448 # we steal mls from the host system for now, might be best to always set it to 1????
449 files = ("/mls",)
450 for file in files:
451 shutil.copyfile("/selinux" + file, selinux_dir + file)
453 # make /load -> /dev/null so chroot policy loads don't hurt anything
454 os.mknod(selinux_dir + "/load", 0666 | stat.S_IFCHR, os.makedev(1, 3))
456 # selinux is on in the kickstart, so clean up as best we can to start
457 if kickstart.selinux_enabled(self.ks):
458 # label the fs like it is a root before the bind mounting
459 arglist = ["/sbin/setfiles", "-F", "-r", self._instroot, selinux.selinux_file_context_path(), self._instroot]
460 subprocess.call(arglist, close_fds = True)
461 # these dumb things don't get magically fixed, so make the user generic
462 for f in ("/proc", "/sys", "/selinux"):
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 the system was running selinux clean up our lies
468 if os.path.exists("/selinux/enforce"):
469 files = ('/enforce',
470 '/policyvers',
471 '/mls',
472 '/load')
473 for file in files:
474 try:
475 os.unlink(self._instroot + "/selinux" + file)
476 except OSError:
477 pass
480 def mount(self, base_on = None, cachedir = None):
481 """Setup the target filesystem in preparation for an install.
483 This function sets up the filesystem which the ImageCreator will
484 install into and configure. The ImageCreator class merely creates an
485 install root directory, bind mounts some system directories (e.g. /dev)
486 and writes out /etc/fstab. Other subclasses may also e.g. create a
487 sparse file, format it and loopback mount it to the install root.
489 base_on -- a previous install on which to base this install; defaults
490 to None, causing a new image to be created
492 cachedir -- a directory in which to store the Yum cache; defaults to
493 None, causing a new cache to be created; by setting this
494 to another directory, the same cache can be reused across
495 multiple installs.
498 self.__ensure_builddir()
500 makedirs(self._instroot)
501 makedirs(self._outdir)
503 self._mount_instroot(base_on)
505 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/selinux"):
506 makedirs(self._instroot + d)
508 cachesrc = cachedir or (self.__builddir + "/yum-cache")
509 makedirs(cachesrc)
511 # bind mount system directories into _instroot
512 for (f, dest) in [("/sys", None), ("/proc", None),
513 ("/dev/pts", None),
514 (cachesrc, "/var/cache/yum")]:
515 self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))
517 self.__create_selinuxfs()
519 self._do_bindmounts()
521 self.__create_minimal_dev()
523 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
525 self.__write_fstab()
527 def unmount(self):
528 """Unmounts the target filesystem.
530 The ImageCreator class detaches the system from the install root, but
531 other subclasses may also detach the loopback mounted filesystem image
532 from the install root.
535 try:
536 os.unlink(self._instroot + "/etc/mtab")
537 except OSError:
538 pass
540 self.__destroy_selinuxfs()
542 self._undo_bindmounts()
544 self._unmount_instroot()
546 def cleanup(self):
547 """Unmounts the target filesystem and deletes temporary files.
549 This method calls unmount() and then deletes any temporary files and
550 directories that were created on the host system while building the
551 image.
553 Note, make sure to call this method once finished with the creator
554 instance in order to ensure no stale files are left on the host e.g.:
556 creator = ImageCreator(ks, name)
557 try:
558 creator.create()
559 finally:
560 creator.cleanup()
563 if not self.__builddir:
564 return
566 self.unmount()
568 shutil.rmtree(self.__builddir, ignore_errors = True)
569 self.__builddir = None
571 def __select_packages(self, ayum):
572 skipped_pkgs = []
573 for pkg in kickstart.get_packages(self.ks,
574 self._get_required_packages()):
575 try:
576 ayum.selectPackage(pkg)
577 except yum.Errors.InstallError, e:
578 if kickstart.ignore_missing(self.ks):
579 skipped_pkgs.append(pkg)
580 else:
581 raise CreatorError("Failed to find package '%s' : %s" %
582 (pkg, e))
584 for pkg in skipped_pkgs:
585 logging.warn("Skipping missing package '%s'" % (pkg,))
587 def __select_groups(self, ayum):
588 skipped_groups = []
589 for group in kickstart.get_groups(self.ks):
590 try:
591 ayum.selectGroup(group.name, group.include)
592 except (yum.Errors.InstallError, yum.Errors.GroupsError), e:
593 if kickstart.ignore_missing(self.ks):
594 raise CreatorError("Failed to find group '%s' : %s" %
595 (group.name, e))
596 else:
597 skipped_groups.append(group)
599 for group in skipped_groups:
600 logging.warn("Skipping missing group '%s'" % (group.name,))
602 def __deselect_packages(self, ayum):
603 for pkg in kickstart.get_excluded(self.ks,
604 self._get_excluded_packages()):
605 ayum.deselectPackage(pkg)
607 # if the system is running selinux and the kickstart wants it disabled
608 # we need /usr/sbin/lokkit
609 def __can_handle_selinux(self, ayum):
610 file = "/usr/sbin/lokkit"
611 if not kickstart.selinux_enabled(self.ks) and os.path.exists("/selinux/enforce") and not ayum.installHasFile(file):
612 raise CreatorError("Unable to disable SELinux because the installed package set did not include the file %s" % (file))
614 def install(self, repo_urls = {}):
615 """Install packages into the install root.
617 This function installs the packages listed in the supplied kickstart
618 into the install root. By default, the packages are installed from the
619 repository URLs specified in the kickstart.
621 repo_urls -- a dict which maps a repository name to a repository URL;
622 if supplied, this causes any repository URLs specified in
623 the kickstart to be overridden.
626 yum_conf = self._mktemp(prefix = "yum.conf-")
628 ayum = LiveCDYum()
629 ayum.setup(yum_conf, self._instroot)
631 for repo in kickstart.get_repos(self.ks, repo_urls):
632 (name, baseurl, mirrorlist, inc, exc) = repo
634 yr = ayum.addRepository(name, baseurl, mirrorlist)
635 if inc:
636 yr.includepkgs = inc
637 if exc:
638 yr.exclude = exc
640 if kickstart.exclude_docs(self.ks):
641 rpm.addMacro("_excludedocs", "1")
642 if not kickstart.selinux_enabled(self.ks):
643 rpm.addMacro("__file_context_path", "%{nil}")
644 if kickstart.inst_langs(self.ks) != None:
645 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
647 try:
648 self.__select_packages(ayum)
649 self.__select_groups(ayum)
650 self.__deselect_packages(ayum)
652 self.__can_handle_selinux(ayum)
654 ayum.runInstall()
655 except yum.Errors.RepoError, e:
656 raise CreatorError("Unable to download from repo : %s" % (e,))
657 except yum.Errors.YumBaseError, e:
658 raise CreatorError("Unable to install: %s" % (e,))
659 finally:
660 ayum.closeRpmDB()
661 ayum.close()
662 os.unlink(yum_conf)
664 # do some clean up to avoid lvm info leakage. this sucks.
665 for subdir in ("cache", "backup", "archive"):
666 lvmdir = self._instroot + "/etc/lvm/" + subdir
667 try:
668 for f in os.listdir(lvmdir):
669 os.unlink(lvmdir + "/" + f)
670 except:
671 pass
673 def __run_post_scripts(self):
674 for s in kickstart.get_post_scripts(self.ks):
675 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
676 dir = self._instroot + "/tmp")
678 os.write(fd, s.script)
679 os.close(fd)
680 os.chmod(path, 0700)
682 env = self._get_post_scripts_env(s.inChroot)
684 if not s.inChroot:
685 env["INSTALL_ROOT"] = self._instroot
686 preexec = None
687 script = path
688 else:
689 preexec = self._chroot
690 script = "/tmp/" + os.path.basename(path)
692 try:
693 subprocess.call([s.interp, script],
694 preexec_fn = preexec, env = env)
695 except OSError, (err, msg):
696 raise CreatorError("Failed to execute %%post script "
697 "with '%s' : %s" % (s.interp, msg))
698 finally:
699 os.unlink(path)
701 def configure(self):
702 """Configure the system image according to the kickstart.
704 This method applies the (e.g. keyboard or network) configuration
705 specified in the kickstart and executes the kickstart %post scripts.
707 If neccessary, it also prepares the image to be bootable by e.g.
708 creating an initrd and bootloader configuration.
711 ksh = self.ks.handler
713 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
714 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
715 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
716 kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
717 kickstart.SelinuxConfig(self._instroot).apply(ksh.selinux)
718 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
719 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
720 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
721 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
722 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
723 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
725 self._create_bootconfig()
727 self.__run_post_scripts()
729 def launch_shell(self):
730 """Launch a shell in the install root.
732 This method is launches a bash shell chroot()ed in the install root;
733 this can be useful for debugging.
736 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
738 def package(self, destdir = "."):
739 """Prepares the created image for final delivery.
741 In its simplest form, this method merely copies the install root to the
742 supplied destination directory; other subclasses may choose to package
743 the image by e.g. creating a bootable ISO containing the image and
744 bootloader configuration.
746 destdir -- the directory into which the final image should be moved;
747 this defaults to the current directory.
750 self._stage_final_image()
752 for f in os.listdir(self._outdir):
753 shutil.move(os.path.join(self._outdir, f),
754 os.path.join(destdir, f))
756 def create(self):
757 """Install, configure and package an image.
759 This method is a utility method which creates and image by calling some
760 of the other methods in the following order - mount(), install(),
761 configure(), unmount and package().
764 self.mount()
765 self.install()
766 self.configure()
767 self.unmount()
768 self.package()
770 class LoopImageCreator(ImageCreator):
771 """Installs a system into a loopback-mountable filesystem image.
773 LoopImageCreator is a straightforward ImageCreator subclass; the system
774 is installed into an ext3 filesystem on a sparse file which can be
775 subsequently loopback-mounted.
779 def __init__(self, ks, name, fslabel = None):
780 """Initialize a LoopImageCreator instance.
782 This method takes the same arguments as ImageCreator.__init__() with
783 the addition of:
785 fslabel -- A string used as a label for any filesystems created.
788 ImageCreator.__init__(self, ks, name)
790 self.__fslabel = None
791 self.fslabel = fslabel
793 self.__minsize_KB = 0
794 self.__blocksize = 4096
795 self.__fstype = kickstart.get_image_fstype(self.ks, "ext3")
797 self.__instloop = None
798 self.__imgdir = None
800 self.__image_size = kickstart.get_image_size(self.ks,
801 4096L * 1024 * 1024)
804 # Properties
806 def __get_fslabel(self):
807 if self.__fslabel is None:
808 return self.name
809 else:
810 return self.__fslabel
811 def __set_fslabel(self, val):
812 if val is None:
813 self.__fslabel = None
814 else:
815 self.__fslabel = val[:FSLABEL_MAXLEN]
816 fslabel = property(__get_fslabel, __set_fslabel)
817 """A string used to label any filesystems created.
819 Some filesystems impose a constraint on the maximum allowed size of the
820 filesystem label. In the case of ext3 it's 16 characters, but in the case
821 of ISO9660 it's 32 characters.
823 mke2fs silently truncates the label, but mkisofs aborts if the label is too
824 long. So, for convenience sake, any string assigned to this attribute is
825 silently truncated to FSLABEL_MAXLEN (32) characters.
829 def __get_image(self):
830 if self.__imgdir is None:
831 raise CreatorError("_image is not valid before calling mount()")
832 return self.__imgdir + "/ext3fs.img"
833 _image = property(__get_image)
834 """The location of the image file.
836 This is the path to the filesystem image. Subclasses may use this path
837 in order to package the image in _stage_final_image().
839 Note, this directory does not exist before ImageCreator.mount() is called.
841 Note also, this is a read-only attribute.
845 def __get_blocksize(self):
846 return self.__blocksize
847 def __set_blocksize(self, val):
848 if self.__instloop:
849 raise CreatorError("_blocksize must be set before calling mount()")
850 try:
851 self.__blocksize = int(val)
852 except ValueError:
853 raise CreatorError("'%s' is not a valid integer value "
854 "for _blocksize" % val)
855 _blocksize = property(__get_blocksize, __set_blocksize)
856 """The block size used by the image's filesystem.
858 This is the block size used when creating the filesystem image. Subclasses
859 may change this if they wish to use something other than a 4k block size.
861 Note, this attribute may only be set before calling mount().
865 def __get_fstype(self):
866 return self.__fstype
867 def __set_fstype(self, val):
868 if val not in ("ext2", "ext3", "ext4"):
869 raise CreatorError("Unknown _fstype '%s' supplied" % val)
870 self.__fstype = val
871 _fstype = property(__get_fstype, __set_fstype)
872 """The type of filesystem used for the image.
874 This is the filesystem type used when creating the filesystem image.
875 Subclasses may change this if they wish to use something other ext3.
877 Note, only ext2, ext3, ext4 are currently supported.
879 Note also, this attribute may only be set before calling mount().
884 # Helpers for subclasses
886 def _resparse(self, size = None):
887 """Rebuild the filesystem image to be as sparse as possible.
889 This method should be used by subclasses when staging the final image
890 in order to reduce the actual space taken up by the sparse image file
891 to be as little as possible.
893 This is done by resizing the filesystem to the minimal size (thereby
894 eliminating any space taken up by deleted files) and then resizing it
895 back to the supplied size.
897 size -- the size in, in bytes, which the filesystem image should be
898 resized to after it has been minimized; this defaults to None,
899 causing the original size specified by the kickstart file to
900 be used (or 4GiB if not specified in the kickstart).
903 return self.__instloop.resparse(size)
905 def _base_on(self, base_on):
906 shutil.copyfile(base_on, self._image)
909 # Actual implementation
911 def _mount_instroot(self, base_on = None):
912 self.__imgdir = self._mkdtemp()
914 if not base_on is None:
915 self._base_on(base_on)
917 self.__instloop = ExtDiskMount(SparseLoopbackDisk(self._image, self.__image_size),
918 self._instroot,
919 self.__fstype,
920 self.__blocksize,
921 self.fslabel)
923 try:
924 self.__instloop.mount()
925 except MountError, e:
926 raise CreatorError("Failed to loopback mount '%s' : %s" %
927 (self._image, e))
929 def _unmount_instroot(self):
930 if not self.__instloop is None:
931 self.__instloop.cleanup()
933 def _stage_final_image(self):
934 self._resparse()
935 shutil.move(self._image, self._outdir + "/" + self.name + ".img")