Problem
Running livecd-creator (livecd-tools-13.4.5-1.el6.x86_64 + python-imgcreate-13.4.5-1.el6.x86_64.rpm) leaves loopback mounts around after execution.
Environment
RedHat Enterprise Linux 6.5 running under Virtualbox 4.3
# lsof | grep loop # # time livecd-creator -d -v --logfile=/tmp/livecd.debug.out -c /a/infoboot.ks -f "INFO" # ... #Inserting fragment md5sums into iso image... #fragmd5 = a1223c226ed416222a53519717949fd69cfef83f3bf48e8fe5f3d46d5686 #frags = 20 #Setting supported flag to 0 # real 5m54.977s # user 5m14.839s # sys 1m32.820s # lsof | grep loop loop0 10927 root cwd DIR 8,3 4096 2 / loop0 10927 root rtd DIR 8,3 4096 2 / loop0 10927 root txt unknown /proc/10927/exe
loopback turds are left around as the unmounts fail and lazy unmounts are tried, which never resolve themselves.
This leaves a situation where only so many livecd-creators can be run before failing with ‘can’t get loop device’
I coded a workaround as follows.
/usr/lib/python2.6/site-packages/imgcreate/fs.py
The entire modified fs.py file from livecd-tools-13.4.5-1.el6.x86_64.rpm lives here.
Diff of orig version and patched version
$ diff -c fs.py fs.py.ORIG *** fs.py 2014-03-07 09:41:35.000000000 -0700 --- fs.py.ORIG 2014-03-05 15:22:30.000000000 -0700 *************** *** 27,33 **** import logging import tempfile import time - import re from imgcreate.errors import * --- 27,32 ---- *************** *** 112,147 **** def unmount(self): if not self.mounted: return - self.mounted = False - - rc = subprocess.call(["/bin/umount", self.dest]) - if rc == 0: - logging.debug("umount %s succeeded", self.dest ) - return - - logging.info("Second attempt Unmounting directory %s" % self.dest) - logging.info("Calling fuser -ck on %s" % self.dest) - - if re.match( ".*yum.*", self.dest) == None: # We don't want to nuke everything associated with yum - rc = subprocess.call(["/sbin/fuser", '-ck', self.dest]) - logging.info("fuser -ck returned %s" % rc ) - - time.sleep(2) rc = subprocess.call(["/bin/umount", self.dest]) - if rc == 0: - logging.info("Second umount succeeded") - return - - logging.debug("Unable to unmount %s normally, using lazy unmount" % self.dest) - rc = subprocess.call(["/bin/umount", "-l", self.dest]) if rc != 0: ! raise MountError("Unable to unmount fs at %s" % self.dest) ! else: ! logging.debug("lazy umount succeeded on %s" % self.dest) ! print >> sys.stdout, "lazy umount succeeded on %s" % self.dest ! class LoopbackMount: """LoopbackMount compatibility layer for old API""" --- 111,128 ---- def unmount(self): if not self.mounted: return rc = subprocess.call(["/bin/umount", self.dest]) if rc != 0: ! logging.debug("Unable to unmount %s normally, using lazy unmount" % self.dest) ! rc = subprocess.call(["/bin/umount", "-l", self.dest]) ! if rc != 0: ! raise MountError("Unable to unmount fs at %s" % self.dest) ! else: ! logging.debug("lazy umount succeeded on %s" % self.dest) ! print >> sys.stdout, "lazy umount succeeded on %s" % self.dest ! self.mounted = False class LoopbackMount: """LoopbackMount compatibility layer for old API""" *************** *** 156,184 **** self.diskmount.unmount() def lounsetup(self): ! if not self.losetup: ! return ! ! rc = subprocess.call(["/sbin/losetup", "-d", self.loopdev]) ! if rc == 0: self.losetup = False self.loopdev = None - logging.info( "First call to losetup remove Succeeded " ) - return - - logging.info("Calling fuser -ck on %s" % self.loopdev) - rc = subprocess.call(["/sbin/fuser", '-ck', self.loopdev]) - logging.info("fuser -ck returned %s" % rc ) - - self.loopdev = None - self.losetup = False - - rc = subprocess.call(["/sbin/losetup", "-d", self.loopdev]) - if rc != 0: - logging.info("Second call to losetup -d Failed for %s", self.loopdev) - return - logging.info("Second call to losetup -d Succeeded for %s ", self.loopdev) - def loopsetup(self): if self.losetup: --- 137,146 ---- self.diskmount.unmount() def lounsetup(self): ! if self.losetup: ! rc = subprocess.call(["/sbin/losetup", "-d", self.loopdev]) self.losetup = False self.loopdev = None def loopsetup(self): if self.losetup: *************** *** 404,430 **** if rc == 0: self.mounted = False else: ! logging.info("Second attempt Unmounting directory %s" % self.mountdir) ! logging.info("Calling fuser -ck on %s" % self.mountdir) ! rc = subprocess.call(["/sbin/fuser", '-ck', self.mountdir]) ! logging.info("fuser -ck returned %s" % rc ) ! ! time.sleep(2) ! ! rc = subprocess.call(["/bin/umount", self.mountdir]) ! if rc == 0: ! logging.info("Second umount succeeded") self.mounted = False - else: - logging.debug("Second Unmounting directory %s failed, using lazy umount" % self.mountdir) - print >> sys.stdout, "Second Unmounting directory %s failed, using lazy umount" %self.mountdir - rc = subprocess.call(["/bin/umount", "-l", self.mountdir]) - if rc != 0: - raise MountError("Unable to unmount filesystem at %s" % self.mountdir) - else: - logging.debug("lazy umount succeeded on %s" % self.mountdir) - print >> sys.stdout, "lazy umount succeeded on %s" % self.mountdir - self.mounted = False if self.rmdir and not self.mounted: try: --- 366,380 ---- if rc == 0: self.mounted = False else: ! logging.debug("Unmounting directory %s failed, using lazy umount" % self.mountdir) ! print >> sys.stdout, "Unmounting directory %s failed, using lazy umount" %self.mountdir ! rc = subprocess.call(["/bin/umount", "-l", self.mountdir]) ! if rc != 0: ! raise MountError("Unable to unmount filesystem at %s" % self.mountdir) ! else: ! logging.debug("lazy umount succeeded on %s" % self.mountdir) ! print >> sys.stdout, "lazy umount succeeded on %s" % self.mountdir self.mounted = False if self.rmdir and not self.mounted: try:
The entire modified fs.py file from livecd-tools-13.4.5-1.el6.x86_64.rpm lives here.