direct-io.hg

changeset 10963: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 +            return struct.unpack("<L",
   2.104 +                                 buf[poff+8:poff+12])[0] * SECTOR_SIZE
   2.105 +
   2.106 +    # if there's not a partition marked as active, fall back to
   2.107 +    # the first partition
   2.108 +    P1 = 446
   2.109 +    return struct.unpack("<L", buf[P1+8:P1+12])[0] * SECTOR_SIZE
   2.110 +
   2.111 +class GrubLineEditor(curses.textpad.Textbox):
   2.112 +    def __init__(self, screen, startx, starty, line = ""):
   2.113 +        screen.addstr(startx, starty, "> ")
   2.114 +        screen.refresh()
   2.115 +        win = curses.newwin(1, 74, startx, starty + 2)
   2.116 +        curses.textpad.Textbox.__init__(self, win)
   2.117 +        
   2.118 +        self.line = list(line)
   2.119 +        self.pos = len(line)
   2.120 +        self.cancelled = False
   2.121 +        self.show_text()
   2.122 +
   2.123 +    def show_text(self):
   2.124 +        """Show the text.  One of our advantages over standard textboxes
   2.125 +        is that we can handle lines longer than the window."""
   2.126 +
   2.127 +        self.win.clear()
   2.128 +        if self.pos > 70:
   2.129 +            if self.pos > 130:
   2.130 +                off = 120
   2.131 +            else:
   2.132 +                off = 55
   2.133 +            l = [ "<" ] + self.line[off:]
   2.134 +            p = self.pos - off
   2.135 +        else:
   2.136 +            l = self.line[:70]
   2.137 +            p = self.pos
   2.138 +        self.win.addstr(0, 0, string.join(l, ("")))
   2.139 +        if self.pos > 70:
   2.140 +            self.win.addch(0, 0, curses.ACS_LARROW)
   2.141 +
   2.142 +        self.win.move(0, p)
   2.143 +
   2.144 +    def do_command(self, ch):
   2.145 +        # we handle escape as well as moving the line around, so have
   2.146 +        # to override some of the default handling
   2.147  
   2.148 -def get_config(fn, isconfig = False):
   2.149 -    if not os.access(fn, os.R_OK):
   2.150 -        raise RuntimeError, "Unable to access %s" %(fn,)
   2.151 +        self.lastcmd = ch
   2.152 +        if ch == 27: # esc
   2.153 +            self.cancelled = True
   2.154 +            return 0
   2.155 +        elif curses.ascii.isprint(ch):
   2.156 +            self.line.insert(self.pos, chr(ch))
   2.157 +            self.pos += 1
   2.158 +        elif ch == curses.ascii.SOH:  # ^a
   2.159 +            self.pos = 0
   2.160 +        elif ch in (curses.ascii.STX,curses.KEY_LEFT):
   2.161 +            self.pos -= 1
   2.162 +        elif ch in (curses.ascii.BS,curses.KEY_BACKSPACE):
   2.163 +            if self.pos > 0:
   2.164 +                self.pos -= 1
   2.165 +                self.line.pop(self.pos)
   2.166 +        elif ch == curses.ascii.EOT:                           # ^d
   2.167 +            self.line.pop(self.pos)
   2.168 +        elif ch == curses.ascii.ENQ:                           # ^e
   2.169 +            self.pos = len(self.line)
   2.170 +        elif ch in (curses.ascii.ACK, curses.KEY_RIGHT):
   2.171 +            self.pos +=1
   2.172 +        elif ch == curses.ascii.VT:                            # ^k
   2.173 +            self.line = self.line[:self.pos]
   2.174 +        else:
   2.175 +            return curses.textpad.Textbox.do_command(self, ch)
   2.176 +        self.show_text()
   2.177 +        return 1
   2.178  
   2.179 -    cf = grub.GrubConf.GrubConfigFile()
   2.180 +    def edit(self):
   2.181 +        r = curses.textpad.Textbox.edit(self)
   2.182 +        if self.cancelled:
   2.183 +            return None
   2.184 +        return string.join(self.line, "")
   2.185 +        
   2.186 +
   2.187 +class Grub:
   2.188 +    def __init__(self, file, isconfig = False):
   2.189 +        self.screen = None
   2.190 +        self.entry_win = None
   2.191 +        self.text_win = None
   2.192 +        if file:
   2.193 +            self.read_config(file, isconfig)
   2.194 +
   2.195 +    def draw_main_windows(self):
   2.196 +        if self.screen is None: #only init stuff once
   2.197 +            self.screen = curses.initscr()
   2.198 +            self.screen.timeout(1000)
   2.199 +            if hasattr(curses, 'use_default_colors'):
   2.200 +                curses.use_default_colors()
   2.201 +            try:
   2.202 +                curses.curs_set(0)
   2.203 +            except _curses.error:
   2.204 +                pass
   2.205 +            self.entry_win = curses.newwin(10, 74, 2, 1)
   2.206 +            self.text_win = curses.newwin(10, 70, 12, 5)
   2.207 +            
   2.208 +        self.screen.clear()
   2.209 +        self.screen.refresh()
   2.210 +
   2.211 +        # create basic grub screen with a box of entries and a textbox
   2.212 +        self.screen.addstr(1, 4, "pyGRUB  version %s" %(PYGRUB_VER,))
   2.213 +        self.entry_win.box()
   2.214 +        self.screen.refresh()
   2.215  
   2.216 -    if isconfig:
   2.217 -        # set the config file and parse it
   2.218 -        cf.filename = fn
   2.219 -        cf.parse()
   2.220 -        return cf
   2.221 +    def fill_entry_list(self):
   2.222 +        self.entry_win.clear()
   2.223 +        self.entry_win.box()
   2.224 +        for y in range(0, len(self.cf.images)):
   2.225 +            i = self.cf.images[y]
   2.226 +            if (0, y) > self.entry_win.getmaxyx():
   2.227 +                break
   2.228 +            if y == self.selected_image:
   2.229 +                attr = curses.A_REVERSE
   2.230 +            else:
   2.231 +                attr = 0
   2.232 +            self.entry_win.addstr(y + 1, 2, i.title.ljust(70), attr)
   2.233 +        self.entry_win.refresh()
   2.234 +
   2.235 +    def edit_entry(self, origimg):
   2.236 +        def draw():
   2.237 +            self.draw_main_windows()
   2.238 +
   2.239 +            self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
   2.240 +            self.text_win.addstr(1, 0, "Press 'b' to boot, 'e' to edit the selected command in the")
   2.241 +            self.text_win.addstr(2, 0, "boot sequence, 'c' for a command-line, 'o' to open a new line")
   2.242 +            self.text_win.addstr(3, 0, "after ('O' for before) the selected line, 'd' to remove the")
   2.243 +            self.text_win.addstr(4, 0, "selected line, or escape to go back to the main menu.")
   2.244 +            self.text_win.addch(0, 8, curses.ACS_UARROW)
   2.245 +            self.text_win.addch(0, 14, curses.ACS_DARROW)
   2.246 +            (y, x) = self.text_win.getmaxyx()
   2.247 +            self.text_win.move(y - 1, x - 1)
   2.248 +            self.text_win.refresh()
   2.249 +
   2.250 +        curline = 1
   2.251 +        img = copy.deepcopy(origimg)
   2.252 +        while 1:
   2.253 +            draw()
   2.254 +            self.entry_win.clear()
   2.255 +            self.entry_win.box()
   2.256 +            for idx in range(1, len(img.lines)):
   2.257 +                # current line should be highlighted
   2.258 +                attr = 0
   2.259 +                if idx == curline:
   2.260 +                    attr = curses.A_REVERSE
   2.261 +
   2.262 +                # trim the line
   2.263 +                l = img.lines[idx].ljust(70)
   2.264 +                if len(l) > 70:
   2.265 +                    l = l[:69] + ">"
   2.266 +                    
   2.267 +                self.entry_win.addstr(idx, 2, l, attr)
   2.268 +            self.entry_win.refresh()
   2.269  
   2.270 -    offset = 0
   2.271 -    if is_disk_image(fn):
   2.272 -        offset = get_active_offset(fn)
   2.273 -        if offset == -1:
   2.274 -            raise RuntimeError, "Unable to find active partition on disk"
   2.275 +            c = self.screen.getch()
   2.276 +            if c in (ord('q'), 27): # 27 == esc
   2.277 +                break
   2.278 +            elif c == curses.KEY_UP:
   2.279 +                curline -= 1
   2.280 +            elif c == curses.KEY_DOWN:
   2.281 +                curline += 1
   2.282 +            elif c == ord('b'):
   2.283 +                self.isdone = True
   2.284 +                break
   2.285 +            elif c == ord('e'):
   2.286 +                l = self.edit_line(img.lines[curline])
   2.287 +                if l is not None:
   2.288 +                    img.set_from_line(l, replace = curline)
   2.289 +            elif c == ord('d'):
   2.290 +                img.lines.pop(curline)
   2.291 +            elif c == ord('o'):
   2.292 +                img.lines.insert(curline+1, "")
   2.293 +                curline += 1
   2.294 +            elif c == ord('O'):
   2.295 +                img.lines.insert(curline, "")
   2.296 +            elif c == ord('c'):
   2.297 +                self.command_line_mode()
   2.298 +                if self.isdone:
   2.299 +                    return
   2.300 +                
   2.301 +            # bound at the top and bottom
   2.302 +            if curline < 1:
   2.303 +                curline = 1
   2.304 +            elif curline >= len(img.lines):
   2.305 +                curline = len(img.lines) - 1
   2.306 +
   2.307 +        if self.isdone:
   2.308 +            origimg.reset(img.lines)
   2.309  
   2.310 -    # open the image and read the grub config
   2.311 -    fs = None
   2.312 -    for fstype in grub.fsys.fstypes.values():
   2.313 -        if fstype.sniff_magic(fn, offset):
   2.314 -            fs = fstype.open_fs(fn, offset)
   2.315 +    def edit_line(self, line):
   2.316 +        self.screen.clear()
   2.317 +        self.screen.addstr(1, 2, "[ Minimal BASH-like line editing is supported.  ")
   2.318 +        self.screen.addstr(2, 2, "  ESC at any time cancels.  ENTER at any time accepts your changes. ]")
   2.319 +        self.screen.refresh()
   2.320 +
   2.321 +        t = GrubLineEditor(self.screen, 5, 2, line)
   2.322 +        ret = t.edit()
   2.323 +        if ret:
   2.324 +            return ret
   2.325 +        return None
   2.326 +
   2.327 +    def command_line_mode(self):
   2.328 +        self.screen.clear()
   2.329 +        self.screen.addstr(1, 2, "[ Minimal BASH-like line editing is supported.  ESC at any time ")
   2.330 +        self.screen.addstr(2, 2, "  exits.  Typing 'boot' will boot with your entered commands. ] ")
   2.331 +        self.screen.refresh()
   2.332 +
   2.333 +        y = 5
   2.334 +        lines = []
   2.335 +        while 1:
   2.336 +            t = GrubLineEditor(self.screen, y, 2)
   2.337 +            ret = t.edit()
   2.338 +            if ret:
   2.339 +                if ret in ("quit", "return"):
   2.340 +                    break
   2.341 +                elif ret != "boot":
   2.342 +                    y += 1
   2.343 +                    lines.append(ret)
   2.344 +                    continue
   2.345 +
   2.346 +                # if we got boot, then we want to boot the entered image 
   2.347 +                img = grub.GrubConf.GrubImage(lines)
   2.348 +                self.cf.add_image(img)
   2.349 +                self.selected_image = len(self.cf.images) - 1
   2.350 +                self.isdone = True
   2.351 +                break
   2.352 +
   2.353 +            # else, we cancelled and should just go back
   2.354              break
   2.355  
   2.356 -    if fs is not None:
   2.357 -        grubfile = None
   2.358 -        for f in ("/boot/grub/menu.lst", "/boot/grub/grub.conf",
   2.359 -                  "/grub/menu.lst", "/grub/grub.conf"):
   2.360 -            if fs.file_exist(f):
   2.361 -                grubfile = f
   2.362 +    def read_config(self, fn, isConfig = False):
   2.363 +        """Read the given file to parse the config.  If isconfig, then
   2.364 +        we're being given a raw config file rather than a disk image."""
   2.365 +        
   2.366 +        if not os.access(fn, os.R_OK):
   2.367 +            raise RuntimeError, "Unable to access %s" %(fn,)
   2.368 +
   2.369 +        self.cf = grub.GrubConf.GrubConfigFile()
   2.370 +
   2.371 +        if isConfig:
   2.372 +            # set the config file and parse it
   2.373 +            self.cf.filename = fn
   2.374 +            self.cf.parse()
   2.375 +            return
   2.376 +
   2.377 +        offset = 0
   2.378 +        if is_disk_image(fn):
   2.379 +            offset = get_active_offset(fn)
   2.380 +            if offset == -1:
   2.381 +                raise RuntimeError, "Unable to find active partition on disk"
   2.382 +
   2.383 +        # open the image and read the grub config
   2.384 +        fs = None
   2.385 +        for fstype in grub.fsys.fstypes.values():
   2.386 +            if fstype.sniff_magic(fn, offset):
   2.387 +                fs = fstype.open_fs(fn, offset)
   2.388                  break
   2.389 -        if grubfile is None:
   2.390 -            raise RuntimeError, "we couldn't find /boot/grub{menu.lst,grub.conf} " + \
   2.391 -                                "in the image provided. halt!"
   2.392 -        f = fs.open_file(grubfile)
   2.393 -        buf = f.read()
   2.394 -        f.close()
   2.395 -        fs.close()
   2.396 -        # then parse the grub config
   2.397 -        cf.parse(buf)
   2.398 -    else:
   2.399 -        raise RuntimeError, "Unable to read filesystem" 
   2.400 -    
   2.401 -    return cf
   2.402 +
   2.403 +        if fs is not None:
   2.404 +            grubfile = None
   2.405 +            for f in ("/boot/grub/menu.lst", "/boot/grub/grub.conf",
   2.406 +                      "/grub/menu.lst", "/grub/grub.conf"):
   2.407 +                if fs.file_exist(f):
   2.408 +                    grubfile = f
   2.409 +                    break
   2.410 +            if grubfile is None:
   2.411 +                raise RuntimeError, "we couldn't find grub config file in the image provided."
   2.412 +            f = fs.open_file(grubfile)
   2.413 +            buf = f.read()
   2.414 +            f.close()
   2.415 +            fs.close()
   2.416 +            # then parse the grub config
   2.417 +            self.cf.parse(buf)
   2.418 +        else:
   2.419 +            raise RuntimeError, "Unable to read filesystem" 
   2.420 +
   2.421 +    def run(self):
   2.422 +        timeout = int(self.cf.timeout)
   2.423 +
   2.424 +        self.selected_image = self.cf.default
   2.425 +        self.isdone = False
   2.426 +        while not self.isdone:
   2.427 +            self.run_main(timeout)
   2.428 +            timeout = -1
   2.429 +            
   2.430 +        return self.selected_image
   2.431 +
   2.432 +    def run_main(self, timeout = -1):
   2.433 +        def draw():
   2.434 +            # set up the screen
   2.435 +            self.draw_main_windows()
   2.436 +            self.text_win.addstr(0, 0, "Use the U and D keys to select which entry is highlighted.")
   2.437 +            self.text_win.addstr(1, 0, "Press enter to boot the selected OS. 'e' to edit the")
   2.438 +            self.text_win.addstr(2, 0, "commands before booting, 'a' to modify the kernel arguments ")
   2.439 +            self.text_win.addstr(3, 0, "before booting, or 'c' for a command line.")
   2.440 +            self.text_win.addch(0, 8, curses.ACS_UARROW)
   2.441 +            self.text_win.addch(0, 14, curses.ACS_DARROW)
   2.442 +            (y, x) = self.text_win.getmaxyx()
   2.443 +            self.text_win.move(y - 1, x - 1)
   2.444 +            self.text_win.refresh()
   2.445  
   2.446 +        # now loop until we hit the timeout or get a go from the user
   2.447 +        mytime = 0
   2.448 +        while (timeout == -1 or mytime < int(timeout)):
   2.449 +            draw()
   2.450 +            if timeout != -1 and mytime != -1: 
   2.451 +                self.screen.addstr(20, 5, "Will boot selected entry in %2d seconds"
   2.452 +                                   %(int(timeout) - mytime))
   2.453 +            else:
   2.454 +                self.screen.addstr(20, 5, " " * 80)
   2.455 +
   2.456 +            self.fill_entry_list()
   2.457 +            c = self.screen.getch()
   2.458 +            if mytime != -1:
   2.459 +                mytime += 1
   2.460 +
   2.461 +            # handle keypresses
   2.462 +            if c == ord('c'):
   2.463 +                self.command_line_mode()
   2.464 +                break
   2.465 +            elif c == ord('a'):
   2.466 +                # find the kernel line, edit it and then boot
   2.467 +                img = self.cf.images[self.selected_image]
   2.468 +                for line in img.lines:
   2.469 +                    if line.startswith("kernel"):
   2.470 +                        l = self.edit_line(line)
   2.471 +                        if l is not None:
   2.472 +                            img.set_from_line(l, replace = True)
   2.473 +                            self.isdone = True
   2.474 +                            break
   2.475 +                break
   2.476 +            elif c == ord('e'):
   2.477 +                img = self.cf.images[self.selected_image]
   2.478 +                self.edit_entry(img)
   2.479 +                break
   2.480 +            elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
   2.481 +                self.isdone = True
   2.482 +                break
   2.483 +            elif c == curses.KEY_UP:
   2.484 +                mytime = -1
   2.485 +                self.selected_image -= 1
   2.486 +            elif c == curses.KEY_DOWN:
   2.487 +                mytime = -1
   2.488 +                self.selected_image += 1
   2.489 +#            elif c in (ord('q'), 27): # 27 == esc
   2.490 +#                self.selected_image = -1
   2.491 +#                self.isdone = True
   2.492 +#                break
   2.493 +
   2.494 +            # bound at the top and bottom
   2.495 +            if self.selected_image < 0:
   2.496 +                self.selected_image = 0
   2.497 +            elif self.selected_image >= len(self.cf.images):
   2.498 +                self.selected_image = len(self.cf.images) - 1
   2.499 +        
   2.500  def get_entry_idx(cf, entry):
   2.501      # first, see if the given entry is numeric
   2.502      try:
   2.503 @@ -155,63 +421,12 @@ def get_entry_idx(cf, entry):
   2.504  
   2.505      return None
   2.506  
   2.507 -def main(cf = None):
   2.508 -    mytime = 0
   2.509 -    timeout = int(cf.timeout)
   2.510 -
   2.511 -    (stdscr, win) = draw_window()
   2.512 -    stdscr.timeout(1000)
   2.513 -    selected = cf.default
   2.514 -    
   2.515 -    while (timeout == -1 or mytime < int(timeout)):
   2.516 -        if timeout != -1 and mytime != -1: 
   2.517 -            stdscr.addstr(20, 5, "Will boot selected entry in %2d seconds"
   2.518 -                          %(int(timeout) - mytime))
   2.519 -        else:
   2.520 -            stdscr.addstr(20, 5, " " * 80)
   2.521 -            
   2.522 -        fill_entries(win, cf, selected)
   2.523 -        c = stdscr.getch()
   2.524 -        if mytime != -1:
   2.525 -            mytime += 1
   2.526 -#        if c == ord('q'):
   2.527 -#            selected = -1
   2.528 -#            break
   2.529 -        if c == ord('c'):
   2.530 -            # FIXME: needs to go to command line mode
   2.531 -            continue
   2.532 -        elif c == ord('a'):
   2.533 -            # FIXME: needs to go to append mode
   2.534 -            continue
   2.535 -        elif c == ord('e'):
   2.536 -            # FIXME: needs to go to edit mode
   2.537 -            continue
   2.538 -        elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
   2.539 -            break
   2.540 -        elif c == curses.KEY_UP:
   2.541 -            mytime = -1
   2.542 -            selected -= 1
   2.543 -        elif c == curses.KEY_DOWN:
   2.544 -            mytime = -1
   2.545 -            selected += 1
   2.546 -        else:
   2.547 -            pass
   2.548 -
   2.549 -        # bound at the top and bottom
   2.550 -        if selected < 0:
   2.551 -            selected = 0
   2.552 -        elif selected >= len(cf.images):
   2.553 -            selected = len(cf.images) - 1
   2.554 -
   2.555 -    if selected >= 0:
   2.556 -        return selected
   2.557 -
   2.558  if __name__ == "__main__":
   2.559      sel = None
   2.560      
   2.561      def run_main(scr, *args):
   2.562          global sel
   2.563 -        sel = main(cf)
   2.564 +        sel = g.run()
   2.565  
   2.566      def usage():
   2.567          print >> sys.stderr, "Usage: %s [-q|--quiet] [--output=] [--entry=] <image>" %(sys.argv[0],)
   2.568 @@ -253,24 +468,32 @@ if __name__ == "__main__":
   2.569      else:
   2.570          fd = os.open(output, os.O_WRONLY)
   2.571  
   2.572 -    cf = get_config(file, isconfig)
   2.573 +    g = Grub(file, isconfig)
   2.574      if interactive:
   2.575          curses.wrapper(run_main)
   2.576      else:
   2.577 -        sel = cf.default
   2.578 +        sel = g.cf.default
   2.579  
   2.580      # set the entry to boot as requested
   2.581      if entry is not None:
   2.582 -        idx = get_entry_idx(cf, entry)
   2.583 -        if idx is not None and idx > 0 and idx < len(cf.images):
   2.584 +        idx = get_entry_idx(g.cf, entry)
   2.585 +        if idx is not None and idx > 0 and idx < len(g.cf.images):
   2.586              sel = idx
   2.587  
   2.588 -    img = cf.images[sel]
   2.589 +    if sel == -1:
   2.590 +        print "No kernel image selected!"
   2.591 +        sys.exit(1)
   2.592 +
   2.593 +    img = g.cf.images[sel]
   2.594      print "Going to boot %s" %(img.title)
   2.595      print "  kernel: %s" %(img.kernel[1],)
   2.596      if img.initrd:
   2.597          print "  initrd: %s" %(img.initrd[1],)
   2.598  
   2.599 +    if isconfig:
   2.600 +        print "  args: %s" %(img.args,)
   2.601 +        sys.exit(0)
   2.602 +        
   2.603      offset = 0
   2.604      if is_disk_image(file):
   2.605          offset = get_active_offset(file)
   2.606 @@ -288,14 +511,14 @@ if __name__ == "__main__":
   2.607          raise RuntimeError, "Unable to open filesystem"
   2.608  
   2.609      kernel = fs.open_file(img.kernel[1],).read()
   2.610 -    (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.")
   2.611 +    (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.", dir="/var/lib/xen")
   2.612      os.write(tfd, kernel)
   2.613      os.close(tfd)
   2.614      sxp = "linux (kernel %s)" %(fn,)
   2.615  
   2.616      if img.initrd:
   2.617          initrd = fs.open_file(img.initrd[1],).read()
   2.618 -        (tfd, fn) = tempfile.mkstemp(prefix="initrd.")
   2.619 +        (tfd, fn) = tempfile.mkstemp(prefix="initrd.", dir="/var/lib/xen")
   2.620          os.write(tfd, initrd)
   2.621          os.close(tfd)
   2.622          sxp += "(ramdisk %s)" %(fn,)