diff options
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 + |