598a3740c6
virtualization on 32-bit exposes host crash CVE-2013-0151-xsa34.patch - bnc#797287 - VUL-0: Xen: XSA-35 (CVE-2013-0152) - Nested HVM exposes host to being driven out of memory by guest CVE-2013-0152-xsa35.patch - bnc#793717 - NetWare will not boot on Xen 4.2 xnloader.py domUloader.py pygrub-netware-xnloader.patch Removed reverse-24757-use-grant-references.patch - bnc#797523 - VUL-1: CVE-2012-6075: qemu / kvm-qemu: e1000 overflows under some conditions CVE-2012-6075-xsa41.patch - Mask the floating point exceptions for guests like NetWare on machines that support XSAVE. x86-fpu-context-conditional.patch - fate##313584: pass bios information to XEN HVM guest 26341-hvm-firmware-passthrough.patch 26342-hvm-firmware-passthrough.patch 26343-hvm-firmware-passthrough.patch 26344-hvm-firmware-passthrough.patch OBS-URL: https://build.opensuse.org/package/show/Virtualization/xen?expand=0&rev=223
554 lines
18 KiB
Python
554 lines
18 KiB
Python
#!/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 > 16*1024*1024:
|
|
raise RuntimeError("Too large file %s (%s larger than 16MB)" \
|
|
% (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)
|