aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/lib/mic/utils/fs_related.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/mic/utils/fs_related.py')
-rw-r--r--scripts/lib/mic/utils/fs_related.py1029
1 files changed, 1029 insertions, 0 deletions
diff --git a/scripts/lib/mic/utils/fs_related.py b/scripts/lib/mic/utils/fs_related.py
new file mode 100644
index 0000000000..b9b9a97175
--- /dev/null
+++ b/scripts/lib/mic/utils/fs_related.py
@@ -0,0 +1,1029 @@
+#!/usr/bin/python -tt
+#
+# Copyright (c) 2007, Red Hat, Inc.
+# Copyright (c) 2009, 2010, 2011 Intel, Inc.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; version 2 of the License
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+from __future__ import with_statement
+import os
+import sys
+import errno
+import stat
+import random
+import string
+import time
+import uuid
+
+from mic import msger
+from mic.utils import runner
+from mic.utils.errors import *
+
+
+def find_binary_inchroot(binary, chroot):
+ paths = ["/usr/sbin",
+ "/usr/bin",
+ "/sbin",
+ "/bin"
+ ]
+
+ for path in paths:
+ bin_path = "%s/%s" % (path, binary)
+ if os.path.exists("%s/%s" % (chroot, bin_path)):
+ return bin_path
+ return None
+
+def find_binary_path(binary):
+ if os.environ.has_key("PATH"):
+ paths = os.environ["PATH"].split(":")
+ else:
+ paths = []
+ if os.environ.has_key("HOME"):
+ paths += [os.environ["HOME"] + "/bin"]
+ paths += ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
+
+ for path in paths:
+ bin_path = "%s/%s" % (path, binary)
+ if os.path.exists(bin_path):
+ return bin_path
+ raise CreatorError("Command '%s' is not available." % binary)
+
+def makedirs(dirname):
+ """A version of os.makedirs() that doesn't throw an
+ exception if the leaf directory already exists.
+ """
+ try:
+ os.makedirs(dirname)
+ except OSError, err:
+ if err.errno != errno.EEXIST:
+ raise
+
+def mksquashfs(in_img, out_img):
+ fullpathmksquashfs = find_binary_path("mksquashfs")
+ args = [fullpathmksquashfs, in_img, out_img]
+
+ if not sys.stdout.isatty():
+ args.append("-no-progress")
+
+ ret = runner.show(args)
+ if ret != 0:
+ raise SquashfsError("'%s' exited with error (%d)" % (' '.join(args), ret))
+
+def resize2fs(fs, size):
+ resize2fs = find_binary_path("resize2fs")
+ if size == 0:
+ # it means to minimalize it
+ return runner.show([resize2fs, '-M', fs])
+ else:
+ return runner.show([resize2fs, fs, "%sK" % (size / 1024,)])
+
+def my_fuser(fp):
+ fuser = find_binary_path("fuser")
+ if not os.path.exists(fp):
+ return False
+
+ rc = runner.quiet([fuser, "-s", fp])
+ if rc == 0:
+ for pid in runner.outs([fuser, fp]).split():
+ fd = open("/proc/%s/cmdline" % pid, "r")
+ cmdline = fd.read()
+ fd.close()
+ if cmdline[:-1] == "/bin/bash":
+ return True
+
+ # not found
+ return False
+
+class BindChrootMount:
+ """Represents a bind mount of a directory into a chroot."""
+ def __init__(self, src, chroot, dest = None, option = None):
+ self.root = os.path.abspath(os.path.expanduser(chroot))
+ self.option = option
+
+ self.orig_src = self.src = src
+ if os.path.islink(src):
+ self.src = os.readlink(src)
+ if not self.src.startswith('/'):
+ self.src = os.path.abspath(os.path.join(os.path.dirname(src),
+ self.src))
+
+ if not dest:
+ dest = self.src
+ self.dest = os.path.join(self.root, dest.lstrip('/'))
+
+ self.mounted = False
+ self.mountcmd = find_binary_path("mount")
+ self.umountcmd = find_binary_path("umount")
+
+ def ismounted(self):
+ with open('/proc/mounts') as f:
+ for line in f:
+ if line.split()[1] == os.path.abspath(self.dest):
+ return True
+
+ return False
+
+ def has_chroot_instance(self):
+ lock = os.path.join(self.root, ".chroot.lock")
+ return my_fuser(lock)
+
+ def mount(self):
+ if self.mounted or self.ismounted():
+ return
+
+ makedirs(self.dest)
+ rc = runner.show([self.mountcmd, "--bind", self.src, self.dest])
+ if rc != 0:
+ raise MountError("Bind-mounting '%s' to '%s' failed" %
+ (self.src, self.dest))
+ if self.option:
+ rc = runner.show([self.mountcmd, "--bind", "-o", "remount,%s" % self.option, self.dest])
+ if rc != 0:
+ raise MountError("Bind-remounting '%s' failed" % self.dest)
+
+ self.mounted = True
+ if os.path.islink(self.orig_src):
+ dest = os.path.join(self.root, self.orig_src.lstrip('/'))
+ if not os.path.exists(dest):
+ os.symlink(self.src, dest)
+
+ def unmount(self):
+ if self.has_chroot_instance():
+ return
+
+ if self.ismounted():
+ runner.show([self.umountcmd, "-l", self.dest])
+ self.mounted = False
+
+class LoopbackMount:
+ """LoopbackMount compatibility layer for old API"""
+ def __init__(self, lofile, mountdir, fstype = None):
+ self.diskmount = DiskMount(LoopbackDisk(lofile,size = 0),mountdir,fstype,rmmountdir = True)
+ self.losetup = False
+ self.losetupcmd = find_binary_path("losetup")
+
+ def cleanup(self):
+ self.diskmount.cleanup()
+
+ def unmount(self):
+ self.diskmount.unmount()
+
+ def lounsetup(self):
+ if self.losetup:
+ runner.show([self.losetupcmd, "-d", self.loopdev])
+ self.losetup = False
+ self.loopdev = None
+
+ def loopsetup(self):
+ if self.losetup:
+ return
+
+ self.loopdev = get_loop_device(self.losetupcmd, self.lofile)
+ self.losetup = True
+
+ def mount(self):
+ self.diskmount.mount()
+
+class SparseLoopbackMount(LoopbackMount):
+ """SparseLoopbackMount compatibility layer for old API"""
+ def __init__(self, lofile, mountdir, size, fstype = None):
+ self.diskmount = DiskMount(SparseLoopbackDisk(lofile,size),mountdir,fstype,rmmountdir = True)
+
+ def expand(self, create = False, size = None):
+ self.diskmount.disk.expand(create, size)
+
+ def truncate(self, size = None):
+ self.diskmount.disk.truncate(size)
+
+ def create(self):
+ self.diskmount.disk.create()
+
+class SparseExtLoopbackMount(SparseLoopbackMount):
+ """SparseExtLoopbackMount compatibility layer for old API"""
+ def __init__(self, lofile, mountdir, size, fstype, blocksize, fslabel):
+ self.diskmount = ExtDiskMount(SparseLoopbackDisk(lofile,size), mountdir, fstype, blocksize, fslabel, rmmountdir = True)
+
+
+ def __format_filesystem(self):
+ self.diskmount.__format_filesystem()
+
+ def create(self):
+ self.diskmount.disk.create()
+
+ def resize(self, size = None):
+ return self.diskmount.__resize_filesystem(size)
+
+ def mount(self):
+ self.diskmount.mount()
+
+ def __fsck(self):
+ self.extdiskmount.__fsck()
+
+ def __get_size_from_filesystem(self):
+ return self.diskmount.__get_size_from_filesystem()
+
+ def __resize_to_minimal(self):
+ return self.diskmount.__resize_to_minimal()
+
+ def resparse(self, size = None):
+ return self.diskmount.resparse(size)
+
+class Disk:
+ """Generic base object for a disk
+
+ The 'create' method must make the disk visible as a block device - eg
+ by calling losetup. For RawDisk, this is obviously a no-op. The 'cleanup'
+ method must undo the 'create' operation.
+ """
+ def __init__(self, size, device = None):
+ self._device = device
+ self._size = size
+
+ def create(self):
+ pass
+
+ def cleanup(self):
+ pass
+
+ def get_device(self):
+ return self._device
+ def set_device(self, path):
+ self._device = path
+ device = property(get_device, set_device)
+
+ def get_size(self):
+ return self._size
+ size = property(get_size)
+
+
+class RawDisk(Disk):
+ """A Disk backed by a block device.
+ Note that create() is a no-op.
+ """
+ def __init__(self, size, device):
+ Disk.__init__(self, size, device)
+
+ def fixed(self):
+ return True
+
+ def exists(self):
+ return True
+
+class LoopbackDisk(Disk):
+ """A Disk backed by a file via the loop module."""
+ def __init__(self, lofile, size):
+ Disk.__init__(self, size)
+ self.lofile = lofile
+ self.losetupcmd = find_binary_path("losetup")
+
+ def fixed(self):
+ return False
+
+ def exists(self):
+ return os.path.exists(self.lofile)
+
+ def create(self):
+ if self.device is not None:
+ return
+
+ self.device = get_loop_device(self.losetupcmd, self.lofile)
+
+ def cleanup(self):
+ if self.device is None:
+ return
+ msger.debug("Losetup remove %s" % self.device)
+ rc = runner.show([self.losetupcmd, "-d", self.device])
+ self.device = None
+
+class SparseLoopbackDisk(LoopbackDisk):
+ """A Disk backed by a sparse file via the loop module."""
+ def __init__(self, lofile, size):
+ LoopbackDisk.__init__(self, lofile, size)
+
+ def expand(self, create = False, size = None):
+ flags = os.O_WRONLY
+ if create:
+ flags |= os.O_CREAT
+ if not os.path.exists(self.lofile):
+ makedirs(os.path.dirname(self.lofile))
+
+ if size is None:
+ size = self.size
+
+ msger.debug("Extending sparse file %s to %d" % (self.lofile, size))
+ if create:
+ fd = os.open(self.lofile, flags, 0644)
+ else:
+ fd = os.open(self.lofile, flags)
+
+ if size <= 0:
+ size = 1
+ try:
+ os.ftruncate(fd, size)
+ except:
+ # may be limited by 2G in 32bit env
+ os.ftruncate(fd, 2**31L)
+
+ os.close(fd)
+
+ def truncate(self, size = None):
+ if size is None:
+ size = self.size
+
+ msger.debug("Truncating sparse file %s to %d" % (self.lofile, size))
+ fd = os.open(self.lofile, os.O_WRONLY)
+ os.ftruncate(fd, size)
+ os.close(fd)
+
+ def create(self):
+ self.expand(create = True)
+ LoopbackDisk.create(self)
+
+class Mount:
+ """A generic base class to deal with mounting things."""
+ def __init__(self, mountdir):
+ self.mountdir = mountdir
+
+ def cleanup(self):
+ self.unmount()
+
+ def mount(self, options = None):
+ pass
+
+ def unmount(self):
+ pass
+
+class DiskMount(Mount):
+ """A Mount object that handles mounting of a Disk."""
+ def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
+ Mount.__init__(self, mountdir)
+
+ self.disk = disk
+ self.fstype = fstype
+ self.rmmountdir = rmmountdir
+
+ self.mounted = False
+ self.rmdir = False
+ if fstype:
+ self.mkfscmd = find_binary_path("mkfs." + self.fstype)
+ else:
+ self.mkfscmd = None
+ self.mountcmd = find_binary_path("mount")
+ self.umountcmd = find_binary_path("umount")
+
+ def cleanup(self):
+ Mount.cleanup(self)
+ self.disk.cleanup()
+
+ def unmount(self):
+ if self.mounted:
+ msger.debug("Unmounting directory %s" % self.mountdir)
+ runner.quiet('sync') # sync the data on this mount point
+ rc = runner.show([self.umountcmd, "-l", self.mountdir])
+ if rc == 0:
+ self.mounted = False
+ else:
+ raise MountError("Failed to umount %s" % self.mountdir)
+ if self.rmdir and not self.mounted:
+ try:
+ os.rmdir(self.mountdir)
+ except OSError, e:
+ pass
+ self.rmdir = False
+
+
+ def __create(self):
+ self.disk.create()
+
+
+ def mount(self, options = None):
+ if self.mounted:
+ return
+
+ if not os.path.isdir(self.mountdir):
+ msger.debug("Creating mount point %s" % self.mountdir)
+ os.makedirs(self.mountdir)
+ self.rmdir = self.rmmountdir
+
+ self.__create()
+
+ msger.debug("Mounting %s at %s" % (self.disk.device, self.mountdir))
+ if options:
+ args = [ self.mountcmd, "-o", options, self.disk.device, self.mountdir ]
+ else:
+ args = [ self.mountcmd, self.disk.device, self.mountdir ]
+ if self.fstype:
+ args.extend(["-t", self.fstype])
+
+ rc = runner.show(args)
+ if rc != 0:
+ raise MountError("Failed to mount '%s' to '%s' with command '%s'. Retval: %s" %
+ (self.disk.device, self.mountdir, " ".join(args), rc))
+
+ self.mounted = True
+
+class ExtDiskMount(DiskMount):
+ """A DiskMount object that is able to format/resize ext[23] filesystems."""
+ def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None):
+ DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
+ self.blocksize = blocksize
+ self.fslabel = fslabel.replace("/", "")
+ self.uuid = str(uuid.uuid4())
+ self.skipformat = skipformat
+ self.fsopts = fsopts
+ self.extopts = None
+ self.dumpe2fs = find_binary_path("dumpe2fs")
+ self.tune2fs = find_binary_path("tune2fs")
+
+ def __parse_field(self, output, field):
+ for line in output.split("\n"):
+ if line.startswith(field + ":"):
+ return line[len(field) + 1:].strip()
+
+ raise KeyError("Failed to find field '%s' in output" % field)
+
+ def __format_filesystem(self):
+ if self.skipformat:
+ msger.debug("Skip filesystem format.")
+ return
+
+ msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
+ cmdlist = [self.mkfscmd, "-F", "-L", self.fslabel, "-m", "1", "-b",
+ str(self.blocksize), "-U", self.uuid]
+ if self.extopts:
+ cmdlist.extend(self.extopts.split())
+ cmdlist.extend([self.disk.device])
+
+ rc, errout = runner.runtool(cmdlist, catch=2)
+ if rc != 0:
+ raise MountError("Error creating %s filesystem on disk %s:\n%s" %
+ (self.fstype, self.disk.device, errout))
+
+ if not self.extopts:
+ msger.debug("Tuning filesystem on %s" % self.disk.device)
+ runner.show([self.tune2fs, "-c0", "-i0", "-Odir_index", "-ouser_xattr,acl", self.disk.device])
+
+ def __resize_filesystem(self, size = None):
+ current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
+
+ if size is None:
+ size = self.disk.size
+
+ if size == current_size:
+ return
+
+ if size > current_size:
+ self.disk.expand(size)
+
+ self.__fsck()
+
+ resize2fs(self.disk.lofile, size)
+ return size
+
+ def __create(self):
+ resize = False
+ if not self.disk.fixed() and self.disk.exists():
+ resize = True
+
+ self.disk.create()
+
+ if resize:
+ self.__resize_filesystem()
+ else:
+ self.__format_filesystem()
+
+ def mount(self, options = None):
+ self.__create()
+ DiskMount.mount(self, options)
+
+ def __fsck(self):
+ msger.info("Checking filesystem %s" % self.disk.lofile)
+ runner.quiet(["/sbin/e2fsck", "-f", "-y", self.disk.lofile])
+
+ def __get_size_from_filesystem(self):
+ return int(self.__parse_field(runner.outs([self.dumpe2fs, '-h', self.disk.lofile]),
+ "Block count")) * self.blocksize
+
+ def __resize_to_minimal(self):
+ self.__fsck()
+
+ #
+ # Use a binary search to find the minimal size
+ # we can resize the image to
+ #
+ bot = 0
+ top = self.__get_size_from_filesystem()
+ while top != (bot + 1):
+ t = bot + ((top - bot) / 2)
+
+ if not resize2fs(self.disk.lofile, t):
+ top = t
+ else:
+ bot = t
+ return top
+
+ def resparse(self, size = None):
+ self.cleanup()
+ if size == 0:
+ minsize = 0
+ else:
+ minsize = self.__resize_to_minimal()
+ self.disk.truncate(minsize)
+
+ self.__resize_filesystem(size)
+ return minsize
+
+class VfatDiskMount(DiskMount):
+ """A DiskMount object that is able to format vfat/msdos filesystems."""
+ def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None):
+ DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
+ self.blocksize = blocksize
+ self.fslabel = fslabel.replace("/", "")
+ rand1 = random.randint(0, 2**16 - 1)
+ rand2 = random.randint(0, 2**16 - 1)
+ self.uuid = "%04X-%04X" % (rand1, rand2)
+ self.skipformat = skipformat
+ self.fsopts = fsopts
+ self.fsckcmd = find_binary_path("fsck." + self.fstype)
+
+ def __format_filesystem(self):
+ if self.skipformat:
+ msger.debug("Skip filesystem format.")
+ return
+
+ msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
+ rc = runner.show([self.mkfscmd, "-n", self.fslabel,
+ "-i", self.uuid.replace("-", ""), self.disk.device])
+ if rc != 0:
+ raise MountError("Error creating %s filesystem on disk %s" % (self.fstype,self.disk.device))
+
+ msger.verbose("Tuning filesystem on %s" % self.disk.device)
+
+ def __resize_filesystem(self, size = None):
+ current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
+
+ if size is None:
+ size = self.disk.size
+
+ if size == current_size:
+ return
+
+ if size > current_size:
+ self.disk.expand(size)
+
+ self.__fsck()
+
+ #resize2fs(self.disk.lofile, size)
+ return size
+
+ def __create(self):
+ resize = False
+ if not self.disk.fixed() and self.disk.exists():
+ resize = True
+
+ self.disk.create()
+
+ if resize:
+ self.__resize_filesystem()
+ else:
+ self.__format_filesystem()
+
+ def mount(self, options = None):
+ self.__create()
+ DiskMount.mount(self, options)
+
+ def __fsck(self):
+ msger.debug("Checking filesystem %s" % self.disk.lofile)
+ runner.show([self.fsckcmd, "-y", self.disk.lofile])
+
+ def __get_size_from_filesystem(self):
+ return self.disk.size
+
+ def __resize_to_minimal(self):
+ self.__fsck()
+
+ #
+ # Use a binary search to find the minimal size
+ # we can resize the image to
+ #
+ bot = 0
+ top = self.__get_size_from_filesystem()
+ return top
+
+ def resparse(self, size = None):
+ self.cleanup()
+ minsize = self.__resize_to_minimal()
+ self.disk.truncate(minsize)
+ self.__resize_filesystem(size)
+ return minsize
+
+class BtrfsDiskMount(DiskMount):
+ """A DiskMount object that is able to format/resize btrfs filesystems."""
+ def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None):
+ self.__check_btrfs()
+ DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
+ self.blocksize = blocksize
+ self.fslabel = fslabel.replace("/", "")
+ self.uuid = None
+ self.skipformat = skipformat
+ self.fsopts = fsopts
+ self.blkidcmd = find_binary_path("blkid")
+ self.btrfsckcmd = find_binary_path("btrfsck")
+
+ def __check_btrfs(self):
+ found = False
+ """ Need to load btrfs module to mount it """
+ load_module("btrfs")
+ for line in open("/proc/filesystems").xreadlines():
+ if line.find("btrfs") > -1:
+ found = True
+ break
+ if not found:
+ raise MountError("Your system can't mount btrfs filesystem, please make sure your kernel has btrfs support and the module btrfs.ko has been loaded.")
+
+ # disable selinux, selinux will block write
+ if os.path.exists("/usr/sbin/setenforce"):
+ runner.show(["/usr/sbin/setenforce", "0"])
+
+ def __parse_field(self, output, field):
+ for line in output.split(" "):
+ if line.startswith(field + "="):
+ return line[len(field) + 1:].strip().replace("\"", "")
+
+ raise KeyError("Failed to find field '%s' in output" % field)
+
+ def __format_filesystem(self):
+ if self.skipformat:
+ msger.debug("Skip filesystem format.")
+ return
+
+ msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
+ rc = runner.show([self.mkfscmd, "-L", self.fslabel, self.disk.device])
+ if rc != 0:
+ raise MountError("Error creating %s filesystem on disk %s" % (self.fstype,self.disk.device))
+
+ self.uuid = self.__parse_field(runner.outs([self.blkidcmd, self.disk.device]), "UUID")
+
+ def __resize_filesystem(self, size = None):
+ current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
+
+ if size is None:
+ size = self.disk.size
+
+ if size == current_size:
+ return
+
+ if size > current_size:
+ self.disk.expand(size)
+
+ self.__fsck()
+ return size
+
+ def __create(self):
+ resize = False
+ if not self.disk.fixed() and self.disk.exists():
+ resize = True
+
+ self.disk.create()
+
+ if resize:
+ self.__resize_filesystem()
+ else:
+ self.__format_filesystem()
+
+ def mount(self, options = None):
+ self.__create()
+ DiskMount.mount(self, options)
+
+ def __fsck(self):
+ msger.debug("Checking filesystem %s" % self.disk.lofile)
+ runner.quiet([self.btrfsckcmd, self.disk.lofile])
+
+ def __get_size_from_filesystem(self):
+ return self.disk.size
+
+ def __resize_to_minimal(self):
+ self.__fsck()
+
+ return self.__get_size_from_filesystem()
+
+ def resparse(self, size = None):
+ self.cleanup()
+ minsize = self.__resize_to_minimal()
+ self.disk.truncate(minsize)
+ self.__resize_filesystem(size)
+ return minsize
+
+class DeviceMapperSnapshot(object):
+ def __init__(self, imgloop, cowloop):
+ self.imgloop = imgloop
+ self.cowloop = cowloop
+
+ self.__created = False
+ self.__name = None
+ self.dmsetupcmd = find_binary_path("dmsetup")
+
+ """Load dm_snapshot if it isn't loaded"""
+ load_module("dm_snapshot")
+
+ def get_path(self):
+ if self.__name is None:
+ return None
+ return os.path.join("/dev/mapper", self.__name)
+ path = property(get_path)
+
+ def create(self):
+ if self.__created:
+ return
+
+ self.imgloop.create()
+ self.cowloop.create()
+
+ self.__name = "imgcreate-%d-%d" % (os.getpid(),
+ random.randint(0, 2**16))
+
+ size = os.stat(self.imgloop.lofile)[stat.ST_SIZE]
+
+ table = "0 %d snapshot %s %s p 8" % (size / 512,
+ self.imgloop.device,
+ self.cowloop.device)
+
+ args = [self.dmsetupcmd, "create", self.__name, "--table", table]
+ if runner.show(args) != 0:
+ self.cowloop.cleanup()
+ self.imgloop.cleanup()
+ raise SnapshotError("Could not create snapshot device using: " + ' '.join(args))
+
+ self.__created = True
+
+ def remove(self, ignore_errors = False):
+ if not self.__created:
+ return
+
+ time.sleep(2)
+ rc = runner.show([self.dmsetupcmd, "remove", self.__name])
+ if not ignore_errors and rc != 0:
+ raise SnapshotError("Could not remove snapshot device")
+
+ self.__name = None
+ self.__created = False
+
+ self.cowloop.cleanup()
+ self.imgloop.cleanup()
+
+ def get_cow_used(self):
+ if not self.__created:
+ return 0
+
+ #
+ # dmsetup status on a snapshot returns e.g.
+ # "0 8388608 snapshot 416/1048576"
+ # or, more generally:
+ # "A B snapshot C/D"
+ # where C is the number of 512 byte sectors in use
+ #
+ out = runner.outs([self.dmsetupcmd, "status", self.__name])
+ try:
+ return int((out.split()[3]).split('/')[0]) * 512
+ except ValueError:
+ raise SnapshotError("Failed to parse dmsetup status: " + out)
+
+def create_image_minimizer(path, image, minimal_size):
+ """
+ Builds a copy-on-write image which can be used to
+ create a device-mapper snapshot of an image where
+ the image's filesystem is as small as possible
+
+ The steps taken are:
+ 1) Create a sparse COW
+ 2) Loopback mount the image and the COW
+ 3) Create a device-mapper snapshot of the image
+ using the COW
+ 4) Resize the filesystem to the minimal size
+ 5) Determine the amount of space used in the COW
+ 6) Restroy the device-mapper snapshot
+ 7) Truncate the COW, removing unused space
+ 8) Create a squashfs of the COW
+ """
+ imgloop = LoopbackDisk(image, None) # Passing bogus size - doesn't matter
+
+ cowloop = SparseLoopbackDisk(os.path.join(os.path.dirname(path), "osmin"),
+ 64L * 1024L * 1024L)
+
+ snapshot = DeviceMapperSnapshot(imgloop, cowloop)
+
+ try:
+ snapshot.create()
+
+ resize2fs(snapshot.path, minimal_size)
+
+ cow_used = snapshot.get_cow_used()
+ finally:
+ snapshot.remove(ignore_errors = (not sys.exc_info()[0] is None))
+
+ cowloop.truncate(cow_used)
+
+ mksquashfs(cowloop.lofile, path)
+
+ os.unlink(cowloop.lofile)
+
+def load_module(module):
+ found = False
+ for line in open('/proc/modules').xreadlines():
+ if line.startswith("%s " % module):
+ found = True
+ break
+ if not found:
+ msger.info("Loading %s..." % module)
+ runner.quiet(['modprobe', module])
+
+class LoopDevice(object):
+ def __init__(self, loopid=None):
+ self.device = None
+ self.loopid = loopid
+ self.created = False
+ self.kpartxcmd = find_binary_path("kpartx")
+ self.losetupcmd = find_binary_path("losetup")
+
+ def register(self, device):
+ self.device = device
+ self.loopid = None
+ self.created = True
+
+ def reg_atexit(self):
+ import atexit
+ atexit.register(self.close)
+
+ def _genloopid(self):
+ import glob
+ if not glob.glob("/dev/loop[0-9]*"):
+ return 10
+
+ fint = lambda x: x[9:].isdigit() and int(x[9:]) or 0
+ maxid = 1 + max(filter(lambda x: x<100,
+ map(fint, glob.glob("/dev/loop[0-9]*"))))
+ if maxid < 10: maxid = 10
+ if maxid >= 100: raise
+ return maxid
+
+ def _kpseek(self, device):
+ rc, out = runner.runtool([self.kpartxcmd, '-l', '-v', device])
+ if rc != 0:
+ raise MountError("Can't query dm snapshot on %s" % device)
+ for line in out.splitlines():
+ if line and line.startswith("loop"):
+ return True
+ return False
+
+ def _loseek(self, device):
+ import re
+ rc, out = runner.runtool([self.losetupcmd, '-a'])
+ if rc != 0:
+ raise MountError("Failed to run 'losetup -a'")
+ for line in out.splitlines():
+ m = re.match("([^:]+): .*", line)
+ if m and m.group(1) == device:
+ return True
+ return False
+
+ def create(self):
+ if not self.created:
+ if not self.loopid:
+ self.loopid = self._genloopid()
+ self.device = "/dev/loop%d" % self.loopid
+ if os.path.exists(self.device):
+ if self._loseek(self.device):
+ raise MountError("Device busy: %s" % self.device)
+ else:
+ self.created = True
+ return
+
+ mknod = find_binary_path('mknod')
+ rc = runner.show([mknod, '-m664', self.device, 'b', '7', str(self.loopid)])
+ if rc != 0:
+ raise MountError("Failed to create device %s" % self.device)
+ else:
+ self.created = True
+
+ def close(self):
+ if self.created:
+ try:
+ self.cleanup()
+ self.device = None
+ except MountError, e:
+ msger.error("%s" % e)
+
+ def cleanup(self):
+
+ if self.device is None:
+ return
+
+
+ if self._kpseek(self.device):
+ if self.created:
+ for i in range(3, os.sysconf("SC_OPEN_MAX")):
+ try:
+ os.close(i)
+ except:
+ pass
+ runner.quiet([self.kpartxcmd, "-d", self.device])
+ if self._loseek(self.device):
+ runner.quiet([self.losetupcmd, "-d", self.device])
+ # FIXME: should sleep a while between two loseek
+ if self._loseek(self.device):
+ msger.warning("Can't cleanup loop device %s" % self.device)
+ elif self.loopid:
+ os.unlink(self.device)
+
+DEVICE_PIDFILE_DIR = "/var/tmp/mic/device"
+DEVICE_LOCKFILE = "/var/lock/__mic_loopdev.lock"
+
+def get_loop_device(losetupcmd, lofile):
+ global DEVICE_PIDFILE_DIR
+ global DEVICE_LOCKFILE
+
+ import fcntl
+ makedirs(os.path.dirname(DEVICE_LOCKFILE))
+ fp = open(DEVICE_LOCKFILE, 'w')
+ fcntl.flock(fp, fcntl.LOCK_EX)
+ try:
+ loopdev = None
+ devinst = LoopDevice()
+
+ # clean up left loop device first
+ clean_loop_devices()
+
+ # provide an avaible loop device
+ rc, out = runner.runtool([losetupcmd, "--find"])
+ if rc == 0:
+ loopdev = out.split()[0]
+ devinst.register(loopdev)
+ if not loopdev or not os.path.exists(loopdev):
+ devinst.create()
+ loopdev = devinst.device
+
+ # setup a loop device for image file
+ rc = runner.show([losetupcmd, loopdev, lofile])
+ if rc != 0:
+ raise MountError("Failed to setup loop device for '%s'" % lofile)
+
+ devinst.reg_atexit()
+
+ # try to save device and pid
+ makedirs(DEVICE_PIDFILE_DIR)
+ pidfile = os.path.join(DEVICE_PIDFILE_DIR, os.path.basename(loopdev))
+ if os.path.exists(pidfile):
+ os.unlink(pidfile)
+ with open(pidfile, 'w') as wf:
+ wf.write(str(os.getpid()))
+
+ except MountError, err:
+ raise CreatorError("%s" % str(err))
+ except:
+ raise
+ finally:
+ try:
+ fcntl.flock(fp, fcntl.LOCK_UN)
+ fp.close()
+ os.unlink(DEVICE_LOCKFILE)
+ except:
+ pass
+
+ return loopdev
+
+def clean_loop_devices(piddir=DEVICE_PIDFILE_DIR):
+ if not os.path.exists(piddir) or not os.path.isdir(piddir):
+ return
+
+ for loopdev in os.listdir(piddir):
+ pidfile = os.path.join(piddir, loopdev)
+ try:
+ with open(pidfile, 'r') as rf:
+ devpid = int(rf.read())
+ except:
+ devpid = None
+
+ # if the process using this device is alive, skip it
+ if not devpid or os.path.exists(os.path.join('/proc', str(devpid))):
+ continue
+
+ # try to clean it up
+ try:
+ devinst = LoopDevice()
+ devinst.register(os.path.join('/dev', loopdev))
+ devinst.cleanup()
+ os.unlink(pidfile)
+ except:
+ pass
+