Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP3:GA
xen.5015
domUloader.py
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File domUloader.py of Package xen.5015
#!/usr/bin/env python # domUloader.py """Loader for kernel and (optional) ramdisk from domU filesystem Given a physical disk (or disk image) for a domU and the path of a kernel and optional ramdisk, copies the kernel and ramdisk from the domU disk to a temporary location in dom0. The --entry parameter specifies the location of the kernel (and optional ramdisk) within the domU filesystem. dev is the disk as seen by domU. Filenames are relative to that filesystem. The disk is passed as the last parameter. It must be a block device or raw disk image. More complex disk images (QCOW, VMDK, etc) must already be configured via blktap and presented as a block device. The script writes an sxpr specifying the locations of the copied kernel and ramdisk into the file specified by --output (default is stdout). Limitations: - It is assumed both kernel and ramdisk are on the same filesystem. - domUs might use LVM; the script currently does not have support for setting up LVM mappings for domUs; it's not trivial and we might risk namespace conflicts. If you want to use LVM inside domUs, set up a small non-LVM boot partition and specify it in bootentry. The script uses kpartx (multipath-tools) to create mappings for devices that are exported as whole disk devices that are partitioned. (c) 01/2006 Novell Inc License: GNU GPL Author: Kurt Garloff <garloff@suse.de> """ import os, sys, getopt from stat import * from xen.xend import sxp import tempfile import time import xnloader # Global options quiet = False verbose = False dryrun = False tmpdir = '/var/lib/xen/tmp' in_args = '' # kpartx, left to its own devices, does not consistently pick the # same partition separator. Explicitly specify it. kpartx_args = '-p -part' # Helper functions def kpartx_has_opt(opt): """ Return True if kpartx supports option opt, otherwise False""" have_opt = True kpartx_cmd = 'kpartx -' + opt + ' 2>&1' p = os.popen(kpartx_cmd) for line in p.readlines(): if line.find('invalid option') >= 0: have_opt = False break p.close() return have_opt def error(s): print >> sys.stderr, "domUloader error: %s" % s def verbose_print(s): if verbose: print >> sys.stderr, "domUloader: %s" % s def traildigits(strg): """Return the trailing digits, used to split the partition number off""" idx = len(strg)-1 while strg[idx].isdigit(): if len == 0: return strg idx -= 1 return strg[idx+1:] def getWholedisk(part): while len(part) and part[len(part)-1].isdigit(): part = part[:-1] return part #def isWholedisk(domUname): # """Determines whether dev is a wholedisk dev""" # return not domUname[-1:].isdigit() # If available, add '-f' option (bnc#613584) if kpartx_has_opt('f'): kpartx_args += ' -f' class Wholedisk: "Class representing a whole disk that may have partitions" def __init__(self, vdev, pdev): "c'tor: set up" # Initialize object; will not raise: self.ldev = None self.vdev = vdev self.pdev = pdev self.mapped = 0 self.partitions = [] self.pcount = 0 self.lvm = False # Finish initialization; may raise: self.is_blk = (S_ISBLK(os.stat(pdev)[ST_MODE])) self.pcount = self.scanpartitions() def physdev(self): """Gets the physical device used to access the device from dom0""" if self.ldev: return self.ldev return self.pdev def findPart(self, vdev): "Find device dev in list of partitions" if len(vdev) > 5 and vdev[:5] == "/dev/": vdev = vdev[5:] for part in self.partitions: if vdev == part.vdev: return part if len(self.partitions): return self.partitions[0] return None def loopsetup(self): """Sets up the loop mapping for a disk image. Will raise if no loopbacks are available. """ if not self.is_blk and not self.ldev: # Loops through all loopback devices, attempting to # find a free one to set up. Don't scan for free and # then try to set it up as a separate step - too racy! i = 0 while True: ldev = '/dev/loop%i' % (i) if not os.path.exists(ldev): break i += 1 fd = os.popen("losetup %s '%s' 2> /dev/null" % (ldev, self.pdev)) if not fd.close(): verbose_print("losetup %s '%s'" % (ldev, self.pdev)) self.ldev = ldev break if not self.ldev: raise RuntimeError("No free loop device found") def loopclean(self): """Delete the loop mapping. Will never raise. """ if self.ldev: verbose_print("losetup -d %s" % self.ldev) # Even seemingly innocent queries like "losetup /dev/loop0" # can temporarily block the loopback and cause transient # failures deleting the loopback, hence the retry logic. retries = 10 while retries: fd = os.popen("losetup -d %s" % self.ldev) if not fd.close(): self.ldev = None break else: # Mappings may not have been deleted due to race # between udev and dm - see bnc#379032. Causes # loop devices to leak. Call kpartx -d again os.system("kpartx %s -d '%s'" % (kpartx_args, self.physdev())) time.sleep(0.1) retries -= 1 def scanlvmpartitions(self): pcount = 0 verbose_print("vgchange -ay '%s'" % (self.vdev)) ret = os.system("vgchange -ay '%s' > /dev/null 2>&1" % (self.vdev)) >> 8 if not ret: self.lvm = True verbose_print("lvscan | grep '/dev/%s'" % (self.vdev)) fd = os.popen("lvscan | grep '/dev/%s'" % (self.vdev)) for line in fd.readlines(): line = line.strip() (t1, lvname, t2) = line.split('\'') pname = lvname[lvname.rfind('/')+1:] pname = pname.strip() pname = "/dev/mapper/" + self.vdev + "-" + pname verbose_print("Found partition: vdev %s, pdev %s" % (self.vdev, pname)) self.partitions.append(Partition(self, self.vdev, pname)) pcount += 1 fd.close() verbose_print("vgchange -an '%s'" % (self.vdev)) os.system("vgchange -an '%s' > /dev/null 2>&1" % (self.vdev)) else: verbose_print("vgchange -ay %s ... failed: -%d" % (self.vdev, ret)) return pcount def scanpartitions(self): """Scan device for partitions (kpartx -l) and set up data structures, Returns number of partitions found.""" self.loopsetup() # TODO: We could use fdisk -l instead and look at the type of # partitions; this way we could also detect LVM and support it. verbose_print("kpartx %s -l '%s'" % (kpartx_args, self.physdev())) fd = os.popen("kpartx %s -l '%s'" % (kpartx_args, self.physdev())) pcount = 0 for line in fd.readlines(): line = line.strip() verbose_print("kpartx -l: %s" % (line,)) (pname, params) = line.split(' : ') pname = pname.strip() pno = int(traildigits(pname)) #if pname.rfind('/') != -1: # pname = pname[pname.rfind('/')+1:] #pname = self.pdev[:self.pdev.rfind('/')] + '/' + pname pname = "/dev/mapper/" + pname verbose_print("Found partition: vdev %s, pdev %s" % ('%s%i' % (self.vdev, pno), pname)) self.partitions.append(Partition(self, '%s%i' % (self.vdev, pno), pname)) pcount += 1 fd.close() # Try lvm if not pcount: pcount = self.scanlvmpartitions() # Add self to partition table if not pcount: if self.ldev: ref = self else: ref = None self.partitions.append(Partition(ref, self.vdev, self.pdev)) return pcount def activatepartitions(self): "Set up loop mapping and device-mapper mappings" verbose_print("activatepartitions") if not self.mapped: self.loopsetup() if self.pcount: verbose_print("kpartx %s -a '%s'" % (kpartx_args, self.physdev())) fd = os.popen("kpartx %s -a '%s'" % (kpartx_args, self.physdev())) fd.close() if self.pcount and self.lvm: verbose_print("vgchange -ay '%s'" % (self.vdev)) ret = os.system("vgchange -ay '%s' > /dev/null 2>&1" % (self.vdev)) >> 8 if not ret: verbose_print("lvchange -ay '%s'" % (self.vdev)) os.system("lvchange -ay '%s' > /dev/null 2>&1" % (self.vdev)) self.mapped += 1 def partitionsdeactivated(self): "Return True if partition mappings have been removed, False otherwise" for part in self.partitions: if os.access(part.pdev, os.F_OK): return False return True def deactivatepartitions(self): """Remove device-mapper mappings and loop mapping. Will never raise. """ verbose_print("deactivatepartitions") if not self.mapped: return self.mapped -= 1 if not self.mapped: if self.pcount: retries = 10 while retries and not self.partitionsdeactivated(): verbose_print("kpartx %s -d '%s'" % (kpartx_args, self.physdev())) os.system("kpartx %s -d '%s'" % (kpartx_args, self.physdev())) time.sleep(0.1) retries -= 1 if retries == 0: error("unable to remove partition mappings with kpartx -d") if self.pcount and self.lvm: verbose_print("lvchange -an '%s'" % (self.vdev)) ret = os.system("lvchange -an '%s' > /dev/null 2>&1" % (self.vdev)) >> 8 if ret: time.sleep(0.3) os.system("lvchange -an '/dev/%s' > /dev/null 2>&1" % (self.vdev)) verbose_print("vgchange -an '%s'" % (self.vdev)) ret = os.system("vgchange -an '%s' > /dev/null 2>&1" % (self.vdev)) >> 8 if ret: time.sleep(0.3) os.system("vgchange -an '%s' > /dev/null 2>&1" % (self.vdev)) self.loopclean() def __del__(self): "d'tor: clean up" self.deactivatepartitions() self.loopclean() def __repr__(self): "string representation for debugging" strg = "[" + self.vdev + "," + self.pdev + "," if self.ldev: strg += self.ldev strg += "," + str(self.pcount) + ",mapped %ix]" % self.mapped return strg class Partition: """Class representing a domU filesystem (partition) that can be mounted in dom0""" def __init__(self, whole = None, vdev = None, pdev = None): "c'tor: setup" self.wholedisk = whole self.vdev = vdev self.pdev = pdev self.mountpoint = None def __del__(self): "d'tor: cleanup" if self.mountpoint: self.umount() # Not needed: Refcounting will take care of it. #if self.wholedisk: # self.wholedisk.deactivatepartitions() def __repr__(self): "string representation for debugging" strg = "[" + self.vdev + "," + self.pdev + "," if self.mountpoint: strg += "mounted on " + self.mountpoint + "," else: strg += "not mounted," if self.wholedisk: return strg + self.wholedisk.__repr__() + "]" else: return strg + "]" def mount(self, fstype = None, options = "ro"): "mount filesystem, sets self.mountpoint" if self.mountpoint: return if self.wholedisk: self.wholedisk.activatepartitions() mtpt = tempfile.mkdtemp(prefix = "%s." % self.vdev, dir = tmpdir) mopts = "" if fstype: mopts += " -t %s" % fstype if options: mopts += " -o %s" % options verbose_print("mount %s '%s' %s" % (mopts, self.pdev, mtpt)) fd = os.popen("mount %s '%s' %s" % (mopts, self.pdev, mtpt)) err = fd.close() if err: try: os.rmdir(mtpt) except: pass raise RuntimeError("Error %i from mount %s '%s' on %s" % \ (err, mopts, self.pdev, mtpt)) self.mountpoint = mtpt def umount(self): """umount filesystem at self.mountpoint""" if not self.mountpoint: return verbose_print("umount %s" % self.mountpoint) fd = os.popen("umount %s" % self.mountpoint) err = fd.close() try: os.rmdir(self.mountpoint) except: pass if err: error("Error %i from umount %s" % (err, self.mountpoint)) else: self.mountpoint = None if self.wholedisk: self.wholedisk.deactivatepartitions() def parseEntry(entry): "disects bootentry and returns vdev, kernel, ramdisk" def bad(): raise RuntimeError, "Malformed --entry" fsspl = entry.split(':') if len(fsspl) != 2: bad() vdev = fsspl[0] entry = fsspl[1] enspl = entry.split(',') if len(enspl) not in (1, 2): bad() # Prepend '/' if missing kernel = enspl[0] if kernel == '': bad() if kernel[0] != '/': kernel = '/' + kernel ramdisk = None if len(enspl) > 1: ramdisk = enspl[1] if ramdisk != '' and ramdisk[0] != '/': ramdisk = '/' + ramdisk return vdev, kernel, ramdisk def copyFile(src, dst): "Wrapper for shutil.filecopy" import shutil verbose_print("cp %s %s" % (src, dst)) stat = os.stat(src) if stat.st_size > 32*1024*1024: raise RuntimeError("Too large file %s (%s larger than 32MB)" \ % (src, stat.st_size)) try: shutil.copyfile(src, dst) except: os.unlink(dst) raise() def copyKernelAndRamdisk(disk, vdev, kernel, ramdisk): """Finds vdev in list of partitions, mounts the partition, copies kernel [and ramdisk] off to dom0 files, umounts the parition again, and returns sxpr pointing to these copies.""" verbose_print("copyKernelAndRamdisk(%s, %s, %s, %s)" % (disk, vdev, kernel, ramdisk)) if dryrun: return "linux (kernel kernel.dummy) (ramdisk ramdisk.dummy)" part = disk.findPart(vdev) if not part: raise RuntimeError("Partition '%s' does not exist" % vdev) part.mount() try: (fd, knm) = tempfile.mkstemp(prefix = "kernel.", dir = tmpdir) os.close(fd) copyFile(part.mountpoint + kernel, knm) except: os.unlink(knm) part.umount() raise if not quiet: print "Copy kernel %s from %s to %s for booting" % (kernel, vdev, knm) sxpr = "linux (kernel %s)" % knm if ramdisk: try: (fd, inm) = tempfile.mkstemp(prefix = "ramdisk.", dir = tmpdir) os.close(fd) copyFile(part.mountpoint + ramdisk, inm) except: os.unlink(knm) os.unlink(inm) part.umount() raise sxpr += "(ramdisk %s)" % inm part.umount() xnloader.patch_netware_loader(knm) return sxpr def main(argv): "Main routine: Parses options etc." global quiet, dryrun, verbose, tmpdir, in_args def usage(): "Help output (usage info)" global verbose, quiet, dryrun print >> sys.stderr, "domUloader usage: domUloader [--output=fd] [--quiet] [--dryrun] [--verbose]\n" +\ "[--args] [--help] --entry=dev:kernel[,ramdisk] physdisk [virtdisk]\n" +\ "\n" +\ "dev format: hd[a-p][0-9]*, xvd[a-p][0-9]*, LVM-vgname-lvname\n" print >> sys.stderr, __doc__ try: (optlist, args) = getopt.gnu_getopt(argv, 'qvh', \ ('entry=', 'output=', 'tmpdir=', 'args=', 'kernel=', 'ramdisk=', 'help', 'quiet', 'dryrun', 'verbose')) except: usage() sys.exit(1) entry = None output = None pdisk = None vdisk = None for (opt, oarg) in optlist: if opt in ('-h', '--help'): usage() sys.exit(1) elif opt in ('-q', '--quiet'): quiet = True elif opt in ('-n', '--dryrun'): dryrun = True elif opt in ('-v', '--verbose'): verbose = True elif opt == '--output': output = oarg elif opt == '--entry': entry = oarg elif opt == '--tmpdir': tmpdir = oarg elif opt == '--args': in_args = oarg verbose_print(str(argv)) if args: if len(args) == 2: pdisk = args[1] elif len(args) == 3: pdisk = args[1] vdisk = args[2] if not entry or not pdisk: usage() sys.exit(1) if output is None or output == "-": fd = sys.stdout.fileno() else: fd = os.open(output, os.O_WRONLY) if not os.access(tmpdir, os.X_OK): os.mkdir(tmpdir) os.chmod(tmpdir, 0750) vdev, kernel, ramdisk = parseEntry(entry) if vdev[:vdev.find('-')] == "LVM": vdev = vdev.split('-')[1] if not vdisk: vdisk = getWholedisk(vdev) verbose_print("vdisk not specified; guessing '%s' based on '%s'" % (vdisk, vdev)) if not vdev.startswith(vdisk): error("Virtual disk '%s' does not match entry '%s'" % (vdisk, entry)) sys.exit(1) disk = Wholedisk(vdisk, pdisk) r = 0 try: sxpr = copyKernelAndRamdisk(disk, vdev, kernel, ramdisk) if in_args: sxpr += "(args '%s')" % in_args os.write(fd, sxpr) except Exception, e: error(str(e)) r = 1 for part in disk.partitions: part.wholedisk = None del disk return r # Call main if called (and not imported) if __name__ == "__main__": r = 1 try: r = main(sys.argv) except Exception, e: error(str(e)) sys.exit(r)
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor