+# -*- coding: utf-8 -*-
+#
# Copyright 2013 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
class Image(object):
"""The instances of this class can create images out of block devices."""
- def __init__(self, device, output, bootable=True, meta={}):
+ def __init__(self, device, output, **kargs):
"""Create a new Image instance"""
self.device = device
self.out = output
- self.bootable = bootable
- self.meta = meta
+
+ self.meta = kargs['meta'] if 'meta' in kargs else {}
+ self.sysprep_params = \
+ kargs['sysprep_params'] if 'sysprep_params' in kargs else {}
+
self.progress_bar = None
self.guestfs_device = None
self.size = 0
- self.mounted = False
- self.mounted_ro = False
self.g = guestfs.GuestFS()
- self.g.add_drive_opts(self.device, readonly=0, format="raw")
+ self.guestfs_enabled = False
+ self.guestfs_version = self.g.version()
- # Before version 1.17.14 the recovery process, which is a fork of the
- # original process that called libguestfs, did not close its inherited
- # file descriptors. This can cause problems especially if the parent
- # process has opened pipes. Since the recovery process is an optional
- # feature of libguestfs, it's better to disable it.
- self.g.set_recovery_proc(0)
- version = self.g.version()
- if version['major'] > 1 or \
- (version['major'] == 1 and (version['minor'] >= 18 or
- (version['minor'] == 17 and
- version['release'] >= 14))):
- self.g.set_recovery_proc(1)
- self.out.output("Enabling recovery proc")
+ def check_guestfs_version(self, major, minor, release):
+ """Checks if the version of the used libguestfs is smaller, equal or
+ greater than the one specified by the major, minor and release triplet
- #self.g.set_trace(1)
- #self.g.set_verbose(1)
+ Returns:
+ < 0 if the installed version is smaller than the specified one
+ = 0 if they are equal
+ > 0 if the installed one is greater than the specified one
+ """
- self.guestfs_enabled = False
+ for (a, b) in (self.guestfs_version['major'], major), \
+ (self.guestfs_version['minor'], minor), \
+ (self.guestfs_version['release'], release):
+ if a != b:
+ return a - b
+
+ return 0
def enable(self):
"""Enable a newly created Image instance"""
- self.out.output('Launching helper VM (may take a while) ...', False)
- # self.progressbar = self.out.Progress(100, "Launching helper VM",
- # "percent")
- # eh = self.g.set_event_callback(self.progress_callback,
- # guestfs.EVENT_PROGRESS)
- self.g.launch()
- self.guestfs_enabled = True
- # self.g.delete_event_callback(eh)
- # self.progressbar.success('done')
- # self.progressbar = None
- self.out.success('done')
+ self.enable_guestfs()
self.out.output('Inspecting Operating System ...', False)
roots = self.g.inspect_os()
'found a(n) %s system' %
self.ostype if self.distro == "unknown" else self.distro)
+ def enable_guestfs(self):
+ """Enable the guestfs handler"""
+
+ if self.guestfs_enabled:
+ self.out.warn("Guestfs is already enabled")
+ return
+
+ # Before version 1.18.4 the behaviour of kill_subprocess was different
+ # and you need to reset the guestfs handler to relaunch a previously
+ # shut down qemu backend
+ if self.check_guestfs_version(1, 18, 4) < 0:
+ self.g = guestfs.GuestFS()
+
+ self.g.add_drive_opts(self.device, readonly=0, format="raw")
+
+ # Before version 1.17.14 the recovery process, which is a fork of the
+ # original process that called libguestfs, did not close its inherited
+ # file descriptors. This can cause problems especially if the parent
+ # process has opened pipes. Since the recovery process is an optional
+ # feature of libguestfs, it's better to disable it.
+ if self.check_guestfs_version(1, 17, 14) >= 0:
+ self.out.output("Enabling recovery proc")
+ self.g.set_recovery_proc(1)
+ else:
+ self.g.set_recovery_proc(0)
+
+ #self.g.set_trace(1)
+ #self.g.set_verbose(1)
+
+ self.out.output('Launching helper VM (may take a while) ...', False)
+ # self.progressbar = self.out.Progress(100, "Launching helper VM",
+ # "percent")
+ # eh = self.g.set_event_callback(self.progress_callback,
+ # guestfs.EVENT_PROGRESS)
+ self.g.launch()
+ self.guestfs_enabled = True
+ # self.g.delete_event_callback(eh)
+ # self.progressbar.success('done')
+ # self.progressbar = None
+
+ if self.check_guestfs_version(1, 18, 4) < 0:
+ self.g.inspect_os() # some calls need this
+
+ self.out.success('done')
+
+ def disable_guestfs(self):
+ """Disable the guestfs handler"""
+
+ if not self.guestfs_enabled:
+ self.out.warn("Guestfs is already disabled")
+ return
+
+ self.out.output("Shutting down helper VM ...", False)
+ self.g.sync()
+ # guestfs_shutdown which is the prefered way to shutdown the backend
+ # process was introduced in version 1.19.16
+ if self.check_guestfs_version(1, 19, 16) >= 0:
+ self.g.shutdown()
+ else:
+ self.g.kill_subprocess()
+
+ self.guestfs_enabled = False
+ self.out.success('done')
+
def _get_os(self):
"""Return an OS class instance for this image"""
if hasattr(self, "_os"):
if not self.guestfs_enabled:
self.enable()
- if not self.mounted:
- do_unmount = True
- self.mount(readonly=True)
- else:
- do_unmount = False
+ cls = os_cls(self.distro, self.ostype)
+ self._os = cls(self, sysprep_params=self.sysprep_params)
- try:
- cls = os_cls(self.distro, self.ostype)
- self._os = cls(self.root, self.g, self.out)
-
- finally:
- if do_unmount:
- self.umount()
+ self._os.collect_metadata()
return self._os
#
# self.progressbar.goto((position * 100) // total)
- def mount(self, readonly=False):
- """Mount all disk partitions in a correct order."""
-
- msg = "Mounting the media%s ..." % (" read-only" if readonly else "")
- self.out.output(msg, False)
-
- #If something goes wrong when mounting rw, remount the filesystem ro
- remount_ro = False
- rw_mpoints = ('/', '/etc', '/root', '/home', '/var')
-
- # Sort the keys to mount the fs in a correct order.
- # / should be mounted befor /boot, etc
- def compare(a, b):
- if len(a[0]) > len(b[0]):
- return 1
- elif len(a[0]) == len(b[0]):
- return 0
- else:
- return -1
- mps = self.g.inspect_get_mountpoints(self.root)
- mps.sort(compare)
-
- mopts = 'ro' if readonly else 'rw'
- for mp, dev in mps:
- if self.ostype == 'freebsd':
- # libguestfs can't handle correct freebsd partitions on GUID
- # Partition Table. We have to do the translation to linux
- # device names ourselves
- m = re.match('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$', dev)
- if m:
- m2 = int(m.group(2))
- m3 = int(m.group(3))
- dev = '/dev/sd%c%d' % (chr(ord('a') + m2), m3)
- try:
- self.g.mount_options(mopts, dev, mp)
- except RuntimeError as msg:
- if self.ostype == 'freebsd':
- freebsd_mopts = "ufstype=ufs2,%s" % mopts
- try:
- self.g.mount_vfs(freebsd_mopts, 'ufs', dev, mp)
- except RuntimeError as msg:
- if readonly is False and mp in rw_mpoints:
- remount_ro = True
- break
- elif readonly is False and mp in rw_mpoints:
- remount_ro = True
- break
- else:
- self.out.warn("%s (ignored)" % msg)
- if remount_ro:
- self.out.warn("Unable to mount %s read-write. "
- "Remounting everything read-only..." % mp)
- self.umount()
- self.mount(True)
- else:
- self.mounted = True
- self.mounted_ro = readonly
- self.out.success("done")
-
- def umount(self):
- """Umount all mounted filesystems."""
- self.g.umount_all()
- self.mounted = False
-
def _last_partition(self):
"""Return the last partition of the image disk"""
if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':