diff options
author | Tom Zanussi <tom.zanussi@linux.intel.com> | 2013-08-24 15:31:34 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2013-10-01 22:53:38 +0100 |
commit | 31f0360f1fd4ebc9dfcaed42d1c50d2448b4632e (patch) | |
tree | 7d8882b785efdb755de36a28dffa69712cf9b365 /scripts/lib/mic/utils/fs_related.py | |
parent | 95455ae4251e06d66e60945092b784d2d9ef165c (diff) | |
download | openembedded-core-31f0360f1fd4ebc9dfcaed42d1c50d2448b4632e.tar.gz openembedded-core-31f0360f1fd4ebc9dfcaed42d1c50d2448b4632e.tar.bz2 openembedded-core-31f0360f1fd4ebc9dfcaed42d1c50d2448b4632e.zip |
wic: Add mic w/pykickstart
This is the starting point for the implemention described in [YOCTO
3847] which came to the conclusion that it would make sense to use
kickstart syntax to implement image creation in OpenEmbedded. I
subsequently realized that there was an existing tool that already
implemented image creation using kickstart syntax, the Tizen/Meego mic
tool. As such, it made sense to use that as a starting point - this
commit essentially just copies the relevant Python code from the MIC
tool to the scripts/lib dir, where it can be accessed by the
previously created wic tool.
Most of this will be removed or renamed by later commits, since we're
initially focusing on partitioning only. Care should be taken so that
we can easily add back any additional functionality should we decide
later to expand the tool, though (we may also want to contribute our
local changes to the mic tool to the Tizen project if it makes sense,
and therefore should avoid gratuitous changes to the original code if
possible).
Added the /mic subdir from Tizen mic repo as a starting point:
git clone git://review.tizen.org/tools/mic.git
For reference, the top commit:
commit 20164175ddc234a17b8a12c33d04b012347b1530
Author: Gui Chen <gui.chen@intel.com>
Date: Sun Jun 30 22:32:16 2013 -0400
bump up to 0.19.2
Also added the /plugins subdir, moved to under the /mic subdir (to
match the default plugin_dir location in mic.conf.in, which was
renamed to yocto-image.conf (moved and renamed by later patches) and
put into /scripts.
Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com>
Signed-off-by: Saul Wold <sgw@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts/lib/mic/utils/fs_related.py')
-rw-r--r-- | scripts/lib/mic/utils/fs_related.py | 1029 |
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 + |