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.
32 from imgcreate
.errors
import *
33 from imgcreate
.fs
import *
34 from imgcreate
.yuminst
import *
35 from imgcreate
import kickstart
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.
49 ks = imgcreate.read_kickstart("foo.ks")
50 imgcreate.ImageCreator(ks, "foo").create()
54 def __init__(self
, ks
, name
, releasever
=None, tmpdir
="/tmp", useplugins
=False, cacheonly
=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
64 releasever -- Value to substitute for $releasever in repo urls
66 tmpdir -- Top level directory to use for temporary files and dirs
68 cacheonly -- Only read from cache, work offline
71 """A pykickstart.KickstartParser instance."""
74 """A name for the image."""
76 self
.releasever
= releasever
77 self
.useplugins
= useplugins
80 """The directory in which all temporary files will be created."""
81 if not os
.path
.exists(self
.tmpdir
):
84 self
.cacheonly
= cacheonly
86 self
.__builddir
= None
87 self
.__bindmounts
= []
91 # get selinuxfs mountpoint
92 self
.__selinux
_mountpoint
= "/sys/fs/selinux"
93 with
open("/proc/self/mountinfo", "r") as f
:
94 for line
in f
.readlines():
96 if fields
[-2] == "selinuxfs":
97 self
.__selinux
_mountpoint
= fields
[4]
106 def __get_instroot(self
):
107 if self
.__builddir
is None:
108 raise CreatorError("_instroot is not valid before calling mount()")
109 return self
.__builddir
+ "/install_root"
110 _instroot
= property(__get_instroot
)
111 """The location of the install root directory.
113 This is the directory into which the system is installed. Subclasses may
114 mount a filesystem image here or copy files to/from here.
116 Note, this directory does not exist before ImageCreator.mount() is called.
118 Note also, this is a read-only attribute.
122 def __get_outdir(self
):
123 if self
.__builddir
is None:
124 raise CreatorError("_outdir is not valid before calling mount()")
125 return self
.__builddir
+ "/out"
126 _outdir
= property(__get_outdir
)
127 """The staging location for the final image.
129 This is where subclasses should stage any files that are part of the final
130 image. ImageCreator.package() will copy any files found here into the
131 requested destination directory.
133 Note, this directory does not exist before ImageCreator.mount() is called.
135 Note also, this is a read-only attribute.
140 # Hooks for subclasses
142 def _mount_instroot(self
, base_on
= None):
143 """Mount or prepare the install root directory.
145 This is the hook where subclasses may prepare the install root by e.g.
146 mounting creating and loopback mounting a filesystem image to
149 There is no default implementation.
151 base_on -- this is the value passed to mount() and can be interpreted
152 as the subclass wishes; it might e.g. be the location of
153 a previously created ISO containing a system image.
158 def _unmount_instroot(self
):
159 """Undo anything performed in _mount_instroot().
161 This is the hook where subclasses must undo anything which was done
162 in _mount_instroot(). For example, if a filesystem image was mounted
163 onto _instroot, it should be unmounted here.
165 There is no default implementation.
170 def _create_bootconfig(self
):
171 """Configure the image so that it's bootable.
173 This is the hook where subclasses may prepare the image for booting by
174 e.g. creating an initramfs and bootloader configuration.
176 This hook is called while the install root is still mounted, after the
177 packages have been installed and the kickstart configuration has been
178 applied, but before the %post scripts have been executed.
180 There is no default implementation.
185 def _stage_final_image(self
):
186 """Stage the final system image in _outdir.
188 This is the hook where subclasses should place the image in _outdir
189 so that package() can copy it to the requested destination directory.
191 By default, this moves the install root into _outdir.
194 shutil
.move(self
._instroot
, self
._outdir
+ "/" + self
.name
)
196 def _get_required_packages(self
):
197 """Return a list of required packages.
199 This is the hook where subclasses may specify a set of packages which
200 it requires to be installed.
202 This returns an empty list by default.
204 Note, subclasses should usually chain up to the base class
205 implementation of this hook.
210 def _get_excluded_packages(self
):
211 """Return a list of excluded packages.
213 This is the hook where subclasses may specify a set of packages which
214 it requires _not_ to be installed.
216 This returns an empty list by default.
218 Note, subclasses should usually chain up to the base class
219 implementation of this hook.
224 def _get_fstab(self
):
225 """Return the desired contents of /etc/fstab.
227 This is the hook where subclasses may specify the contents of
228 /etc/fstab by returning a string containing the desired contents.
230 A sensible default implementation is provided.
233 s
= "/dev/root / %s defaults,noatime 0 0\n" %(self
._fstype
)
234 s
+= self
._get
_fstab
_special
()
237 def _get_fstab_special(self
):
238 s
= "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
239 s
+= "tmpfs /dev/shm tmpfs defaults 0 0\n"
240 s
+= "proc /proc proc defaults 0 0\n"
241 s
+= "sysfs /sys sysfs defaults 0 0\n"
244 def _get_post_scripts_env(self
, in_chroot
):
245 """Return an environment dict for %post scripts.
247 This is the hook where subclasses may specify some environment
248 variables for %post scripts by return a dict containing the desired
251 By default, this returns an empty dict.
253 in_chroot -- whether this %post script is to be executed chroot()ed
259 def _get_kernel_versions(self
):
260 """Return a dict detailing the available kernel types/versions.
262 This is the hook where subclasses may override what kernel types and
263 versions should be available for e.g. creating the booloader
266 A dict should be returned mapping the available kernel types to a list
267 of the available versions for those kernels.
269 The default implementation uses rpm to iterate over everything
270 providing 'kernel', finds /boot/vmlinuz-* and returns the version
271 obtained from the vmlinuz filename. (This can differ from the kernel
272 RPM's n-v-r in the case of e.g. xen)
275 def get_version(header
):
277 for f
in header
['filenames']:
278 if f
.startswith('/boot/vmlinuz-'):
282 ts
= rpm
.TransactionSet(self
._instroot
)
285 for header
in ts
.dbMatch('provides', 'kernel'):
286 version
= get_version(header
)
290 name
= header
['name']
292 ret
[name
] = [version
]
293 elif not version
in ret
[name
]:
294 ret
[name
].append(version
)
299 # Helpers for subclasses
301 def _do_bindmounts(self
):
302 """Mount various system directories onto _instroot.
304 This method is called by mount(), but may also be used by subclasses
305 in order to re-mount the bindmounts after modifying the underlying
309 for b
in self
.__bindmounts
:
312 def _undo_bindmounts(self
):
313 """Unmount the bind-mounted system directories from _instroot.
315 This method is usually only called by unmount(), but may also be used
316 by subclasses in order to gain access to the filesystem obscured by
317 the bindmounts - e.g. in order to create device nodes on the image
321 self
.__bindmounts
.reverse()
322 for b
in self
.__bindmounts
:
326 """Chroot into the install root.
328 This method may be used by subclasses when executing programs inside
329 the install root e.g.
331 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
334 os
.chroot(self
._instroot
)
337 def _mkdtemp(self
, prefix
= "tmp-"):
338 """Create a temporary directory.
340 This method may be used by subclasses to create a temporary directory
341 for use in building the final image - e.g. a subclass might create
342 a temporary directory in order to bundle a set of files into a package.
344 The subclass may delete this directory if it wishes, but it will be
345 automatically deleted by cleanup().
347 The 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 directory;
355 self
.__ensure
_builddir
()
356 return tempfile
.mkdtemp(dir = self
.__builddir
, prefix
= prefix
)
358 def _mkstemp(self
, prefix
= "tmp-"):
359 """Create a temporary file.
361 This method may be used by subclasses to create a temporary file
362 for use in building the final image - e.g. a subclass might need
363 a temporary location to unpack a compressed file.
365 The subclass may delete this file if it wishes, but it will be
366 automatically deleted by cleanup().
368 A tuple containing a file descriptor (returned from os.open() and the
369 absolute path to the temporary directory is returned.
371 Note, this method should only be called after mount() has been called.
373 prefix -- a prefix which should be used when creating the file;
377 self
.__ensure
_builddir
()
378 return tempfile
.mkstemp(dir = self
.__builddir
, prefix
= prefix
)
380 def _mktemp(self
, prefix
= "tmp-"):
381 """Create a temporary file.
383 This method simply calls _mkstemp() and closes the returned file
386 The absolute path to the temporary file is returned.
388 Note, this method should only be called after mount() has been called.
390 prefix -- a prefix which should be used when creating the file;
395 (f
, path
) = self
._mkstemp
(prefix
)
400 # Actual implementation
402 def __ensure_builddir(self
):
403 if not self
.__builddir
is None:
407 self
.__builddir
= tempfile
.mkdtemp(dir = os
.path
.abspath(self
.tmpdir
),
408 prefix
= "imgcreate-")
410 raise CreatorError("Failed create build directory in %s: %s" %
411 (self
.tmpdir
, e
.strerror
))
413 def __sanity_check(self
):
414 """Ensure that the config we've been given is sane."""
415 if not (kickstart
.get_packages(self
.ks
) or
416 kickstart
.get_groups(self
.ks
)):
417 raise CreatorError("No packages or groups specified")
419 kickstart
.convert_method_to_repo(self
.ks
)
421 if not kickstart
.get_repos(self
.ks
):
422 raise CreatorError("No repositories specified")
424 def __write_fstab(self
):
425 fstab
= open(self
._instroot
+ "/etc/fstab", "w")
426 fstab
.write(self
._get
_fstab
())
429 def __create_minimal_dev(self
):
430 """Create a minimal /dev so that we don't corrupt the host /dev"""
431 origumask
= os
.umask(0000)
432 devices
= (('null', 1, 3, 0666),
433 ('urandom',1, 9, 0666),
434 ('random', 1, 8, 0666),
435 ('full', 1, 7, 0666),
436 ('ptmx', 5, 2, 0666),
438 ('zero', 1, 5, 0666))
439 links
= (("/proc/self/fd", "/dev/fd"),
440 ("/proc/self/fd/0", "/dev/stdin"),
441 ("/proc/self/fd/1", "/dev/stdout"),
442 ("/proc/self/fd/2", "/dev/stderr"))
444 for (node
, major
, minor
, perm
) in devices
:
445 if not os
.path
.exists(self
._instroot
+ "/dev/" + node
):
446 os
.mknod(self
._instroot
+ "/dev/" + node
, perm | stat
.S_IFCHR
, os
.makedev(major
,minor
))
447 for (src
, dest
) in links
:
448 if not os
.path
.exists(self
._instroot
+ dest
):
449 os
.symlink(src
, self
._instroot
+ dest
)
452 def __create_selinuxfs(self
):
453 if not os
.path
.exists(self
.__selinux
_mountpoint
):
456 arglist
= ["/bin/mount", "--bind", "/dev/null", self
._instroot
+ self
.__selinux
_mountpoint
+ "/load"]
457 subprocess
.call(arglist
, close_fds
= True)
459 if kickstart
.selinux_enabled(self
.ks
):
460 # label the fs like it is a root before the bind mounting
461 arglist
= ["/sbin/setfiles", "-F", "-r", self
._instroot
, selinux
.selinux_file_context_path(), self
._instroot
]
462 subprocess
.call(arglist
, close_fds
= True)
463 # these dumb things don't get magically fixed, so make the user generic
464 # if selinux exists on the host we need to lie to the chroot
465 if selinux
.is_selinux_enabled():
466 for f
in ("/proc", "/sys"):
467 arglist
= ["/usr/bin/chcon", "-u", "system_u", self
._instroot
+ f
]
468 subprocess
.call(arglist
, close_fds
= True)
470 def __destroy_selinuxfs(self
):
471 if not os
.path
.exists(self
.__selinux
_mountpoint
):
474 # if the system was running selinux clean up our lies
475 arglist
= ["/bin/umount", self
._instroot
+ self
.__selinux
_mountpoint
+ "/load"]
476 subprocess
.call(arglist
, close_fds
= True)
478 def mount(self
, base_on
= None, cachedir
= None):
479 """Setup the target filesystem in preparation for an install.
481 This function sets up the filesystem which the ImageCreator will
482 install into and configure. The ImageCreator class merely creates an
483 install root directory, bind mounts some system directories (e.g. /dev)
484 and writes out /etc/fstab. Other subclasses may also e.g. create a
485 sparse file, format it and loopback mount it to the install root.
487 base_on -- a previous install on which to base this install; defaults
488 to None, causing a new image to be created
490 cachedir -- a directory in which to store the Yum cache; defaults to
491 None, causing a new cache to be created; by setting this
492 to another directory, the same cache can be reused across
496 self
.__ensure
_builddir
()
498 makedirs(self
._instroot
)
499 makedirs(self
._outdir
)
501 self
._mount
_instroot
(base_on
)
503 for d
in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc"):
504 makedirs(self
._instroot
+ d
)
506 cachesrc
= cachedir
or (self
.__builddir
+ "/yum-cache")
509 # bind mount system directories into _instroot
510 for (f
, dest
) in [("/sys", None), ("/proc", None),
511 ("/dev/pts", None), ("/dev/shm", None),
512 (self
.__selinux
_mountpoint
, self
.__selinux
_mountpoint
),
513 (cachesrc
, "/var/cache/yum")]:
514 if os
.path
.exists(f
):
515 self
.__bindmounts
.append(BindChrootMount(f
, self
._instroot
, dest
))
517 logging
.warn("Skipping (%s,%s) because source doesn't exist." % (f
, dest
))
519 self
._do
_bindmounts
()
521 self
.__create
_selinuxfs
()
523 self
.__create
_minimal
_dev
()
525 os
.symlink("/proc/self/mounts", self
._instroot
+ "/etc/mtab")
530 """Unmounts the target filesystem.
532 The ImageCreator class detaches the system from the install root, but
533 other subclasses may also detach the loopback mounted filesystem image
534 from the install root.
537 self
.__destroy
_selinuxfs
()
539 self
._undo
_bindmounts
()
541 self
._unmount
_instroot
()
544 """Unmounts the target filesystem and deletes temporary files.
546 This method calls unmount() and then deletes any temporary files and
547 directories that were created on the host system while building the
550 Note, make sure to call this method once finished with the creator
551 instance in order to ensure no stale files are left on the host e.g.:
553 creator = ImageCreator(ks, name)
560 if not self
.__builddir
:
565 shutil
.rmtree(self
.__builddir
, ignore_errors
= True)
566 self
.__builddir
= None
568 def __select_packages(self
, ayum
):
570 for pkg
in kickstart
.get_packages(self
.ks
,
571 self
._get
_required
_packages
()):
573 ayum
.selectPackage(pkg
)
574 except yum
.Errors
.InstallError
, e
:
575 if kickstart
.ignore_missing(self
.ks
):
576 skipped_pkgs
.append(pkg
)
578 raise CreatorError("Failed to find package '%s' : %s" %
581 for pkg
in skipped_pkgs
:
582 logging
.warn("Skipping missing package '%s'" % (pkg
,))
584 def __select_groups(self
, ayum
):
586 for group
in kickstart
.get_groups(self
.ks
):
588 ayum
.selectGroup(group
.name
, group
.include
)
589 except (yum
.Errors
.InstallError
, yum
.Errors
.GroupsError
), e
:
590 if kickstart
.ignore_missing(self
.ks
):
591 raise CreatorError("Failed to find group '%s' : %s" %
594 skipped_groups
.append(group
)
596 for group
in skipped_groups
:
597 logging
.warn("Skipping missing group '%s'" % (group
.name
,))
599 def __deselect_packages(self
, ayum
):
600 for pkg
in kickstart
.get_excluded(self
.ks
,
601 self
._get
_excluded
_packages
()):
602 ayum
.deselectPackage(pkg
)
604 # if the system is running selinux and the kickstart wants it disabled
605 # we need /usr/sbin/lokkit
606 def __can_handle_selinux(self
, ayum
):
607 file = "/usr/sbin/lokkit"
608 if not kickstart
.selinux_enabled(self
.ks
) and selinux
.is_selinux_enabled() and not ayum
.installHasFile(file):
609 raise CreatorError("Unable to disable SELinux because the installed package set did not include the file %s" % (file))
611 def install(self
, repo_urls
= {}):
612 """Install packages into the install root.
614 This function installs the packages listed in the supplied kickstart
615 into the install root. By default, the packages are installed from the
616 repository URLs specified in the kickstart.
618 repo_urls -- a dict which maps a repository name to a repository URL;
619 if supplied, this causes any repository URLs specified in
620 the kickstart to be overridden.
623 yum_conf
= self
._mktemp
(prefix
= "yum.conf-")
625 ayum
= LiveCDYum(releasever
=self
.releasever
, useplugins
=self
.useplugins
)
626 ayum
.setup(yum_conf
, self
._instroot
, cacheonly
=self
.cacheonly
)
628 for repo
in kickstart
.get_repos(self
.ks
, repo_urls
):
629 (name
, baseurl
, mirrorlist
, proxy
, inc
, exc
, cost
) = repo
631 yr
= ayum
.addRepository(name
, baseurl
, mirrorlist
)
640 ayum
.setup(yum_conf
, self
._instroot
)
642 if kickstart
.exclude_docs(self
.ks
):
643 rpm
.addMacro("_excludedocs", "1")
644 if not kickstart
.selinux_enabled(self
.ks
):
645 rpm
.addMacro("__file_context_path", "%{nil}")
646 if kickstart
.inst_langs(self
.ks
) != None:
647 rpm
.addMacro("_install_langs", kickstart
.inst_langs(self
.ks
))
650 self
.__select
_packages
(ayum
)
651 self
.__select
_groups
(ayum
)
652 self
.__deselect
_packages
(ayum
)
654 self
.__can
_handle
_selinux
(ayum
)
657 except yum
.Errors
.RepoError
, e
:
658 raise CreatorError("Unable to download from repo : %s" % (e
,))
659 except yum
.Errors
.YumBaseError
, e
:
660 raise CreatorError("Unable to install: %s" % (e
,))
666 # do some clean up to avoid lvm info leakage. this sucks.
667 for subdir
in ("cache", "backup", "archive"):
668 lvmdir
= self
._instroot
+ "/etc/lvm/" + subdir
670 for f
in os
.listdir(lvmdir
):
671 os
.unlink(lvmdir
+ "/" + f
)
675 def _run_post_scripts(self
):
676 for s
in kickstart
.get_post_scripts(self
.ks
):
677 (fd
, path
) = tempfile
.mkstemp(prefix
= "ks-script-",
678 dir = self
._instroot
+ "/tmp")
680 os
.write(fd
, s
.script
)
684 env
= self
._get
_post
_scripts
_env
(s
.inChroot
)
687 env
["INSTALL_ROOT"] = self
._instroot
691 preexec
= self
._chroot
692 script
= "/tmp/" + os
.path
.basename(path
)
695 subprocess
.check_call([s
.interp
, script
],
696 preexec_fn
= preexec
, env
= env
)
698 raise CreatorError("Failed to execute %%post script "
699 "with '%s' : %s" % (s
.interp
, e
.strerror
))
700 except subprocess
.CalledProcessError
, err
:
702 raise CreatorError("%%post script failed with code %d "
704 logging
.warning("ignoring %%post failure (code %d)"
710 """Configure the system image according to the kickstart.
712 This method applies the (e.g. keyboard or network) configuration
713 specified in the kickstart and executes the kickstart %post scripts.
715 If neccessary, it also prepares the image to be bootable by e.g.
716 creating an initrd and bootloader configuration.
719 ksh
= self
.ks
.handler
721 kickstart
.LanguageConfig(self
._instroot
).apply(ksh
.lang
)
722 kickstart
.KeyboardConfig(self
._instroot
).apply(ksh
.keyboard
)
723 kickstart
.TimezoneConfig(self
._instroot
).apply(ksh
.timezone
)
724 kickstart
.AuthConfig(self
._instroot
).apply(ksh
.authconfig
)
725 kickstart
.FirewallConfig(self
._instroot
).apply(ksh
.firewall
)
726 kickstart
.RootPasswordConfig(self
._instroot
).apply(ksh
.rootpw
)
727 kickstart
.ServicesConfig(self
._instroot
).apply(ksh
.services
)
728 kickstart
.XConfig(self
._instroot
).apply(ksh
.xconfig
)
729 kickstart
.NetworkConfig(self
._instroot
).apply(ksh
.network
)
730 kickstart
.RPMMacroConfig(self
._instroot
).apply(self
.ks
)
732 self
._create
_bootconfig
()
734 self
._run
_post
_scripts
()
735 kickstart
.SelinuxConfig(self
._instroot
).apply(ksh
.selinux
)
737 def launch_shell(self
):
738 """Launch a shell in the install root.
740 This method is launches a bash shell chroot()ed in the install root;
741 this can be useful for debugging.
744 subprocess
.call(["/bin/bash"], preexec_fn
= self
._chroot
)
746 def package(self
, destdir
= "."):
747 """Prepares the created image for final delivery.
749 In its simplest form, this method merely copies the install root to the
750 supplied destination directory; other subclasses may choose to package
751 the image by e.g. creating a bootable ISO containing the image and
752 bootloader configuration.
754 destdir -- the directory into which the final image should be moved;
755 this defaults to the current directory.
758 self
._stage
_final
_image
()
760 for f
in os
.listdir(self
._outdir
):
761 shutil
.move(os
.path
.join(self
._outdir
, f
),
762 os
.path
.join(destdir
, f
))
765 """Install, configure and package an image.
767 This method is a utility method which creates and image by calling some
768 of the other methods in the following order - mount(), install(),
769 configure(), unmount and package().
778 class LoopImageCreator(ImageCreator
):
779 """Installs a system into a loopback-mountable filesystem image.
781 LoopImageCreator is a straightforward ImageCreator subclass; the system
782 is installed into an ext3 filesystem on a sparse file which can be
783 subsequently loopback-mounted.
787 def __init__(self
, ks
, name
, fslabel
=None, releasever
=None, tmpdir
="/tmp", useplugins
=False, cacheonly
=False):
788 """Initialize a LoopImageCreator instance.
790 This method takes the same arguments as ImageCreator.__init__() with
793 fslabel -- A string used as a label for any filesystems created.
796 ImageCreator
.__init
__(self
, ks
, name
, releasever
=releasever
, tmpdir
=tmpdir
, useplugins
=useplugins
, cacheonly
=cacheonly
)
798 self
.__fslabel
= None
799 self
.fslabel
= fslabel
801 self
.__minsize
_KB
= 0
802 self
.__blocksize
= 4096
803 self
.__fstype
= kickstart
.get_image_fstype(self
.ks
, "ext3")
805 self
.__instloop
= None
808 self
.__image
_size
= kickstart
.get_image_size(self
.ks
,
814 def __get_fslabel(self
):
815 if self
.__fslabel
is None:
818 return self
.__fslabel
819 def __set_fslabel(self
, val
):
821 self
.__fslabel
= None
823 self
.__fslabel
= val
[:FSLABEL_MAXLEN
]
824 fslabel
= property(__get_fslabel
, __set_fslabel
)
825 """A string used to label any filesystems created.
827 Some filesystems impose a constraint on the maximum allowed size of the
828 filesystem label. In the case of ext3 it's 16 characters, but in the case
829 of ISO9660 it's 32 characters.
831 mke2fs silently truncates the label, but mkisofs aborts if the label is too
832 long. So, for convenience sake, any string assigned to this attribute is
833 silently truncated to FSLABEL_MAXLEN (32) characters.
837 def __get_image(self
):
838 if self
.__imgdir
is None:
839 raise CreatorError("_image is not valid before calling mount()")
840 return self
.__imgdir
+ "/ext3fs.img"
841 _image
= property(__get_image
)
842 """The location of the image file.
844 This is the path to the filesystem image. Subclasses may use this path
845 in order to package the image in _stage_final_image().
847 Note, this directory does not exist before ImageCreator.mount() is called.
849 Note also, this is a read-only attribute.
853 def __get_blocksize(self
):
854 return self
.__blocksize
855 def __set_blocksize(self
, val
):
857 raise CreatorError("_blocksize must be set before calling mount()")
859 self
.__blocksize
= int(val
)
861 raise CreatorError("'%s' is not a valid integer value "
862 "for _blocksize" % val
)
863 _blocksize
= property(__get_blocksize
, __set_blocksize
)
864 """The block size used by the image's filesystem.
866 This is the block size used when creating the filesystem image. Subclasses
867 may change this if they wish to use something other than a 4k block size.
869 Note, this attribute may only be set before calling mount().
873 def __get_fstype(self
):
875 def __set_fstype(self
, val
):
876 if val
not in ("ext2", "ext3", "ext4"):
877 raise CreatorError("Unknown _fstype '%s' supplied" % val
)
879 _fstype
= property(__get_fstype
, __set_fstype
)
880 """The type of filesystem used for the image.
882 This is the filesystem type used when creating the filesystem image.
883 Subclasses may change this if they wish to use something other ext3.
885 Note, only ext2, ext3, ext4 are currently supported.
887 Note also, this attribute may only be set before calling mount().
892 # Helpers for subclasses
894 def _resparse(self
, size
= None):
895 """Rebuild the filesystem image to be as sparse as possible.
897 This method should be used by subclasses when staging the final image
898 in order to reduce the actual space taken up by the sparse image file
899 to be as little as possible.
901 This is done by resizing the filesystem to the minimal size (thereby
902 eliminating any space taken up by deleted files) and then resizing it
903 back to the supplied size.
905 size -- the size in, in bytes, which the filesystem image should be
906 resized to after it has been minimized; this defaults to None,
907 causing the original size specified by the kickstart file to
908 be used (or 4GiB if not specified in the kickstart).
911 return self
.__instloop
.resparse(size
)
913 def _base_on(self
, base_on
):
914 shutil
.copyfile(base_on
, self
._image
)
917 # Actual implementation
919 def _mount_instroot(self
, base_on
= None):
920 self
.__imgdir
= self
._mkdtemp
()
922 if not base_on
is None:
923 self
._base
_on
(base_on
)
925 self
.__instloop
= ExtDiskMount(SparseLoopbackDisk(self
._image
,
934 self
.__instloop
.mount()
935 except MountError
, e
:
936 raise CreatorError("Failed to loopback mount '%s' : %s" %
939 def _unmount_instroot(self
):
940 if not self
.__instloop
is None:
941 self
.__instloop
.cleanup()
943 def _stage_final_image(self
):
945 shutil
.move(self
._image
, self
._outdir
+ "/" + self
.name
+ ".img")