From: Jon Ludlam <jonathan.ludlam@eu.citrix.com>
Date: Fri, 7 Oct 2011 14:44:46 +0100
Subject: pygrub-path
---
ocaml/xapi/xapi_globs.ml | 2 +-
scripts/OMakefile | 2 +
scripts/pygrub.xcp | 913 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 916 insertions(+), 1 deletions(-)
create mode 100644 scripts/pygrub.xcp
diff --git a/ocaml/xapi/xapi_globs.ml b/ocaml/xapi/xapi_globs.ml
index 7b16d6b..355c4ed 100644
--- a/ocaml/xapi/xapi_globs.ml
+++ b/ocaml/xapi/xapi_globs.ml
@@ -136,7 +136,7 @@ let software_version = [ _product_version, Version.product_version;
_hostname, Version.hostname;
_date, Version.date]
-let pygrub_path = "/usr/bin/pygrub"
+let pygrub_path = Filename.concat Fhs.libexecdir "pygrub.xcp"
let eliloader_path = "/usr/bin/eliloader"
let supported_bootloaders = [ "pygrub", pygrub_path;
"eliloader", eliloader_path ]
diff --git a/scripts/OMakefile b/scripts/OMakefile
index 9ad8fba..c17ae05 100644
--- a/scripts/OMakefile
+++ b/scripts/OMakefile
@@ -109,6 +109,8 @@ install:
mkdir -p $(DESTDIR)$(HOOKSDIR)/host-post-declare-dead
$(IPROG) 10resetvdis $(DESTDIR)$(HOOKSDIR)/host-post-declare-dead
$(IPROG) pci-info $(DESTDIR)$(LIBEXECDIR)
+ $(IPROG) pygrub.xcp $(DESTDIR)$(LIBEXECDIR)
+
.PHONY: sdk-install
sdk-install: install
diff --git a/scripts/pygrub.xcp b/scripts/pygrub.xcp
new file mode 100644
index 0000000..b52f364
--- /dev/null
+++ b/scripts/pygrub.xcp
@@ -0,0 +1,913 @@
+#! /usr/bin/env python
+#
+# pygrub - simple python-based bootloader for Xen
+#
+# Copyright 2005-2006 Red Hat, Inc.
+# Jeremy Katz <katzj@redhat.com>
+#
+# This software may be freely redistributed under the terms of the GNU
+# general public license.
+#
+# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+import os, sys, atexit, string, struct, tempfile, re
+
+sys.path.append("/usr/lib/xen-4.1/lib/python")
+
+import copy
+import logging
+import platform
+import xen.lowlevel.xc
+
+import curses, _curses, curses.wrapper, curses.textpad, curses.ascii
+import getopt
+
+import fsimage
+import grub.GrubConf
+import grub.LiloConf
+import grub.ExtLinuxConf
+
+import syslog
+
+PYGRUB_VER = 0.6
+
+def enable_cursor(ison):
+ if ison:
+ val = 2
+ else:
+ val = 0
+
+ try:
+ curses.curs_set(val)
+ except _curses.error:
+ pass
+
+def is_disk_image(file):
+ fd = os.open(file, os.O_RDONLY)
+ buf = os.read(fd, 512)
+ os.close(fd)
+
+ if len(buf) >= 512 and \
+ struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,):
+ return True
+ return False
+
+SECTOR_SIZE=512
+DK_LABEL_LOC=1
+DKL_MAGIC=0xdabe
+V_ROOT=0x2
+
+def get_solaris_slice(file, offset):
+ """Find the root slice in a Solaris VTOC."""
+
+ fd = os.open(file, os.O_RDONLY)
+ os.lseek(fd, offset + (DK_LABEL_LOC * SECTOR_SIZE), 0)
+ buf = os.read(fd, 512)
+ if struct.unpack("<H", buf[508:510])[0] != DKL_MAGIC:
+ raise RuntimeError, "Invalid disklabel magic"
+
+ nslices = struct.unpack("<H", buf[30:32])[0]
+
+ for i in range(nslices):
+ sliceoff = 72 + 12 * i
+ slicetag = struct.unpack("<H", buf[sliceoff:sliceoff+2])[0]
+ slicesect = struct.unpack("<L", buf[sliceoff+4:sliceoff+8])[0]
+ if slicetag == V_ROOT:
+ return slicesect * SECTOR_SIZE
+
+ raise RuntimeError, "No root slice found"
+
+def get_fs_offset_gpt(file):
+ fd = os.open(file, os.O_RDONLY)
+ # assume the first partition is an EFI system partition.
+ os.lseek(fd, SECTOR_SIZE * 2, 0)
+ buf = os.read(fd, 512)
+ return struct.unpack("<Q", buf[32:40])[0] * SECTOR_SIZE
+
+FDISK_PART_SOLARIS=0xbf
+FDISK_PART_SOLARIS_OLD=0x82
+FDISK_PART_GPT=0xee
+
+def get_partition_offsets(file):
+ if not is_disk_image(file):
+ # No MBR: assume whole disk filesystem, which is like a
+ # single partition starting at 0
+ return [0]
+
+ part_offs = []
+
+ fd = os.open(file, os.O_RDONLY)
+ buf = os.read(fd, 512)
+ for poff in (446, 462, 478, 494): # partition offsets
+
+ # MBR contains a 16 byte descriptor per partition
+ partbuf = buf[poff:poff+16]
+ offset = struct.unpack("<L", partbuf[8:12])[0] * SECTOR_SIZE
+ type = struct.unpack("<B", partbuf[4:5])[0]
+
+ # offset == 0 implies this partition is not enabled
+ if offset == 0:
+ continue
+
+ if type == FDISK_PART_SOLARIS or type == FDISK_PART_SOLARIS_OLD:
+ try:
+ offset += get_solaris_slice(file, offset)
+ except RuntimeError:
+ continue # no solaris magic at that offset, ignore partition
+
+ if type == FDISK_PART_GPT:
+ offset = get_fs_offset_gpt(file)
+
+ # Active partition has 0x80 as the first byte.
+ # If active, prepend to front of list, otherwise append to back.
+ if struct.unpack("<c", buf[poff:poff+1]) == ('\x80',):
+ part_offs.insert(0, offset)
+ else:
+ part_offs.append(offset)
+
+ return part_offs
+
+class GrubLineEditor(curses.textpad.Textbox):
+ def __init__(self, screen, startx, starty, line = ""):
+ screen.addstr(startx, starty, "> ")
+ screen.noutrefresh()
+ win = curses.newwin(1, 74, startx, starty + 2)
+ curses.textpad.Textbox.__init__(self, win)
+
+ self.line = list(line)
+ self.pos = len(line)
+ self.cancelled = False
+ self.show_text()
+
+ def show_text(self):
+ """Show the text. One of our advantages over standard textboxes
+ is that we can handle lines longer than the window."""
+
+ self.win.erase()
+ p = self.pos
+ off = 0
+ while p > 70:
+ p -= 55
+ off += 55
+
+ l = self.line[off:off+70]
+ self.win.addstr(0, 0, string.join(l, ("")))
+ if self.pos > 70:
+ self.win.addch(0, 0, curses.ACS_LARROW)
+
+ self.win.move(0, p)
+
+ def do_command(self, ch):
+ # we handle escape as well as moving the line around, so have
+ # to override some of the default handling
+
+ self.lastcmd = ch
+ if ch == 27: # esc
+ self.cancelled = True
+ return 0
+ elif curses.ascii.isprint(ch):
+ self.line.insert(self.pos, chr(ch))
+ self.pos += 1
+ elif ch == curses.ascii.SOH: # ^a
+ self.pos = 0
+ elif ch in (curses.ascii.STX,curses.KEY_LEFT):
+ if self.pos > 0:
+ self.pos -= 1
+ elif ch in (curses.ascii.BS,curses.KEY_BACKSPACE):
+ if self.pos > 0:
+ self.pos -= 1
+ if self.pos < len(self.line):
+ self.line.pop(self.pos)
+ elif ch == curses.ascii.EOT: # ^d
+ if self.pos < len(self.line):
+ self.line.pop(self.pos)
+ elif ch == curses.ascii.ENQ: # ^e
+ self.pos = len(self.line)
+ elif ch in (curses.ascii.ACK, curses.KEY_RIGHT):
+ if self.pos < len(self.line):
+ self.pos +=1
+ elif ch == curses.ascii.VT: # ^k
+ self.line = self.line[:self.pos]
+ else:
+ return curses.textpad.Textbox.do_command(self, ch)
+ self.show_text()
+ return 1
+
+ def edit(self):
+ curses.doupdate()
+ r = curses.textpad.Textbox.edit(self)
+ if self.cancelled:
+ return None
+ return string.join(self.line, "")
+
+
+class Grub:
+ def __init__(self, file, fs = None):
+ self.screen = None
+ self.entry_win = None
+ self.text_win = None
+ if file:
+ self.read_config(file, fs)
+
+ def draw_main_windows(self):
+ if self.screen is None: #only init stuff once
+ self.screen = curses.initscr()
+ self.screen.timeout(1000)
+ if hasattr(curses, 'use_default_colors'):
+ try:
+ curses.use_default_colors()
+ except:
+ pass # Not important if we can't use colour
+ enable_cursor(False)
+ self.entry_win = curses.newwin(10, 74, 2, 1)
+ self.text_win = curses.newwin(10, 70, 12, 5)
+ curses.def_prog_mode()
+
+ curses.reset_prog_mode()
+ self.screen.erase()
+
+ # create basic grub screen with a box of entries and a textbox
+ self.screen.addstr(1, 4, "pyGRUB version %s" %(PYGRUB_VER,))
+ self.entry_win.box()
+ self.screen.noutrefresh()
+
+ def fill_entry_list(self):
+ self.entry_win.erase()
+ self.entry_win.box()
+
+ maxy = self.entry_win.getmaxyx()[0]-3 # maxy - 2 for the frame + index
+ if self.selected_image > self.start_image + maxy:
+ self.start_image = self.selected_image
+ if self.selected_image < self.start_image:
+ self.start_image = self.selected_image
+
+ for y in range(self.start_image, len(self.cf.images)):
+ i = self.cf.images[y]
+ if y > self.start_image + maxy:
+ break
+ if y == self.selected_image:
+ self.entry_win.attron(curses.A_REVERSE)
+ self.entry_win.addstr(y + 1 - self.start_image, 2, i.title.expandtabs().ljust(70))
+ if y == self.selected_image:
+ self.entry_win.attroff(curses.A_REVERSE)
+ self.entry_win.noutrefresh()
+
+ def edit_entry(self, origimg):
+ def draw():
+ self.draw_main_windows()
+
+ self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
+ self.text_win.addstr(1, 0, "Press 'b' to boot, 'e' to edit the selected command in the")
+ self.text_win.addstr(2, 0, "boot sequence, 'c' for a command-line, 'o' to open a new line")
+ self.text_win.addstr(3, 0, "after ('O' for before) the selected line, 'd' to remove the")
+ self.text_win.addstr(4, 0, "selected line, or escape to go back to the main menu.")
+ self.text_win.addch(0, 8, curses.ACS_UARROW)
+ self.text_win.addch(0, 14, curses.ACS_DARROW)
+ (y, x) = self.text_win.getmaxyx()
+ self.text_win.move(y - 1, x - 1)
+ self.text_win.noutrefresh()
+
+ curline = 0
+ img = copy.deepcopy(origimg)
+ while 1:
+ draw()
+ self.entry_win.erase()
+ self.entry_win.box()
+ for idx in range(0, len(img.lines)):
+ # current line should be highlighted
+ if idx == curline:
+ self.entry_win.attron(curses.A_REVERSE)
+
+ # trim the line
+ l = img.lines[idx].expandtabs().ljust(70)
+ if len(l) > 70:
+ l = l[:69] + ">"
+
+ self.entry_win.addstr(idx + 1, 2, l)
+ if idx == curline:
+ self.entry_win.attroff(curses.A_REVERSE)
+ self.entry_win.noutrefresh()
+ curses.doupdate()
+
+ c = self.screen.getch()
+ if c in (ord('q'), 27): # 27 == esc
+ break
+ elif c == curses.KEY_UP:
+ curline -= 1
+ elif c == curses.KEY_DOWN:
+ curline += 1
+ elif c == ord('b'):
+ self.isdone = True
+ break
+ elif c == ord('e'):
+ l = self.edit_line(img.lines[curline])
+ if l is not None:
+ img.set_from_line(l, replace = curline)
+ elif c == ord('d'):
+ img.lines.pop(curline)
+ elif c == ord('o'):
+ img.lines.insert(curline+1, "")
+ curline += 1
+ elif c == ord('O'):
+ img.lines.insert(curline, "")
+ elif c == ord('c'):
+ self.command_line_mode()
+ if self.isdone:
+ return
+
+ # bound at the top and bottom
+ if curline < 0:
+ curline = 0
+ elif curline >= len(img.lines):
+ curline = len(img.lines) - 1
+
+ if self.isdone:
+ # Fix to allow pygrub command-line editing in Lilo bootloader (used by IA64)
+ if platform.machine() == 'ia64':
+ origimg.reset(img.lines, img.path)
+ else:
+ origimg.reset(img.lines)
+
+ def edit_line(self, line):
+ self.screen.erase()
+ self.screen.addstr(1, 2, "[ Minimal BASH-like line editing is supported. ")
+ self.screen.addstr(2, 2, " ESC at any time cancels. ENTER at any time accepts your changes. ]")
+ self.screen.noutrefresh()
+
+ t = GrubLineEditor(self.screen, 5, 2, line)
+ enable_cursor(True)
+ ret = t.edit()
+ if ret:
+ return ret
+ return None
+
+ def command_line_mode(self):
+ self.screen.erase()
+ self.screen.addstr(1, 2, "[ Minimal BASH-like line editing is supported. ESC at any time ")
+ self.screen.addstr(2, 2, " exits. Typing 'boot' will boot with your entered commands. ] ")
+ self.screen.noutrefresh()
+
+ y = 5
+ lines = []
+ while 1:
+ t = GrubLineEditor(self.screen, y, 2)
+ enable_cursor(True)
+ ret = t.edit()
+ if ret:
+ if ret in ("quit", "return"):
+ break
+ elif ret != "boot":
+ y += 1
+ lines.append(ret)
+ continue
+
+ # if we got boot, then we want to boot the entered image
+ img = self.cf.new_image("entered", lines)
+ self.cf.add_image(img)
+ self.selected_image = len(self.cf.images) - 1
+ self.isdone = True
+ break
+
+ # else, we cancelled and should just go back
+ break
+
+ def read_config(self, fn, fs = None):
+ """Read the given file to parse the config. If fs = None, then
+ we're being given a raw config file rather than a disk image."""
+
+ if not os.access(fn, os.R_OK):
+ raise RuntimeError, "Unable to access %s" %(fn,)
+
+ if platform.machine() == 'ia64':
+ cfg_list = map(lambda x: (x,grub.LiloConf.LiloConfigFile),
+ # common distributions
+ ["/efi/debian/elilo.conf", "/efi/gentoo/elilo.conf",
+ "/efi/redflag/elilo.conf", "/efi/redhat/elilo.conf",
+ "/efi/SuSE/elilo.conf",] +
+ # fallbacks
+ ["/efi/boot/elilo.conf", "/elilo.conf",])
+ else:
+ cfg_list = map(lambda x: (x,grub.GrubConf.GrubConfigFile),
+ ["/boot/grub/menu.lst", "/boot/grub/grub.conf",
+ "/grub/menu.lst", "/grub/grub.conf"]) + \
+ map(lambda x: (x,grub.GrubConf.Grub2ConfigFile),
+ ["/boot/grub/grub.cfg", "/grub/grub.cfg"]) + \
+ map(lambda x: (x,grub.ExtLinuxConf.ExtLinuxConfigFile),
+ ["/boot/isolinux/isolinux.cfg",
+ "/boot/extlinux.conf"])
+
+ if not fs:
+ # set the config file and parse it
+ for f,parser in cfg_list:
+ self.cf = parser()
+ self.cf.filename = fn
+ self.cf.parse()
+ return
+
+ for f,parser in cfg_list:
+ if fs.file_exists(f):
+ print >>sys.stderr, "Using %s to parse %s" % (parser,f)
+ self.cf = parser()
+ self.cf.filename = f
+ break
+ if self.__dict__.get('cf', None) is None:
+ raise RuntimeError, "couldn't find bootloader config file in the image provided."
+ f = fs.open_file(self.cf.filename)
+ buf = f.read()
+ del f
+ self.cf.parse(buf)
+
+ def run(self):
+ timeout = int(self.cf.timeout)
+
+ self.selected_image = self.cf.default
+ # If the selected (default) image doesn't exist we select the first entry
+ if self.selected_image > len(self.cf.images):
+ self.selected_image = 0
+ self.isdone = False
+ while not self.isdone:
+ self.run_main(timeout)
+ timeout = -1
+
+ return self.selected_image
+
+ def run_main(self, timeout = -1):
+ def draw():
+ # set up the screen
+ self.draw_main_windows()
+
+ if not self.cf.hasPassword() or self.cf.hasPasswordAccess():
+ self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
+ self.text_win.addstr(1, 0, "Press enter to boot the selected OS, 'e' to edit the")
+ self.text_win.addstr(2, 0, "commands before booting, 'a' to modify the kernel arguments ")
+ self.text_win.addstr(3, 0, "before booting, or 'c' for a command line.")
+
+ else:
+ self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
+ self.text_win.addstr(1, 0, "Press enter to boot the selected OS or `p` to enter a")
+ self.text_win.addstr(2, 0, "password to unlock the next set of features.")
+
+ self.text_win.addch(0, 8, curses.ACS_UARROW)
+ self.text_win.addch(0, 14, curses.ACS_DARROW)
+ (y, x) = self.text_win.getmaxyx()
+ self.text_win.move(y - 1, x - 1)
+ self.text_win.noutrefresh()
+
+ # now loop until we hit the timeout or get a go from the user
+ mytime = 0
+ self.start_image = 0
+ while (timeout == -1 or mytime < int(timeout)):
+ draw()
+ if timeout != -1 and mytime != -1:
+ self.screen.addstr(20, 5, "Will boot selected entry in %2d seconds"
+ %(int(timeout) - mytime))
+ else:
+ self.screen.addstr(20, 5, " " * 80)
+ self.fill_entry_list()
+ curses.doupdate()
+
+ c = self.screen.getch()
+ if c == -1:
+ # Timed out waiting for a keypress
+ if mytime != -1:
+ mytime += 1
+ if mytime >= int(timeout):
+ self.isdone = True
+ break
+ else:
+ # received a keypress: stop the timer
+ mytime = -1
+ self.screen.timeout(-1)
+
+ # handle keypresses
+ if c == ord('c') and self.cf.hasPasswordAccess():
+ self.command_line_mode()
+ break
+ elif c == ord('a') and self.cf.hasPasswordAccess():
+ # find the kernel line, edit it and then boot
+ img = self.cf.images[self.selected_image]
+ for line in img.lines:
+ if line.startswith("kernel"):
+ l = self.edit_line(line)
+ if l is not None:
+ img.set_from_line(l, replace = True)
+ self.isdone = True
+ break
+ break
+ elif c == ord('e') and self.cf.hasPasswordAccess():
+ img = self.cf.images[self.selected_image]
+ self.edit_entry(img)
+ break
+ elif c == ord('p') and self.cf.hasPassword():
+ self.text_win.addstr(6, 1, "Password: ")
+ pwd = self.text_win.getstr(6, 8)
+ if not self.cf.checkPassword(pwd):
+ self.text_win.addstr(6, 1, "Password: ")
+ if self.cf.passExc is not None:
+ self.text_win.addstr(7, 0, "Exception: %s"
+ % self.cf.passExc)
+ else:
+ self.text_win.addstr(7, 0, "Failed!")
+ self.cf.setPasswordAccess( False )
+ else:
+ self.cf.setPasswordAccess( True )
+ break
+ elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
+ self.isdone = True
+ break
+ elif c == curses.KEY_UP:
+ self.selected_image -= 1
+ elif c == curses.KEY_DOWN:
+ self.selected_image += 1
+# elif c in (ord('q'), 27): # 27 == esc
+# self.selected_image = -1
+# self.isdone = True
+# break
+
+ # bound at the top and bottom
+ if self.selected_image < 0:
+ self.selected_image = 0
+ elif self.selected_image >= len(self.cf.images):
+ self.selected_image = len(self.cf.images) - 1
+
+evaluation_lists = []
+
+def readRulesFile(fname):
+ rulefile = open(fname,"r")
+ for line in rulefile:
+ line = line.rstrip('\n')
+ if not re.match(line,"#"):
+ evaluation_lists.append(fname+":"+line)
+
+def readRulesFiles(directory):
+ if os.path.exists(directory):
+ for rulesfile in os.listdir(directory):
+ rulesfilepath = os.path.join(directory,rulesfile)
+ readRulesFile(rulesfilepath)
+
+def evaluateImageAgainstRule(image,ruletext):
+ p=re.compile(r'\s+')
+ elements=p.split(ruletext)
+
+ cand=""
+ ret=None
+
+ if elements[0]=="title":
+ cand=image.title
+
+ for i in range(2,len(elements)):
+ if elements[i] != "":
+ if re.search( elements[i], cand ):
+ if elements[1]=="blacklist":
+ return None
+ if elements[1]=="prefer":
+ return 2
+ if elements[1]=="accept":
+ return 1
+ return 1
+
+def evaluateEntryAsSane(cf, entry):
+ for rule in evaluation_lists:
+ (fname,ruletext)=rule.split(":",1)
+ res = evaluateImageAgainstRule(cf.images[entry],ruletext)
+ if not res:
+ syslog.syslog("\""+ cf.images[entry].title+ "\" is blacklisted by "+rule)
+ return None
+ return 1
+
+def findSuitableDefault(cf, entry):
+ if (evaluateEntryAsSane(cf,entry)):
+ return entry
+
+ for i in range(len(cf.images)):
+ if i != entry:
+ if (evaluateEntryAsSane(cf,i)):
+ return i
+ return entry
+
+def get_entry_idx(cf, entry):
+ # first, see if the given entry is numeric
+ try:
+ idx = string.atoi(entry)
+ return idx
+ except ValueError:
+ pass
+
+ # it's not, now check the labels for a match
+ for i in range(len(cf.images)):
+ if entry == cf.images[i].title:
+ return i
+
+ return None
+
+def run_grub(file, entry, fs, arg):
+ global g
+ global sel
+
+ def run_main(scr, *args):
+ global sel
+ global g
+ sel = g.run()
+
+ g = Grub(file, fs)
+ if interactive:
+ curses.wrapper(run_main)
+ else:
+ sel = g.cf.default
+ try:
+ sel = findSuitableDefault(g.cf, g.cf.default)
+ except ValueError:
+ pass
+ syslog.syslog("\""+ g.cf.images[sel].title+ "\" will be started")
+
+ # set the entry to boot as requested
+ if entry is not None:
+ idx = get_entry_idx(g.cf, entry)
+ if idx is not None and idx > 0 and idx < len(g.cf.images):
+ sel = idx
+
+ if sel == -1:
+ print "No kernel image selected!"
+ sys.exit(1)
+
+ try:
+ img = g.cf.images[sel]
+ except IndexError:
+ img = g.cf.images[0]
+
+ grubcfg = { "kernel": None, "ramdisk": None, "args": None }
+
+ grubcfg["kernel"] = img.kernel[1]
+ if img.initrd:
+ grubcfg["ramdisk"] = img.initrd[1]
+ if img.args:
+ grubcfg["args"] = img.args + " " + arg
+
+ return grubcfg
+
+def supports64bitPVguest():
+ xc = xen.lowlevel.xc.xc()
+ caps = xc.xeninfo()['xen_caps'].split(" ")
+ for cap in caps:
+ if cap == "xen-3.0-x86_64":
+ return True
+ return False
+
+# If nothing has been specified, look for a Solaris domU. If found, perform the
+# necessary tweaks.
+def sniff_solaris(fs, cfg):
+ if not fs.file_exists("/platform/i86xpv/kernel/unix"):
+ return cfg
+
+ if not cfg["kernel"]:
+ if supports64bitPVguest() and \
+ fs.file_exists("/platform/i86xpv/kernel/amd64/unix"):
+ cfg["kernel"] = "/platform/i86xpv/kernel/amd64/unix"
+ cfg["ramdisk"] = "/platform/i86pc/amd64/boot_archive"
+ else:
+ cfg["kernel"] = "/platform/i86xpv/kernel/unix"
+ cfg["ramdisk"] = "/platform/i86pc/boot_archive"
+
+ # Unpleasant. Typically we'll have 'root=foo -k' or 'root=foo /kernel -k',
+ # and we need to maintain Xen properties (root= and ip=) and the kernel
+ # before any user args.
+
+ xenargs = ""
+ userargs = ""
+
+ if not cfg["args"]:
+ cfg["args"] = cfg["kernel"]
+ else:
+ for arg in cfg["args"].split():
+ if re.match("^root=", arg) or re.match("^ip=", arg):
+ xenargs += arg + " "
+ elif arg != cfg["kernel"]:
+ userargs += arg + " "
+ cfg["args"] = xenargs + " " + cfg["kernel"] + " " + userargs
+
+ return cfg
+
+def sniff_netware(fs, cfg):
+ if not fs.file_exists("/nwserver/xnloader.sys"):
+ return cfg
+
+ if not cfg["kernel"]:
+ cfg["kernel"] = "/nwserver/xnloader.sys"
+
+ return cfg
+
+def format_sxp(kernel, ramdisk, args):
+ s = "linux (kernel %s)" % kernel
+ if ramdisk:
+ s += "(ramdisk %s)" % ramdisk
+ if args:
+ s += "(args \"%s\")" % args
+ return s
+
+def format_simple(kernel, ramdisk, args, sep):
+ s = ("kernel %s" % kernel) + sep
+ if ramdisk:
+ s += ("ramdisk %s" % ramdisk) + sep
+ if args:
+ s += ("args %s" % args) + sep
+ s += sep
+ return s
+
+if __name__ == "__main__":
+ sel = None
+ syslog.openlog("PYGRUB");
+ readRulesFiles("/etc/pygrub/rules.d")
+
+ def usage():
+ print >> sys.stderr, "Usage: %s [-q|--quiet] [-i|--interactive] [-n|--not-really] [--output=] [--kernel=] [--ramdisk=] [--args=] [--entry=] [--default_args=] [--extra_args=] <image>" %(sys.argv[0],)
+
+ try:
+ opts, args = getopt.gnu_getopt(sys.argv[1:], 'qinh::',
+ ["quiet", "interactive", "not-really", "help",
+ "output=", "output-format=", "output-directory=",
+ "entry=", "kernel=", "default_args=", "extra_args=",
+ "ramdisk=", "args=", "vm="])
+ except getopt.GetoptError:
+ usage()
+ sys.exit(1)
+
+ if len(args) < 1:
+ usage()
+ sys.exit(1)
+ file = args[0]
+
+ output = None
+ entry = None
+ interactive = True
+ not_really = False
+ output_format = "sxp"
+ output_directory = "/var/run/xend/boot"
+ default_args = ""
+ extra_args = ""
+
+ # what was passed in
+ incfg = { "kernel": None, "ramdisk": None, "args": "" }
+ # what grub or sniffing chose
+ chosencfg = { "kernel": None, "ramdisk": None, "args": None }
+ # what to boot
+ bootcfg = { "kernel": None, "ramdisk": None, "args": None }
+
+ for o, a in opts:
+ if o in ("-q", "--quiet"):
+ interactive = False
+ elif o in ("-i", "--interactive"):
+ interactive = True
+ elif o in ("-n", "--not-really"):
+ not_really = True
+ elif o in ("-h", "--help"):
+ usage()
+ sys.exit()
+ elif o in ("-n", "--not-really"):
+ not_really = True
+ elif o in ("--output",):
+ output = a
+ elif o in ("--kernel",):
+ incfg["kernel"] = a
+ elif o in ("--ramdisk",):
+ incfg["ramdisk"] = a
+ elif o in ("--args",):
+ incfg["args"] = a
+ elif o in ("--entry",):
+ entry = a
+ # specifying the entry to boot implies non-interactive
+ interactive = False
+ elif o in ("--output-format",):
+ if a not in ["sxp", "simple", "simple0"]:
+ print "unkonwn output format %s" % a
+ usage()
+ sys.exit(1)
+ output_format = a
+ elif o in ("--output-directory",):
+ output_directory = a
+ elif o in ("--default_args",):
+ default_args = a
+ elif o in ("--extra_args",):
+ extra_args = a
+
+ if output is None or output == "-":
+ fd = sys.stdout.fileno()
+ else:
+ fd = os.open(output, os.O_WRONLY)
+
+ # if boot filesystem is set then pass to fsimage.open
+ bootfsargs = '"%s"' % incfg["args"]
+ bootfsgroup = re.findall('zfs-bootfs=(.*?)[\s\,\"]', bootfsargs)
+ if bootfsgroup:
+ bootfsoptions = bootfsgroup[0]
+ else:
+ bootfsoptions = ""
+
+ # get list of offsets into file which start partitions
+ part_offs = get_partition_offsets(file)
+
+ fs = fsimage.open(file, part_offs[0], bootfsoptions)
+
+ # We always boot the "default" kernel if it exists, rather than
+ # parsing the grub menu
+ initrd_path = None
+ if fs.file_exists("/xenkernel"):
+ incfg["kernel"] = "/xenkernel"
+ incfg["args"] = default_args
+ if fs.file_exists("/xeninitrd"):
+ incfg["ramdisk"] = "/xeninitrd"
+ elif fs.file_exists("/boot/xenkernel"):
+ incfg["kernel"] = "/boot/xenkernel"
+ incfg["args"] = default_args
+ if fs.file_exists("/boot/xeninitrd"):
+ incfg["ramdisk"] = "/boot/xeninitrd"
+
+ for offset in part_offs:
+ try:
+ fs = fsimage.open(file, offset, bootfsoptions)
+
+ chosencfg = sniff_solaris(fs, incfg)
+
+ if not chosencfg["kernel"]:
+ chosencfg = sniff_netware(fs, incfg)
+
+ if not chosencfg["kernel"]:
+ chosencfg = run_grub(file, entry, fs, incfg["args"])
+
+ # Break as soon as we've found the kernel so that we continue
+ # to use this fsimage object
+ if chosencfg["kernel"]:
+ break
+ fs = None
+
+ except:
+ # IOErrors raised by fsimage.open
+ # RuntimeErrors raised by run_grub if no menu.lst present
+ fs = None
+ continue
+
+ # Did looping through partitions find us a kernel?
+ if not fs:
+ raise RuntimeError, "Unable to find partition containing kernel"
+
+ if not_really:
+ bootcfg["kernel"] = "<kernel:%s>" % chosencfg["kernel"]
+ try:
+ data = fs.open_file(chosencfg["kernel"]).read()
+ except:
+ raise RuntimeError, "The chosen kernel does not exist"
+ else:
+ # Append any extra arguments we were given
+ if extra_args:
+ if chosencfg["args"] == None:
+ chosencfg["args"] = extra_args
+ else:
+ chosencfg["args"] += " " + extra_args
+
+ if not_really:
+ bootcfg["kernel"] = "<kernel:%s>" % chosencfg["kernel"]
+ try:
+ data = fs.open_file(chosencfg["kernel"]).read()
+ except:
+ raise RuntimeError, "The chosen kernel does not exist"
+ else:
+ data = fs.open_file(chosencfg["kernel"]).read()
+ (tfd, bootcfg["kernel"]) = tempfile.mkstemp(prefix="boot_kernel.",
+ dir="/var/run/xend/boot")
+ os.write(tfd, data)
+ os.close(tfd)
+
+ if chosencfg["ramdisk"]:
+ if not_really:
+ bootcfg["ramdisk"] = "<ramdisk:%s>" % chosencfg["ramdisk"]
+ else:
+ data = fs.open_file(chosencfg["ramdisk"],).read()
+ (tfd, bootcfg["ramdisk"]) = tempfile.mkstemp(
+ prefix="boot_ramdisk.", dir="/var/run/xend/boot")
+ os.write(tfd, data)
+ os.close(tfd)
+ else:
+ initrd = None
+
+ args = None
+ if chosencfg["args"]:
+ zfsinfo = fsimage.getbootstring(fs)
+ if zfsinfo is not None:
+ e = re.compile("zfs-bootfs=[\w\-\.\:@/]+" )
+ (chosencfg["args"],count) = e.subn(zfsinfo, chosencfg["args"])
+ if count == 0:
+ chosencfg["args"] += " -B %s" % zfsinfo
+ args = chosencfg["args"]
+
+ if output_format == "sxp":
+ ostring = format_sxp(bootcfg["kernel"], bootcfg["ramdisk"], args)
+ elif output_format == "simple":
+ ostring = format_simple(bootcfg["kernel"], bootcfg["ramdisk"], args, "\n")
+ elif output_format == "simple0":
+ ostring = format_simple(bootcfg["kernel"], bootcfg["ramdisk"], args, "\0")
+
+ sys.stdout.flush()
+ os.write(fd, ostring)
+
--