Support mounting freebsd ufs filesystems
[snf-image-creator] / image_creator / image.py
index 3393efe..39a6f73 100644 (file)
@@ -44,7 +44,7 @@ class Image(object):
     """The instances of this class can create images out of block devices."""
 
     def __init__(self, device, output, bootable=True, meta={}):
-        """Create a new ImageCreator."""
+        """Create a new Image instance"""
 
         self.device = device
         self.out = output
@@ -54,6 +54,7 @@ class Image(object):
         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")
@@ -78,7 +79,7 @@ class Image(object):
         self.guestfs_enabled = False
 
     def enable(self):
-        """Enable a newly created ImageCreator"""
+        """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",
@@ -107,9 +108,12 @@ class Image(object):
 
         self.ostype = self.g.inspect_get_type(self.root)
         self.distro = self.g.inspect_get_distro(self.root)
-        self.out.success('found a(n) %s system' % self.distro)
+        self.out.success(
+            'found a(n) %s system' %
+            self.ostype if self.distro == "unknown" else self.distro)
 
     def _get_os(self):
+        """Return an OS class instance for this image"""
         if hasattr(self, "_os"):
             return self._os
 
@@ -135,7 +139,7 @@ class Image(object):
     os = property(_get_os)
 
     def destroy(self):
-        """Destroy this ImageCreator instance."""
+        """Destroy this Image instance."""
 
         # In new guestfs versions, there is a handy shutdown method for this
         try:
@@ -155,10 +159,12 @@ class Image(object):
     def mount(self, readonly=False):
         """Mount all disk partitions in a correct order."""
 
-        mount = self.g.mount_ro if readonly else self.g.mount
-        msg = " read-only" if readonly else ""
-        self.out.output("Mounting the media%s ..." % msg, False)
-        mps = self.g.inspect_get_mountpoints(self.root)
+        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
@@ -169,15 +175,45 @@ class Image(object):
                 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:
-                mount(dev, mp)
+                self.g.mount_options(mopts, dev, mp)
             except RuntimeError as msg:
-                self.out.warn("%s (ignored)" % msg)
-
-        self.mounted = True
-        self.out.success("done")
+                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."""
@@ -185,6 +221,7 @@ class Image(object):
         self.mounted = False
 
     def _last_partition(self):
+        """Return the last partition of the image disk"""
         if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
             msg = "Unsupported partition table: %s. Only msdos and gpt " \
                 "partition tables are supported" % self.meta['PARTITION_TABLE']
@@ -211,10 +248,10 @@ class Image(object):
         return last_partition
 
     def shrink(self):
-        """Shrink the disk.
+        """Shrink the image.
 
-        This is accomplished by shrinking the last filesystem in the
-        disk and then updating the partition table. The new disk size
+        This is accomplished by shrinking the last file system of the
+        image and then updating the partition table. The new disk size
         (in bytes) is returned.
 
         ATTENTION: make sure unmount is called before shrink
@@ -337,7 +374,7 @@ class Image(object):
         return self.size
 
     def dump(self, outfile):
-        """Dumps the content of device into a file.
+        """Dumps the content of the image into a file.
 
         This method will only dump the actual payload, found by reading the
         partition table. Empty space in the end of the device will be ignored.