ia64/xen-unstable

changeset 10991:2e8b1175fd37

[PYGRUB] Flesh out some of pygrub's functionality as was originally
intended. Changes include:
* Addition of basic command line mode much like grub's so that you can
boot things without having them specified in the config file
* Edit/append mode for modifying kernel command lines, etc
* Fix handling of case where the grub config didn't have a default
specified

Signed-off-by: Jeremy Katz <katzj@redhat.com>
author kaf24@firebug.cl.cam.ac.uk
date Tue Aug 08 09:30:11 2006 +0100 (2006-08-08)
parents d25efa45355b
children 5f59ac51a7ac
files tools/pygrub/src/GrubConf.py tools/pygrub/src/pygrub
line diff
     1.1 --- a/tools/pygrub/src/GrubConf.py	Tue Aug 08 09:25:46 2006 +0100
     1.2 +++ b/tools/pygrub/src/GrubConf.py	Tue Aug 08 09:30:11 2006 +0100
     1.3 @@ -1,7 +1,7 @@
     1.4  #
     1.5  # GrubConf.py - Simple grub.conf parsing
     1.6  #
     1.7 -# Copyright 2005 Red Hat, Inc.
     1.8 +# Copyright 2005-2006 Red Hat, Inc.
     1.9  # Jeremy Katz <katzj@redhat.com>
    1.10  #
    1.11  # This software may be freely redistributed under the terms of the GNU
    1.12 @@ -16,7 +16,6 @@ import os, sys
    1.13  import logging
    1.14  
    1.15  def grub_split(s, maxsplit = -1):
    1.16 -    """Split a grub option screen separated with either '=' or whitespace."""
    1.17      eq = s.find('=')
    1.18      if eq == -1:
    1.19          return s.split(None, maxsplit)
    1.20 @@ -32,6 +31,12 @@ def grub_split(s, maxsplit = -1):
    1.21      else:
    1.22          return s.split(None, maxsplit)
    1.23  
    1.24 +def grub_exact_split(s, num):
    1.25 +    ret = grub_split(s, num - 1)
    1.26 +    if len(ret) < num:
    1.27 +        return ret + [""] * (num - len(ret))
    1.28 +    return ret
    1.29 +
    1.30  def get_path(s):
    1.31      """Returns a tuple of (GrubDiskPart, path) corresponding to string."""
    1.32      if not s.startswith('('):
    1.33 @@ -75,26 +80,40 @@ class GrubDiskPart(object):
    1.34  
    1.35  class GrubImage(object):
    1.36      def __init__(self, lines):
    1.37 -        self._root = self._initrd = self._kernel = self._args = None
    1.38 -        for l in lines:
    1.39 -            (com, arg) = grub_split(l, 1)
    1.40 -
    1.41 -            if self.commands.has_key(com):
    1.42 -                if self.commands[com] is not None:
    1.43 -                    exec("%s = r\"%s\"" %(self.commands[com], arg.strip()))
    1.44 -                else:
    1.45 -                    logging.info("Ignored image directive %s" %(com,))
    1.46 -            else:
    1.47 -                logging.warning("Unknown image directive %s" %(com,))
    1.48 +        self.reset(lines)
    1.49  
    1.50      def __repr__(self):
    1.51          return ("title: %s\n" 
    1.52                  "  root: %s\n"
    1.53                  "  kernel: %s\n"
    1.54                  "  args: %s\n"
    1.55 -                "  initrd: %s" %(self.title, self.root, self.kernel,
    1.56 +                "  initrd: %s\n" %(self.title, self.root, self.kernel,
    1.57                                     self.args, self.initrd))
    1.58  
    1.59 +    def reset(self, lines):
    1.60 +        self._root = self._initrd = self._kernel = self._args = None
    1.61 +        self.title = ""
    1.62 +        self.lines = []
    1.63 +        map(self.set_from_line, lines)
    1.64 +
    1.65 +    def set_from_line(self, line, replace = None):
    1.66 +        (com, arg) = grub_exact_split(line, 2)
    1.67 +
    1.68 +        if self.commands.has_key(com):
    1.69 +            if self.commands[com] is not None:
    1.70 +                exec("%s = r\"%s\"" %(self.commands[com], arg.strip()))
    1.71 +            else:
    1.72 +                logging.info("Ignored image directive %s" %(com,))
    1.73 +        else:
    1.74 +            logging.warning("Unknown image directive %s" %(com,))
    1.75 +
    1.76 +        # now put the line in the list of lines
    1.77 +        if replace is None:
    1.78 +            self.lines.append(line)
    1.79 +        else:
    1.80 +            self.lines.pop(replace)
    1.81 +            self.lines.insert(replace, line)
    1.82 +
    1.83      def set_root(self, val):
    1.84          self._root = GrubDiskPart(val)
    1.85      def get_root(self):
    1.86 @@ -137,6 +156,7 @@ class GrubConfigFile(object):
    1.87          self.filename = fn
    1.88          self.images = []
    1.89          self.timeout = -1
    1.90 +        self._default = 0
    1.91  
    1.92          if fn is not None:
    1.93              self.parse()
    1.94 @@ -164,7 +184,7 @@ class GrubConfigFile(object):
    1.95              # new image
    1.96              if l.startswith("title"):
    1.97                  if len(img) > 0:
    1.98 -                    self.images.append(GrubImage(img))
    1.99 +                    self.add_image(GrubImage(img))
   1.100                  img = [l]
   1.101                  continue
   1.102                  
   1.103 @@ -172,12 +192,7 @@ class GrubConfigFile(object):
   1.104                  img.append(l)
   1.105                  continue
   1.106  
   1.107 -            try:
   1.108 -                (com, arg) = grub_split(l, 1)
   1.109 -            except ValueError:
   1.110 -                com = l
   1.111 -                arg = ""
   1.112 -
   1.113 +            (com, arg) = grub_exact_split(l, 2)
   1.114              if self.commands.has_key(com):
   1.115                  if self.commands[com] is not None:
   1.116                      exec("%s = r\"%s\"" %(self.commands[com], arg.strip()))
   1.117 @@ -187,7 +202,20 @@ class GrubConfigFile(object):
   1.118                  logging.warning("Unknown directive %s" %(com,))
   1.119                  
   1.120          if len(img) > 0:
   1.121 -            self.images.append(GrubImage(img))
   1.122 +            self.add_image(GrubImage(img))
   1.123 +
   1.124 +    def set(self, line):
   1.125 +        (com, arg) = grub_exact_split(line, 2)
   1.126 +        if self.commands.has_key(com):
   1.127 +            if self.commands[com] is not None:
   1.128 +                exec("%s = r\"%s\"" %(self.commands[com], arg.strip()))
   1.129 +            else:
   1.130 +                logging.info("Ignored directive %s" %(com,))
   1.131 +        else:
   1.132 +            logging.warning("Unknown directive %s" %(com,))
   1.133 +
   1.134 +    def add_image(self, image):
   1.135 +        self.images.append(image)
   1.136  
   1.137      def _get_default(self):
   1.138          return self._default
     2.1 --- a/tools/pygrub/src/pygrub	Tue Aug 08 09:25:46 2006 +0100
     2.2 +++ b/tools/pygrub/src/pygrub	Tue Aug 08 09:30:11 2006 +0100
     2.3 @@ -2,7 +2,7 @@
     2.4  #
     2.5  # pygrub - simple python-based bootloader for Xen
     2.6  #
     2.7 -# Copyright 2005 Red Hat, Inc.
     2.8 +# Copyright 2005-2006 Red Hat, Inc.
     2.9  # Jeremy Katz <katzj@redhat.com>
    2.10  #
    2.11  # This software may be freely redistributed under the terms of the GNU
    2.12 @@ -14,9 +14,10 @@
    2.13  #
    2.14  
    2.15  import os, sys, string, struct, tempfile
    2.16 +import copy
    2.17  import logging
    2.18  
    2.19 -import curses, _curses, curses.wrapper
    2.20 +import curses, _curses, curses.wrapper, curses.textpad, curses.ascii
    2.21  import getopt
    2.22  
    2.23  sys.path = [ '/usr/lib/python' ] + sys.path
    2.24 @@ -24,122 +25,387 @@ sys.path = [ '/usr/lib/python' ] + sys.p
    2.25  import grub.GrubConf
    2.26  import grub.fsys
    2.27  
    2.28 -PYGRUB_VER = 0.3
    2.29 -
    2.30 -
    2.31 -def draw_window():
    2.32 -    stdscr = curses.initscr()
    2.33 -    if hasattr(curses, 'use_default_colors'):
    2.34 -        curses.use_default_colors()
    2.35 -    try:
    2.36 -        curses.curs_set(0)
    2.37 -    except _curses.error:
    2.38 -        pass
    2.39 -
    2.40 -    stdscr.addstr(1, 4, "pyGRUB  version %s" %(PYGRUB_VER,))
    2.41 -
    2.42 -    win = curses.newwin(10, 74, 2, 1)
    2.43 -    win.box()
    2.44 -    win.refresh()
    2.45 -
    2.46 -    stdscr.addstr(12, 5, "Use the U and D keys to select which entry is highlighted.")
    2.47 -    stdscr.addstr(13, 5, "Press enter to boot the selected OS. 'e' to edit the")
    2.48 -    stdscr.addstr(14, 5, "commands before booting, 'a' to modify the kernel arguments ")
    2.49 -    stdscr.addstr(15, 5, "before booting, or 'c' for a command line.")
    2.50 -    stdscr.addch(12, 13, curses.ACS_UARROW)
    2.51 -    stdscr.addch(12, 19, curses.ACS_DARROW)
    2.52 -    (y, x) = stdscr.getmaxyx()
    2.53 -    stdscr.move(y - 1, x - 1)
    2.54 -
    2.55 -    stdscr.refresh()
    2.56 -    return (stdscr, win)
    2.57 -
    2.58 -def fill_entries(win, cfg, selected):
    2.59 -    y = 0
    2.60 -
    2.61 -    for i in cfg.images:
    2.62 -        if (0, y) > win.getmaxyx():
    2.63 -            break
    2.64 -        if y == selected:
    2.65 -            attr = curses.A_REVERSE
    2.66 -        else:
    2.67 -            attr = 0
    2.68 -        win.addstr(y + 1, 2, i.title.ljust(70), attr)
    2.69 -        y += 1
    2.70 -    win.refresh()
    2.71 -
    2.72 -def select(win, line):
    2.73 -    win.attron(curses.A_REVERSE)
    2.74 -    win.redrawln(line + 1, 1)
    2.75 -    win.refresh()
    2.76 +PYGRUB_VER = 0.4
    2.77  
    2.78  def is_disk_image(file):
    2.79      fd = os.open(file, os.O_RDONLY)
    2.80      buf = os.read(fd, 512)
    2.81      os.close(fd)
    2.82  
    2.83 -    if len(buf) >= 512 and struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,):
    2.84 +    if len(buf) >= 512 and \
    2.85 +           struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,):
    2.86          return True
    2.87      return False
    2.88  
    2.89  SECTOR_SIZE=512
    2.90  def get_active_offset(file):
    2.91 -    """Find the offset for the start of the first active partition in the
    2.92 -    disk image file."""
    2.93 +    """Find the offset for the start of the first active partition "
    2.94 +    "in the disk image file."""
    2.95 +
    2.96      fd = os.open(file, os.O_RDONLY)
    2.97      buf = os.read(fd, 512)
    2.98      for poff in (446, 462, 478, 494): # partition offsets
    2.99          # active partition has 0x80 as the first byte
   2.100          if struct.unpack("<c", buf[poff:poff+1]) == ('\x80',):
   2.101 -            return struct.unpack("<L", buf[poff+8:poff+12])[0] * SECTOR_SIZE
   2.102 -    return -1
   2.103 -
   2.104 -def get_config(fn, isconfig = False):
   2.105 -    if not os.access(fn, os.R_OK):
   2.106 -        raise RuntimeError, "Unable to access %s" %(fn,)
   2.107 -
   2.108 -    cf = grub.GrubConf.GrubConfigFile()
   2.109 +            return struct.unpack("<L",
   2.110 +                                 buf[poff+8:poff+12])[0] * SECTOR_SIZE
   2.111  
   2.112 -    if isconfig:
   2.113 -        # set the config file and parse it
   2.114 -        cf.filename = fn
   2.115 -        cf.parse()
   2.116 -        return cf
   2.117 +    # if there's not a partition marked as active, fall back to
   2.118 +    # the first partition
   2.119 +    P1 = 446
   2.120 +    return struct.unpack("<L", buf[P1+8:P1+12])[0] * SECTOR_SIZE
   2.121  
   2.122 -    offset = 0
   2.123 -    if is_disk_image(fn):
   2.124 -        offset = get_active_offset(fn)
   2.125 -        if offset == -1:
   2.126 -            raise RuntimeError, "Unable to find active partition on disk"
   2.127 +class GrubLineEditor(curses.textpad.Textbox):
   2.128 +    def __init__(self, screen, startx, starty, line = ""):
   2.129 +        screen.addstr(startx, starty, "> ")
   2.130 +        screen.refresh()
   2.131 +        win = curses.newwin(1, 74, startx, starty + 2)
   2.132 +        curses.textpad.Textbox.__init__(self, win)
   2.133 +        
   2.134 +        self.line = list(line)
   2.135 +        self.pos = len(line)
   2.136 +        self.cancelled = False
   2.137 +        self.show_text()
   2.138  
   2.139 -    # open the image and read the grub config
   2.140 -    fs = None
   2.141 -    for fstype in grub.fsys.fstypes.values():
   2.142 -        if fstype.sniff_magic(fn, offset):
   2.143 -            fs = fstype.open_fs(fn, offset)
   2.144 +    def show_text(self):
   2.145 +        """Show the text.  One of our advantages over standard textboxes
   2.146 +        is that we can handle lines longer than the window."""
   2.147 +
   2.148 +        self.win.clear()
   2.149 +        if self.pos > 70:
   2.150 +            if self.pos > 130:
   2.151 +                off = 120
   2.152 +            else:
   2.153 +                off = 55
   2.154 +            l = [ "<" ] + self.line[off:]
   2.155 +            p = self.pos - off
   2.156 +        else:
   2.157 +            l = self.line[:70]
   2.158 +            p = self.pos
   2.159 +        self.win.addstr(0, 0, string.join(l, ("")))
   2.160 +        if self.pos > 70:
   2.161 +            self.win.addch(0, 0, curses.ACS_LARROW)
   2.162 +
   2.163 +        self.win.move(0, p)
   2.164 +
   2.165 +    def do_command(self, ch):
   2.166 +        # we handle escape as well as moving the line around, so have
   2.167 +        # to override some of the default handling
   2.168 +
   2.169 +        self.lastcmd = ch
   2.170 +        if ch == 27: # esc
   2.171 +            self.cancelled = True
   2.172 +            return 0
   2.173 +        elif curses.ascii.isprint(ch):
   2.174 +            self.line.insert(self.pos, chr(ch))
   2.175 +            self.pos += 1
   2.176 +        elif ch == curses.ascii.SOH:  # ^a
   2.177 +            self.pos = 0
   2.178 +        elif ch in (curses.ascii.STX,curses.KEY_LEFT):
   2.179 +            self.pos -= 1
   2.180 +        elif ch in (curses.ascii.BS,curses.KEY_BACKSPACE):
   2.181 +            if self.pos > 0:
   2.182 +                self.pos -= 1
   2.183 +                self.line.pop(self.pos)
   2.184 +        elif ch == curses.ascii.EOT:                           # ^d
   2.185 +            self.line.pop(self.pos)
   2.186 +        elif ch == curses.ascii.ENQ:                           # ^e
   2.187 +            self.pos = len(self.line)
   2.188 +        elif ch in (curses.ascii.ACK, curses.KEY_RIGHT):
   2.189 +            self.pos +=1
   2.190 +        elif ch == curses.ascii.VT:                            # ^k
   2.191 +            self.line = self.line[:self.pos]
   2.192 +        else:
   2.193 +            return curses.textpad.Textbox.do_command(self, ch)
   2.194 +        self.show_text()
   2.195 +        return 1
   2.196 +
   2.197 +    def edit(self):
   2.198 +        r = curses.textpad.Textbox.edit(self)
   2.199 +        if self.cancelled:
   2.200 +            return None
   2.201 +        return string.join(self.line, "")
   2.202 +        
   2.203 +
   2.204 +class Grub:
   2.205 +    def __init__(self, file, isconfig = False):
   2.206 +        self.screen = None
   2.207 +        self.entry_win = None
   2.208 +        self.text_win = None
   2.209 +        if file:
   2.210 +            self.read_config(file, isconfig)
   2.211 +
   2.212 +    def draw_main_windows(self):
   2.213 +        if self.screen is None: #only init stuff once
   2.214 +            self.screen = curses.initscr()
   2.215 +            self.screen.timeout(1000)
   2.216 +            if hasattr(curses, 'use_default_colors'):
   2.217 +                curses.use_default_colors()
   2.218 +            try:
   2.219 +                curses.curs_set(0)
   2.220 +            except _curses.error:
   2.221 +                pass
   2.222 +            self.entry_win = curses.newwin(10, 74, 2, 1)
   2.223 +            self.text_win = curses.newwin(10, 70, 12, 5)
   2.224 +            
   2.225 +        self.screen.clear()
   2.226 +        self.screen.refresh()
   2.227 +
   2.228 +        # create basic grub screen with a box of entries and a textbox
   2.229 +        self.screen.addstr(1, 4, "pyGRUB  version %s" %(PYGRUB_VER,))
   2.230 +        self.entry_win.box()
   2.231 +        self.screen.refresh()
   2.232 +
   2.233 +    def fill_entry_list(self):
   2.234 +        self.entry_win.clear()
   2.235 +        self.entry_win.box()
   2.236 +        for y in range(0, len(self.cf.images)):
   2.237 +            i = self.cf.images[y]
   2.238 +            if (0, y) > self.entry_win.getmaxyx():
   2.239 +                break
   2.240 +            if y == self.selected_image:
   2.241 +                attr = curses.A_REVERSE
   2.242 +            else:
   2.243 +                attr = 0
   2.244 +            self.entry_win.addstr(y + 1, 2, i.title.ljust(70), attr)
   2.245 +        self.entry_win.refresh()
   2.246 +
   2.247 +    def edit_entry(self, origimg):
   2.248 +        def draw():
   2.249 +            self.draw_main_windows()
   2.250 +
   2.251 +            self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
   2.252 +            self.text_win.addstr(1, 0, "Press 'b' to boot, 'e' to edit the selected command in the")
   2.253 +            self.text_win.addstr(2, 0, "boot sequence, 'c' for a command-line, 'o' to open a new line")
   2.254 +            self.text_win.addstr(3, 0, "after ('O' for before) the selected line, 'd' to remove the")
   2.255 +            self.text_win.addstr(4, 0, "selected line, or escape to go back to the main menu.")
   2.256 +            self.text_win.addch(0, 8, curses.ACS_UARROW)
   2.257 +            self.text_win.addch(0, 14, curses.ACS_DARROW)
   2.258 +            (y, x) = self.text_win.getmaxyx()
   2.259 +            self.text_win.move(y - 1, x - 1)
   2.260 +            self.text_win.refresh()
   2.261 +
   2.262 +        curline = 1
   2.263 +        img = copy.deepcopy(origimg)
   2.264 +        while 1:
   2.265 +            draw()
   2.266 +            self.entry_win.clear()
   2.267 +            self.entry_win.box()
   2.268 +            for idx in range(1, len(img.lines)):
   2.269 +                # current line should be highlighted
   2.270 +                attr = 0
   2.271 +                if idx == curline:
   2.272 +                    attr = curses.A_REVERSE
   2.273 +
   2.274 +                # trim the line
   2.275 +                l = img.lines[idx].ljust(70)
   2.276 +                if len(l) > 70:
   2.277 +                    l = l[:69] + ">"
   2.278 +                    
   2.279 +                self.entry_win.addstr(idx, 2, l, attr)
   2.280 +            self.entry_win.refresh()
   2.281 +
   2.282 +            c = self.screen.getch()
   2.283 +            if c in (ord('q'), 27): # 27 == esc
   2.284 +                break
   2.285 +            elif c == curses.KEY_UP:
   2.286 +                curline -= 1
   2.287 +            elif c == curses.KEY_DOWN:
   2.288 +                curline += 1
   2.289 +            elif c == ord('b'):
   2.290 +                self.isdone = True
   2.291 +                break
   2.292 +            elif c == ord('e'):
   2.293 +                l = self.edit_line(img.lines[curline])
   2.294 +                if l is not None:
   2.295 +                    img.set_from_line(l, replace = curline)
   2.296 +            elif c == ord('d'):
   2.297 +                img.lines.pop(curline)
   2.298 +            elif c == ord('o'):
   2.299 +                img.lines.insert(curline+1, "")
   2.300 +                curline += 1
   2.301 +            elif c == ord('O'):
   2.302 +                img.lines.insert(curline, "")
   2.303 +            elif c == ord('c'):
   2.304 +                self.command_line_mode()
   2.305 +                if self.isdone:
   2.306 +                    return
   2.307 +                
   2.308 +            # bound at the top and bottom
   2.309 +            if curline < 1:
   2.310 +                curline = 1
   2.311 +            elif curline >= len(img.lines):
   2.312 +                curline = len(img.lines) - 1
   2.313 +
   2.314 +        if self.isdone:
   2.315 +            origimg.reset(img.lines)
   2.316 +
   2.317 +    def edit_line(self, line):
   2.318 +        self.screen.clear()
   2.319 +        self.screen.addstr(1, 2, "[ Minimal BASH-like line editing is supported.  ")
   2.320 +        self.screen.addstr(2, 2, "  ESC at any time cancels.  ENTER at any time accepts your changes. ]")
   2.321 +        self.screen.refresh()
   2.322 +
   2.323 +        t = GrubLineEditor(self.screen, 5, 2, line)
   2.324 +        ret = t.edit()
   2.325 +        if ret:
   2.326 +            return ret
   2.327 +        return None
   2.328 +
   2.329 +    def command_line_mode(self):
   2.330 +        self.screen.clear()
   2.331 +        self.screen.addstr(1, 2, "[ Minimal BASH-like line editing is supported.  ESC at any time ")
   2.332 +        self.screen.addstr(2, 2, "  exits.  Typing 'boot' will boot with your entered commands. ] ")
   2.333 +        self.screen.refresh()
   2.334 +
   2.335 +        y = 5
   2.336 +        lines = []
   2.337 +        while 1:
   2.338 +            t = GrubLineEditor(self.screen, y, 2)
   2.339 +            ret = t.edit()
   2.340 +            if ret:
   2.341 +                if ret in ("quit", "return"):
   2.342 +                    break
   2.343 +                elif ret != "boot":
   2.344 +                    y += 1
   2.345 +                    lines.append(ret)
   2.346 +                    continue
   2.347 +
   2.348 +                # if we got boot, then we want to boot the entered image 
   2.349 +                img = grub.GrubConf.GrubImage(lines)
   2.350 +                self.cf.add_image(img)
   2.351 +                self.selected_image = len(self.cf.images) - 1
   2.352 +                self.isdone = True
   2.353 +                break
   2.354 +
   2.355 +            # else, we cancelled and should just go back
   2.356              break
   2.357  
   2.358 -    if fs is not None:
   2.359 -        grubfile = None
   2.360 -        for f in ("/boot/grub/menu.lst", "/boot/grub/grub.conf",
   2.361 -                  "/grub/menu.lst", "/grub/grub.conf"):
   2.362 -            if fs.file_exist(f):
   2.363 -                grubfile = f
   2.364 +    def read_config(self, fn, isConfig = False):
   2.365 +        """Read the given file to parse the config.  If isconfig, then
   2.366 +        we're being given a raw config file rather than a disk image."""
   2.367 +        
   2.368 +        if not os.access(fn, os.R_OK):
   2.369 +            raise RuntimeError, "Unable to access %s" %(fn,)
   2.370 +
   2.371 +        self.cf = grub.GrubConf.GrubConfigFile()
   2.372 +
   2.373 +        if isConfig:
   2.374 +            # set the config file and parse it
   2.375 +            self.cf.filename = fn
   2.376 +            self.cf.parse()
   2.377 +            return
   2.378 +
   2.379 +        offset = 0
   2.380 +        if is_disk_image(fn):
   2.381 +            offset = get_active_offset(fn)
   2.382 +            if offset == -1:
   2.383 +                raise RuntimeError, "Unable to find active partition on disk"
   2.384 +
   2.385 +        # open the image and read the grub config
   2.386 +        fs = None
   2.387 +        for fstype in grub.fsys.fstypes.values():
   2.388 +            if fstype.sniff_magic(fn, offset):
   2.389 +                fs = fstype.open_fs(fn, offset)
   2.390                  break
   2.391 -        if grubfile is None:
   2.392 -            raise RuntimeError, "we couldn't find /boot/grub{menu.lst,grub.conf} " + \
   2.393 -                                "in the image provided. halt!"
   2.394 -        f = fs.open_file(grubfile)
   2.395 -        buf = f.read()
   2.396 -        f.close()
   2.397 -        fs.close()
   2.398 -        # then parse the grub config
   2.399 -        cf.parse(buf)
   2.400 -    else:
   2.401 -        raise RuntimeError, "Unable to read filesystem" 
   2.402 -    
   2.403 -    return cf
   2.404  
   2.405 +        if fs is not None:
   2.406 +            grubfile = None
   2.407 +            for f in ("/boot/grub/menu.lst", "/boot/grub/grub.conf",
   2.408 +                      "/grub/menu.lst", "/grub/grub.conf"):
   2.409 +                if fs.file_exist(f):
   2.410 +                    grubfile = f
   2.411 +                    break
   2.412 +            if grubfile is None:
   2.413 +                raise RuntimeError, "we couldn't find grub config file in the image provided."
   2.414 +            f = fs.open_file(grubfile)
   2.415 +            buf = f.read()
   2.416 +            f.close()
   2.417 +            fs.close()
   2.418 +            # then parse the grub config
   2.419 +            self.cf.parse(buf)
   2.420 +        else:
   2.421 +            raise RuntimeError, "Unable to read filesystem" 
   2.422 +
   2.423 +    def run(self):
   2.424 +        timeout = int(self.cf.timeout)
   2.425 +
   2.426 +        self.selected_image = self.cf.default
   2.427 +        self.isdone = False
   2.428 +        while not self.isdone:
   2.429 +            self.run_main(timeout)
   2.430 +            timeout = -1
   2.431 +            
   2.432 +        return self.selected_image
   2.433 +
   2.434 +    def run_main(self, timeout = -1):
   2.435 +        def draw():
   2.436 +            # set up the screen
   2.437 +            self.draw_main_windows()
   2.438 +            self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
   2.439 +            self.text_win.addstr(1, 0, "Press enter to boot the selected OS. 'e' to edit the")
   2.440 +            self.text_win.addstr(2, 0, "commands before booting, 'a' to modify the kernel arguments ")
   2.441 +            self.text_win.addstr(3, 0, "before booting, or 'c' for a command line.")
   2.442 +            self.text_win.addch(0, 8, curses.ACS_UARROW)
   2.443 +            self.text_win.addch(0, 14, curses.ACS_DARROW)
   2.444 +            (y, x) = self.text_win.getmaxyx()
   2.445 +            self.text_win.move(y - 1, x - 1)
   2.446 +            self.text_win.refresh()
   2.447 +
   2.448 +        # now loop until we hit the timeout or get a go from the user
   2.449 +        mytime = 0
   2.450 +        while (timeout == -1 or mytime < int(timeout)):
   2.451 +            draw()
   2.452 +            if timeout != -1 and mytime != -1: 
   2.453 +                self.screen.addstr(20, 5, "Will boot selected entry in %2d seconds"
   2.454 +                                   %(int(timeout) - mytime))
   2.455 +            else:
   2.456 +                self.screen.addstr(20, 5, " " * 80)
   2.457 +
   2.458 +            self.fill_entry_list()
   2.459 +            c = self.screen.getch()
   2.460 +            if mytime != -1:
   2.461 +                mytime += 1
   2.462 +
   2.463 +            # handle keypresses
   2.464 +            if c == ord('c'):
   2.465 +                self.command_line_mode()
   2.466 +                break
   2.467 +            elif c == ord('a'):
   2.468 +                # find the kernel line, edit it and then boot
   2.469 +                img = self.cf.images[self.selected_image]
   2.470 +                for line in img.lines:
   2.471 +                    if line.startswith("kernel"):
   2.472 +                        l = self.edit_line(line)
   2.473 +                        if l is not None:
   2.474 +                            img.set_from_line(l, replace = True)
   2.475 +                            self.isdone = True
   2.476 +                            break
   2.477 +                break
   2.478 +            elif c == ord('e'):
   2.479 +                img = self.cf.images[self.selected_image]
   2.480 +                self.edit_entry(img)
   2.481 +                break
   2.482 +            elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
   2.483 +                self.isdone = True
   2.484 +                break
   2.485 +            elif c == curses.KEY_UP:
   2.486 +                mytime = -1
   2.487 +                self.selected_image -= 1
   2.488 +            elif c == curses.KEY_DOWN:
   2.489 +                mytime = -1
   2.490 +                self.selected_image += 1
   2.491 +#            elif c in (ord('q'), 27): # 27 == esc
   2.492 +#                self.selected_image = -1
   2.493 +#                self.isdone = True
   2.494 +#                break
   2.495 +
   2.496 +            # bound at the top and bottom
   2.497 +            if self.selected_image < 0:
   2.498 +                self.selected_image = 0
   2.499 +            elif self.selected_image >= len(self.cf.images):
   2.500 +                self.selected_image = len(self.cf.images) - 1
   2.501 +        
   2.502  def get_entry_idx(cf, entry):
   2.503      # first, see if the given entry is numeric
   2.504      try:
   2.505 @@ -155,63 +421,12 @@ def get_entry_idx(cf, entry):
   2.506  
   2.507      return None
   2.508  
   2.509 -def main(cf = None):
   2.510 -    mytime = 0
   2.511 -    timeout = int(cf.timeout)
   2.512 -
   2.513 -    (stdscr, win) = draw_window()
   2.514 -    stdscr.timeout(1000)
   2.515 -    selected = cf.default
   2.516 -    
   2.517 -    while (timeout == -1 or mytime < int(timeout)):
   2.518 -        if timeout != -1 and mytime != -1: 
   2.519 -            stdscr.addstr(20, 5, "Will boot selected entry in %2d seconds"
   2.520 -                          %(int(timeout) - mytime))
   2.521 -        else:
   2.522 -            stdscr.addstr(20, 5, " " * 80)
   2.523 -            
   2.524 -        fill_entries(win, cf, selected)
   2.525 -        c = stdscr.getch()
   2.526 -        if mytime != -1:
   2.527 -            mytime += 1
   2.528 -#        if c == ord('q'):
   2.529 -#            selected = -1
   2.530 -#            break
   2.531 -        if c == ord('c'):
   2.532 -            # FIXME: needs to go to command line mode
   2.533 -            continue
   2.534 -        elif c == ord('a'):
   2.535 -            # FIXME: needs to go to append mode
   2.536 -            continue
   2.537 -        elif c == ord('e'):
   2.538 -            # FIXME: needs to go to edit mode
   2.539 -            continue
   2.540 -        elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
   2.541 -            break
   2.542 -        elif c == curses.KEY_UP:
   2.543 -            mytime = -1
   2.544 -            selected -= 1
   2.545 -        elif c == curses.KEY_DOWN:
   2.546 -            mytime = -1
   2.547 -            selected += 1
   2.548 -        else:
   2.549 -            pass
   2.550 -
   2.551 -        # bound at the top and bottom
   2.552 -        if selected < 0:
   2.553 -            selected = 0
   2.554 -        elif selected >= len(cf.images):
   2.555 -            selected = len(cf.images) - 1
   2.556 -
   2.557 -    if selected >= 0:
   2.558 -        return selected
   2.559 -
   2.560  if __name__ == "__main__":
   2.561      sel = None
   2.562      
   2.563      def run_main(scr, *args):
   2.564          global sel
   2.565 -        sel = main(cf)
   2.566 +        sel = g.run()
   2.567  
   2.568      def usage():
   2.569          print >> sys.stderr, "Usage: %s [-q|--quiet] [--output=] [--entry=] <image>" %(sys.argv[0],)
   2.570 @@ -253,24 +468,32 @@ if __name__ == "__main__":
   2.571      else:
   2.572          fd = os.open(output, os.O_WRONLY)
   2.573  
   2.574 -    cf = get_config(file, isconfig)
   2.575 +    g = Grub(file, isconfig)
   2.576      if interactive:
   2.577          curses.wrapper(run_main)
   2.578      else:
   2.579 -        sel = cf.default
   2.580 +        sel = g.cf.default
   2.581  
   2.582      # set the entry to boot as requested
   2.583      if entry is not None:
   2.584 -        idx = get_entry_idx(cf, entry)
   2.585 -        if idx is not None and idx > 0 and idx < len(cf.images):
   2.586 +        idx = get_entry_idx(g.cf, entry)
   2.587 +        if idx is not None and idx > 0 and idx < len(g.cf.images):
   2.588              sel = idx
   2.589  
   2.590 -    img = cf.images[sel]
   2.591 +    if sel == -1:
   2.592 +        print "No kernel image selected!"
   2.593 +        sys.exit(1)
   2.594 +
   2.595 +    img = g.cf.images[sel]
   2.596      print "Going to boot %s" %(img.title)
   2.597      print "  kernel: %s" %(img.kernel[1],)
   2.598      if img.initrd:
   2.599          print "  initrd: %s" %(img.initrd[1],)
   2.600  
   2.601 +    if isconfig:
   2.602 +        print "  args: %s" %(img.args,)
   2.603 +        sys.exit(0)
   2.604 +        
   2.605      offset = 0
   2.606      if is_disk_image(file):
   2.607          offset = get_active_offset(file)
   2.608 @@ -288,14 +511,14 @@ if __name__ == "__main__":
   2.609          raise RuntimeError, "Unable to open filesystem"
   2.610  
   2.611      kernel = fs.open_file(img.kernel[1],).read()
   2.612 -    (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.")
   2.613 +    (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.", dir="/var/lib/xen")
   2.614      os.write(tfd, kernel)
   2.615      os.close(tfd)
   2.616      sxp = "linux (kernel %s)" %(fn,)
   2.617  
   2.618      if img.initrd:
   2.619          initrd = fs.open_file(img.initrd[1],).read()
   2.620 -        (tfd, fn) = tempfile.mkstemp(prefix="initrd.")
   2.621 +        (tfd, fn) = tempfile.mkstemp(prefix="initrd.", dir="/var/lib/xen")
   2.622          os.write(tfd, initrd)
   2.623          os.close(tfd)
   2.624          sxp += "(ramdisk %s)" %(fn,)