3 # Copyright (c) 2007, Red Hat, Inc.
4 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 from __future__ import with_statement
30 from mic.utils import runner
31 from mic.utils.errors import *
34 def find_binary_inchroot(binary, chroot):
42 bin_path = "%s/%s" % (path, binary)
43 if os.path.exists("%s/%s" % (chroot, bin_path)):
47 def find_binary_path(binary):
48 if os.environ.has_key("PATH"):
49 paths = os.environ["PATH"].split(":")
52 if os.environ.has_key("HOME"):
53 paths += [os.environ["HOME"] + "/bin"]
54 paths += ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"]
57 bin_path = "%s/%s" % (path, binary)
58 if os.path.exists(bin_path):
60 raise CreatorError("Command '%s' is not available." % binary)
62 def makedirs(dirname):
63 """A version of os.makedirs() that doesn't throw an
64 exception if the leaf directory already exists.
69 if err.errno != errno.EEXIST:
72 def mkvdfs(in_img, out_img, fsoptions):
73 """ This function is incomplete. """
74 fullpathmkvdfs = find_binary_path("mkfs.vdfs")
75 # args = fullpathmkvdfs + " -i -r "+ in_img + " -z 1024M -s " + out_img
76 args = fullpathmkvdfs + " " + fsoptions + " -r " + in_img + " " + out_img
77 msger.verbose("vdfs args: %s" % args)
78 runner.show("%s --help" % fullpathmkvdfs)
79 # if not sys.stdout.isatty():
80 # args.append("-no-progress")
81 # runner.show("%s --help" % fullpathmkvdfs)
82 ret = runner.show(args)
84 runner.show ("vdfs error")
85 raise VdfsError("' %s' exited with error (%d)" % (args, ret))
87 def mksquashfs(in_img, out_img):
88 fullpathmksquashfs = find_binary_path("mksquashfs")
89 args = [fullpathmksquashfs, in_img, out_img]
91 if not sys.stdout.isatty():
92 args.append("-no-progress")
94 ret = runner.show(args)
96 raise SquashfsError("'%s' exited with error (%d)" % (' '.join(args), ret))
98 def resize2fs(fs, size):
99 resize2fs = find_binary_path("resize2fs")
101 # it means to minimalize it
102 return runner.show([resize2fs, '-M', fs])
104 return runner.show([resize2fs, fs, "%sK" % (size / 1024,)])
106 class BindChrootMount:
107 """Represents a bind mount of a directory into a chroot."""
108 def __init__(self, src, chroot, dest = None, option = None):
109 self.root = os.path.abspath(os.path.expanduser(chroot))
110 self.mount_option = option
112 self.orig_src = self.src = src
113 if os.path.islink(src):
114 self.src = os.readlink(src)
115 if not self.src.startswith('/'):
116 self.src = os.path.abspath(os.path.join(os.path.dirname(src),
121 self.dest = os.path.join(self.root, dest.lstrip('/'))
124 self.mountcmd = find_binary_path("mount")
125 self.umountcmd = find_binary_path("umount")
128 with open('/proc/mounts') as f:
130 if line.split()[1] == os.path.abspath(self.dest):
136 if self.mounted or self.ismounted():
142 if err.errno == errno.ENOSPC:
143 msger.warning("No space left on device '%s'" % err.filename)
146 if self.mount_option:
147 cmdline = [self.mountcmd, "-o" ,"bind", "-o", "%s" % \
148 self.mount_option, self.src, self.dest]
150 cmdline = [self.mountcmd, "-o" ,"bind", self.src, self.dest]
151 rc, errout = runner.runtool(cmdline, catch=2)
153 raise MountError("Bind-mounting '%s' to '%s' failed: %s" %
154 (self.src, self.dest, errout))
157 if os.path.islink(self.orig_src):
158 dest = os.path.join(self.root, self.orig_src.lstrip('/'))
159 if not os.path.exists(dest):
160 os.symlink(self.src, dest)
163 if self.mounted or self.ismounted():
164 runner.show([self.umountcmd, "-l", self.dest])
168 """LoopbackMount compatibility layer for old API"""
169 def __init__(self, lofile, mountdir, fstype = None):
170 self.diskmount = DiskMount(LoopbackDisk(lofile,size = 0),mountdir,fstype,rmmountdir = True)
172 self.losetupcmd = find_binary_path("losetup")
175 self.diskmount.cleanup()
178 self.diskmount.unmount()
182 runner.show([self.losetupcmd, "-d", self.loopdev])
190 self.loopdev = get_loop_device(self.losetupcmd, self.lofile)
194 self.diskmount.mount()
196 class SparseLoopbackMount(LoopbackMount):
197 """SparseLoopbackMount compatibility layer for old API"""
198 def __init__(self, lofile, mountdir, size, fstype = None):
199 self.diskmount = DiskMount(SparseLoopbackDisk(lofile,size),mountdir,fstype,rmmountdir = True)
201 def expand(self, create = False, size = None):
202 self.diskmount.disk.expand(create, size)
204 def truncate(self, size = None):
205 self.diskmount.disk.truncate(size)
208 self.diskmount.disk.create()
210 class SparseExtLoopbackMount(SparseLoopbackMount):
211 """SparseExtLoopbackMount compatibility layer for old API"""
212 def __init__(self, lofile, mountdir, size, fstype, blocksize, fslabel):
213 self.diskmount = ExtDiskMount(SparseLoopbackDisk(lofile,size), mountdir, fstype, blocksize, fslabel, rmmountdir = True)
216 def __format_filesystem(self):
217 self.diskmount.__format_filesystem()
220 self.diskmount.disk.create()
222 def resize(self, size = None):
223 return self.diskmount.__resize_filesystem(size)
226 self.diskmount.mount()
229 self.extdiskmount.__fsck()
231 def __get_size_from_filesystem(self):
232 return self.diskmount.__get_size_from_filesystem()
234 def __resize_to_minimal(self):
235 return self.diskmount.__resize_to_minimal()
237 def resparse(self, size = None):
238 return self.diskmount.resparse(size)
241 """Generic base object for a disk
243 The 'create' method must make the disk visible as a block device - eg
244 by calling losetup. For RawDisk, this is obviously a no-op. The 'cleanup'
245 method must undo the 'create' operation.
247 def __init__(self, size, device = None):
248 self._device = device
257 def get_device(self):
259 def set_device(self, path):
261 device = property(get_device, set_device)
265 size = property(get_size)
269 """A Disk backed by a block device.
270 Note that create() is a no-op.
272 def __init__(self, size, device):
273 Disk.__init__(self, size, device)
281 class LoopbackDisk(Disk):
282 """A Disk backed by a file via the loop module."""
283 def __init__(self, lofile, size):
284 Disk.__init__(self, size)
286 self.losetupcmd = find_binary_path("losetup")
292 return os.path.exists(self.lofile)
295 if self.device is not None:
298 self.device = get_loop_device(self.losetupcmd, self.lofile)
301 if self.device is None:
303 msger.debug("Losetup remove %s" % self.device)
304 rc = runner.show([self.losetupcmd, "-d", self.device])
307 def reread_size(self):
308 if self.device is None:
310 msger.debug("Reread size %s" % self.device)
311 rc = runner.show([self.losetupcmd, "-c", self.device])
312 # XXX: (WORKAROUND) re-setup loop device when losetup isn't support '-c' option.
314 msger.debug("Fail to reread size, Try to re-setup loop device to reread the size of the file associated")
315 runner.show([self.losetupcmd, "-d", self.device])
316 runner.show([self.losetupcmd, self.device, self.lofile])
318 class SparseLoopbackDisk(LoopbackDisk):
319 """A Disk backed by a sparse file via the loop module."""
320 def __init__(self, lofile, size):
321 LoopbackDisk.__init__(self, lofile, size)
323 def expand(self, create = False, size = None):
327 if not os.path.exists(self.lofile):
328 makedirs(os.path.dirname(self.lofile))
333 msger.debug("Extending sparse file %s to %d" % (self.lofile, size))
335 fd = os.open(self.lofile, flags, 0644)
337 fd = os.open(self.lofile, flags)
342 os.ftruncate(fd, size)
344 # may be limited by 2G in 32bit env
345 os.ftruncate(fd, 2**31L)
349 def truncate(self, size = None):
353 msger.debug("Truncating sparse file %s to %d" % (self.lofile, size))
354 fd = os.open(self.lofile, os.O_WRONLY)
355 os.ftruncate(fd, size)
359 if self.device is not None:
362 self.expand(create = True)
363 LoopbackDisk.create(self)
366 """A generic base class to deal with mounting things."""
367 def __init__(self, mountdir):
368 self.mountdir = mountdir
373 def mount(self, options = None):
379 class DiskMount(Mount):
380 """A Mount object that handles mounting of a Disk."""
381 def __init__(self, disk, mountdir, fstype = None, rmmountdir = True):
382 Mount.__init__(self, mountdir)
386 self.rmmountdir = rmmountdir
391 self.mkfscmd = find_binary_path("mkfs." + self.fstype)
394 self.mountcmd = find_binary_path("mount")
395 self.umountcmd = find_binary_path("umount")
403 msger.debug("Unmounting directory %s" % self.mountdir)
404 runner.quiet('sync') # sync the data on this mount point
405 rc = runner.show([self.umountcmd, "-l", self.mountdir])
409 raise MountError("Failed to umount %s" % self.mountdir)
410 if self.rmdir and not self.mounted:
412 os.rmdir(self.mountdir)
422 def mount(self, options = None):
426 if not os.path.isdir(self.mountdir):
427 msger.debug("Creating mount point %s" % self.mountdir)
428 os.makedirs(self.mountdir)
429 self.rmdir = self.rmmountdir
433 msger.debug("Mounting %s at %s" % (self.disk.device, self.mountdir))
435 args = [ self.mountcmd, "-o", options, self.disk.device, self.mountdir ]
437 args = [ self.mountcmd, self.disk.device, self.mountdir ]
439 args.extend(["-t", self.fstype])
441 rc = runner.show(args)
443 raise MountError("Failed to mount '%s' to '%s' with command '%s'. Retval: %s" %
444 (self.disk.device, self.mountdir, " ".join(args), rc))
448 class ExtDiskMount(DiskMount):
449 """A DiskMount object that is able to format/resize ext[23] filesystems."""
450 def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None, fsuuid=None):
451 DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
452 self.blocksize = blocksize
453 self.fslabel = fslabel.replace("/", "")
454 self.uuid = fsuuid or str(uuid.uuid4())
455 self.skipformat = skipformat
458 self.dumpe2fs = find_binary_path("dumpe2fs")
459 self.tune2fs = find_binary_path("tune2fs")
461 def __parse_field(self, output, field):
462 for line in output.split("\n"):
463 if line.startswith(field + ":"):
464 return line[len(field) + 1:].strip()
466 raise KeyError("Failed to find field '%s' in output" % field)
468 def __format_filesystem(self):
470 msger.debug("Skip filesystem format.")
473 msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
474 cmdlist = [self.mkfscmd, "-F", "-L", self.fslabel, "-m", "1", "-b",
475 str(self.blocksize), "-U", self.uuid]
477 cmdlist.extend(self.extopts.split())
478 cmdlist.extend([self.disk.device])
480 rc, errout = runner.runtool(cmdlist, catch=2)
482 raise MountError("Error creating %s filesystem on disk %s:\n%s" %
483 (self.fstype, self.disk.device, errout))
486 msger.debug("Tuning filesystem on %s" % self.disk.device)
487 runner.show([self.tune2fs, "-c0", "-i0", "-Odir_index", "-ouser_xattr,acl", self.disk.device])
489 def __resize_filesystem(self, size = None):
490 msger.info("Resizing filesystem ...")
491 current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
494 size = self.disk.size
496 if size == current_size:
499 if size > current_size:
500 self.disk.expand(size=size)
504 resize2fs(self.disk.lofile, size)
505 if size and size != os.stat(self.disk.lofile)[stat.ST_SIZE]:
506 raise MountError("Failed to resize filesystem %s to " % (self.disk.lofile, size))
512 if not self.disk.fixed() and self.disk.exists():
518 self.__resize_filesystem()
520 self.__format_filesystem()
522 def mount(self, options = None, init_expand = False):
525 expand_size = long(self.disk.size * 1.5)
526 msger.info("Initial partition size expanded : %ld -> %ld" % (self.disk.size, expand_size))
527 self.__resize_filesystem(expand_size)
528 self.disk.reread_size()
529 DiskMount.mount(self, options)
532 msger.info("Checking filesystem %s" % self.disk.lofile)
533 runner.quiet(["/sbin/e2fsck", "-f", "-y", self.disk.lofile])
535 def __get_size_from_filesystem(self):
536 return int(self.__parse_field(runner.outs([self.dumpe2fs, '-h', self.disk.lofile]),
537 "Block count")) * self.blocksize
539 def __resize_to_minimal(self):
540 msger.info("Resizing filesystem to minimal ...")
542 resize2fs(self.disk.lofile, 0)
543 return self.__get_size_from_filesystem()
545 def resparse(self, size = None):
550 minsize = self.__resize_to_minimal()
551 self.disk.truncate(minsize)
553 self.__resize_filesystem(size)
556 class VfatDiskMount(DiskMount):
557 """A DiskMount object that is able to format vfat/msdos filesystems."""
558 def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None, fsuuid = None):
559 DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
560 self.blocksize = blocksize
561 self.fslabel = fslabel.replace("/", "")
562 rand1 = random.randint(0, 2**16 - 1)
563 rand2 = random.randint(0, 2**16 - 1)
564 self.uuid = fsuuid or "%04X-%04X" % (rand1, rand2)
565 self.skipformat = skipformat
567 self.fsckcmd = find_binary_path("fsck." + self.fstype)
569 def __format_filesystem(self):
571 msger.debug("Skip filesystem format.")
574 msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
575 rc = runner.show([self.mkfscmd, "-n", self.fslabel,
576 "-i", self.uuid.replace("-", ""), self.disk.device])
578 raise MountError("Error creating %s filesystem on disk %s" % (self.fstype,self.disk.device))
580 msger.verbose("Tuning filesystem on %s" % self.disk.device)
582 def __resize_filesystem(self, size = None):
583 current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
586 size = self.disk.size
588 if size == current_size:
591 if size > current_size:
592 self.disk.expand(size=size)
596 #resize2fs(self.disk.lofile, size)
601 if not self.disk.fixed() and self.disk.exists():
607 self.__resize_filesystem()
609 self.__format_filesystem()
611 def mount(self, options = None, init_expand = False):
614 expand_size = long(self.disk.size * 1.5)
615 msger.info("Initial partition size expanded : %ld -> %ld" % (self.disk.size, expand_size))
616 self.__resize_filesystem(expand_size)
617 self.disk.reread_size()
618 DiskMount.mount(self, options)
621 msger.debug("Checking filesystem %s" % self.disk.lofile)
622 runner.show([self.fsckcmd, "-y", self.disk.lofile])
624 def __get_size_from_filesystem(self):
625 return self.disk.size
627 def __resize_to_minimal(self):
631 # Use a binary search to find the minimal size
632 # we can resize the image to
635 top = self.__get_size_from_filesystem()
638 def resparse(self, size = None):
640 minsize = self.__resize_to_minimal()
641 self.disk.truncate(minsize)
642 self.__resize_filesystem(size)
645 class BtrfsDiskMount(DiskMount):
646 """A DiskMount object that is able to format/resize btrfs filesystems."""
647 def __init__(self, disk, mountdir, fstype, blocksize, fslabel, rmmountdir=True, skipformat = False, fsopts = None, fsuuid = None):
649 DiskMount.__init__(self, disk, mountdir, fstype, rmmountdir)
650 self.blocksize = blocksize
651 self.fslabel = fslabel.replace("/", "")
652 self.uuid = fsuuid or None
653 self.skipformat = skipformat
655 self.blkidcmd = find_binary_path("blkid")
656 self.btrfsckcmd = find_binary_path("btrfsck")
658 def __check_btrfs(self):
660 """ Need to load btrfs module to mount it """
662 for line in open("/proc/filesystems").xreadlines():
663 if line.find("btrfs") > -1:
667 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.")
669 # disable selinux, selinux will block write
670 if os.path.exists("/usr/sbin/setenforce"):
671 runner.show(["/usr/sbin/setenforce", "0"])
673 def __parse_field(self, output, field):
674 for line in output.split(" "):
675 if line.startswith(field + "="):
676 return line[len(field) + 1:].strip().replace("\"", "")
678 raise KeyError("Failed to find field '%s' in output" % field)
680 def __format_filesystem(self):
682 msger.debug("Skip filesystem format.")
685 msger.verbose("Formating %s filesystem on %s" % (self.fstype, self.disk.device))
686 rc = runner.show([self.mkfscmd, "-L", self.fslabel, self.disk.device])
688 raise MountError("Error creating %s filesystem on disk %s" % (self.fstype,self.disk.device))
690 self.uuid = self.__parse_field(runner.outs([self.blkidcmd, self.disk.device]), "UUID")
692 def __resize_filesystem(self, size = None):
693 current_size = os.stat(self.disk.lofile)[stat.ST_SIZE]
696 size = self.disk.size
698 if size == current_size:
701 if size > current_size:
702 self.disk.expand(size=size)
709 if not self.disk.fixed() and self.disk.exists():
715 self.__resize_filesystem()
717 self.__format_filesystem()
719 def mount(self, options = None, init_expand = False):
722 expand_size = long(self.disk.size * 1.5)
723 msger.info("Initial partition size expanded : %ld -> %ld" % (self.disk.size, expand_size))
724 self.__resize_filesystem(expand_size)
725 self.disk.reread_size()
726 DiskMount.mount(self, options)
729 msger.debug("Checking filesystem %s" % self.disk.lofile)
730 runner.quiet([self.btrfsckcmd, self.disk.lofile])
732 def __get_size_from_filesystem(self):
733 return self.disk.size
735 def __resize_to_minimal(self):
738 return self.__get_size_from_filesystem()
740 def resparse(self, size = None):
742 minsize = self.__resize_to_minimal()
743 self.disk.truncate(minsize)
744 self.__resize_filesystem(size)
747 class DeviceMapperSnapshot(object):
748 def __init__(self, imgloop, cowloop):
749 self.imgloop = imgloop
750 self.cowloop = cowloop
752 self.__created = False
754 self.dmsetupcmd = find_binary_path("dmsetup")
756 """Load dm_snapshot if it isn't loaded"""
757 load_module("dm_snapshot")
760 if self.__name is None:
762 return os.path.join("/dev/mapper", self.__name)
763 path = property(get_path)
769 self.imgloop.create()
770 self.cowloop.create()
772 self.__name = "imgcreate-%d-%d" % (os.getpid(),
773 random.randint(0, 2**16))
775 size = os.stat(self.imgloop.lofile)[stat.ST_SIZE]
777 table = "0 %d snapshot %s %s p 8" % (size / 512,
781 args = [self.dmsetupcmd, "create", self.__name, "--table", table]
782 if runner.show(args) != 0:
783 self.cowloop.cleanup()
784 self.imgloop.cleanup()
785 raise SnapshotError("Could not create snapshot device using: " + ' '.join(args))
787 self.__created = True
789 def remove(self, ignore_errors = False):
790 if not self.__created:
794 rc = runner.show([self.dmsetupcmd, "remove", self.__name])
795 if not ignore_errors and rc != 0:
796 raise SnapshotError("Could not remove snapshot device")
799 self.__created = False
801 self.cowloop.cleanup()
802 self.imgloop.cleanup()
804 def get_cow_used(self):
805 if not self.__created:
809 # dmsetup status on a snapshot returns e.g.
810 # "0 8388608 snapshot 416/1048576"
811 # or, more generally:
813 # where C is the number of 512 byte sectors in use
815 out = runner.outs([self.dmsetupcmd, "status", self.__name])
817 return int((out.split()[3]).split('/')[0]) * 512
819 raise SnapshotError("Failed to parse dmsetup status: " + out)
821 def create_image_minimizer(path, image, minimal_size):
823 Builds a copy-on-write image which can be used to
824 create a device-mapper snapshot of an image where
825 the image's filesystem is as small as possible
828 1) Create a sparse COW
829 2) Loopback mount the image and the COW
830 3) Create a device-mapper snapshot of the image
832 4) Resize the filesystem to the minimal size
833 5) Determine the amount of space used in the COW
834 6) Restroy the device-mapper snapshot
835 7) Truncate the COW, removing unused space
836 8) Create a squashfs of the COW
838 imgloop = LoopbackDisk(image, None) # Passing bogus size - doesn't matter
840 cowloop = SparseLoopbackDisk(os.path.join(os.path.dirname(path), "osmin"),
843 snapshot = DeviceMapperSnapshot(imgloop, cowloop)
848 resize2fs(snapshot.path, minimal_size)
850 cow_used = snapshot.get_cow_used()
852 snapshot.remove(ignore_errors = (not sys.exc_info()[0] is None))
854 cowloop.truncate(cow_used)
856 mksquashfs(cowloop.lofile, path)
858 os.unlink(cowloop.lofile)
860 def load_module(module):
862 for line in open('/proc/modules').xreadlines():
863 if line.startswith("%s " % module):
867 msger.info("Loading %s..." % module)
868 runner.quiet(['modprobe', module])
870 class LoopDevice(object):
871 def __init__(self, loopid=None):
875 self.kpartxcmd = find_binary_path("kpartx")
876 self.losetupcmd = find_binary_path("losetup")
878 def register(self, device):
883 def reg_atexit(self):
885 atexit.register(self.close)
887 def _genloopid(self):
889 if not glob.glob("/dev/loop[0-9]*"):
892 fint = lambda x: x[9:].isdigit() and int(x[9:]) or 0
893 maxid = 1 + max(filter(lambda x: x<100,
894 map(fint, glob.glob("/dev/loop[0-9]*"))))
895 if maxid < 10: maxid = 10
896 if maxid >= 100: raise
899 def _kpseek(self, device):
900 rc, out = runner.runtool([self.kpartxcmd, '-l', '-v', device])
902 raise MountError("Can't query dm snapshot on %s" % device)
903 for line in out.splitlines():
904 if line and line.startswith("loop"):
908 def _loseek(self, device):
910 rc, out = runner.runtool([self.losetupcmd, '-a'])
912 raise MountError("Failed to run 'losetup -a'")
913 for line in out.splitlines():
914 m = re.match("([^:]+): .*", line)
915 if m and m.group(1) == device:
922 self.loopid = self._genloopid()
923 self.device = "/dev/loop%d" % self.loopid
924 if os.path.exists(self.device):
925 if self._loseek(self.device):
926 raise MountError("Device busy: %s" % self.device)
931 mknod = find_binary_path('mknod')
932 rc = runner.show([mknod, '-m664', self.device, 'b', '7', str(self.loopid)])
934 raise MountError("Failed to create device %s" % self.device)
943 except MountError, e:
944 raise CreatorError("%s" % e)
948 if self.device is None:
952 if self._kpseek(self.device):
953 runner.quiet([self.kpartxcmd, "-d", self.device])
954 if self._loseek(self.device):
955 runner.quiet([self.losetupcmd, "-d", self.device])
956 # FIXME: should sleep a while between two loseek
957 if self._loseek(self.device):
958 msger.warning("Can't cleanup loop device %s" % self.device)
960 os.unlink(self.device)
962 DEVICE_PIDFILE_DIR = "/var/tmp/mic/device"
963 DEVICE_LOCKFILE = "/var/lock/__mic_loopdev.lock"
965 def get_loop_device(losetupcmd, lofile):
966 global DEVICE_PIDFILE_DIR
967 global DEVICE_LOCKFILE
970 makedirs(os.path.dirname(DEVICE_LOCKFILE))
971 fp = open(DEVICE_LOCKFILE, 'w')
972 fcntl.flock(fp, fcntl.LOCK_EX)
975 devinst = LoopDevice()
977 # clean up left loop device first
980 # provide an avaible loop device
981 rc, out = runner.runtool([losetupcmd, "--find"])
983 loopdev = out.split()[0]
984 devinst.register(loopdev)
985 if not loopdev or not os.path.exists(loopdev):
987 loopdev = devinst.device
989 # setup a loop device for image file
990 rc = runner.show([losetupcmd, loopdev, lofile])
992 raise MountError("Failed to setup loop device for '%s'" % lofile)
996 # try to save device and pid
997 makedirs(DEVICE_PIDFILE_DIR)
998 pidfile = os.path.join(DEVICE_PIDFILE_DIR, os.path.basename(loopdev))
999 if os.path.exists(pidfile):
1001 with open(pidfile, 'w') as wf:
1002 wf.write(str(os.getpid()))
1004 except MountError, err:
1005 raise CreatorError("%s" % str(err))
1010 fcntl.flock(fp, fcntl.LOCK_UN)
1017 def clean_loop_devices(piddir=DEVICE_PIDFILE_DIR):
1018 if not os.path.exists(piddir) or not os.path.isdir(piddir):
1021 for loopdev in os.listdir(piddir):
1022 pidfile = os.path.join(piddir, loopdev)
1024 with open(pidfile, 'r') as rf:
1025 devpid = int(rf.read())
1029 # if the process using this device is alive, skip it
1030 if not devpid or os.path.exists(os.path.join('/proc', str(devpid))):
1033 # try to clean it up
1035 devinst = LoopDevice()
1036 devinst.register(os.path.join('/dev', loopdev))