ia64/xen-unstable

view tools/pygrub/src/pygrub @ 9029:c7550ed2ccbf

pygrub currently takes a file on the command line to mean a config file
if it can't figure out partitions and filesystems. This then makes it
balloon to absurd sizes. Make this only happen if you pass a debugging
flag

Signed-off-by: Jeremy Katz <katzj@redhat.com>
author kaf24@firebug.cl.cam.ac.uk
date Sun Feb 26 10:54:06 2006 +0100 (2006-02-26)
parents 859c8d66b203
children 2e8b1175fd37
line source
1 #!/usr/bin/python
2 #
3 # pygrub - simple python-based bootloader for Xen
4 #
5 # Copyright 2005 Red Hat, Inc.
6 # Jeremy Katz <katzj@redhat.com>
7 #
8 # This software may be freely redistributed under the terms of the GNU
9 # general public license.
10 #
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
14 #
16 import os, sys, string, struct, tempfile
17 import logging
19 import curses, _curses, curses.wrapper
20 import getopt
22 sys.path = [ '/usr/lib/python' ] + sys.path
24 import grub.GrubConf
25 import grub.fsys
27 PYGRUB_VER = 0.3
30 def draw_window():
31 stdscr = curses.initscr()
32 if hasattr(curses, 'use_default_colors'):
33 curses.use_default_colors()
34 try:
35 curses.curs_set(0)
36 except _curses.error:
37 pass
39 stdscr.addstr(1, 4, "pyGRUB version %s" %(PYGRUB_VER,))
41 win = curses.newwin(10, 74, 2, 1)
42 win.box()
43 win.refresh()
45 stdscr.addstr(12, 5, "Use the U and D keys to select which entry is highlighted.")
46 stdscr.addstr(13, 5, "Press enter to boot the selected OS. 'e' to edit the")
47 stdscr.addstr(14, 5, "commands before booting, 'a' to modify the kernel arguments ")
48 stdscr.addstr(15, 5, "before booting, or 'c' for a command line.")
49 stdscr.addch(12, 13, curses.ACS_UARROW)
50 stdscr.addch(12, 19, curses.ACS_DARROW)
51 (y, x) = stdscr.getmaxyx()
52 stdscr.move(y - 1, x - 1)
54 stdscr.refresh()
55 return (stdscr, win)
57 def fill_entries(win, cfg, selected):
58 y = 0
60 for i in cfg.images:
61 if (0, y) > win.getmaxyx():
62 break
63 if y == selected:
64 attr = curses.A_REVERSE
65 else:
66 attr = 0
67 win.addstr(y + 1, 2, i.title.ljust(70), attr)
68 y += 1
69 win.refresh()
71 def select(win, line):
72 win.attron(curses.A_REVERSE)
73 win.redrawln(line + 1, 1)
74 win.refresh()
76 def is_disk_image(file):
77 fd = os.open(file, os.O_RDONLY)
78 buf = os.read(fd, 512)
79 os.close(fd)
81 if len(buf) >= 512 and struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,):
82 return True
83 return False
85 SECTOR_SIZE=512
86 def get_active_offset(file):
87 """Find the offset for the start of the first active partition in the
88 disk image file."""
89 fd = os.open(file, os.O_RDONLY)
90 buf = os.read(fd, 512)
91 for poff in (446, 462, 478, 494): # partition offsets
92 # active partition has 0x80 as the first byte
93 if struct.unpack("<c", buf[poff:poff+1]) == ('\x80',):
94 return struct.unpack("<L", buf[poff+8:poff+12])[0] * SECTOR_SIZE
95 return -1
97 def get_config(fn, isconfig = False):
98 if not os.access(fn, os.R_OK):
99 raise RuntimeError, "Unable to access %s" %(fn,)
101 cf = grub.GrubConf.GrubConfigFile()
103 if isconfig:
104 # set the config file and parse it
105 cf.filename = fn
106 cf.parse()
107 return cf
109 offset = 0
110 if is_disk_image(fn):
111 offset = get_active_offset(fn)
112 if offset == -1:
113 raise RuntimeError, "Unable to find active partition on disk"
115 # open the image and read the grub config
116 fs = None
117 for fstype in grub.fsys.fstypes.values():
118 if fstype.sniff_magic(fn, offset):
119 fs = fstype.open_fs(fn, offset)
120 break
122 if fs is not None:
123 grubfile = None
124 for f in ("/boot/grub/menu.lst", "/boot/grub/grub.conf",
125 "/grub/menu.lst", "/grub/grub.conf"):
126 if fs.file_exist(f):
127 grubfile = f
128 break
129 if grubfile is None:
130 raise RuntimeError, "we couldn't find /boot/grub{menu.lst,grub.conf} " + \
131 "in the image provided. halt!"
132 f = fs.open_file(grubfile)
133 buf = f.read()
134 f.close()
135 fs.close()
136 # then parse the grub config
137 cf.parse(buf)
138 else:
139 raise RuntimeError, "Unable to read filesystem"
141 return cf
143 def get_entry_idx(cf, entry):
144 # first, see if the given entry is numeric
145 try:
146 idx = string.atoi(entry)
147 return idx
148 except ValueError:
149 pass
151 # it's not, now check the labels for a match
152 for i in range(len(cf.images)):
153 if entry == cf.images[i].title:
154 return i
156 return None
158 def main(cf = None):
159 mytime = 0
160 timeout = int(cf.timeout)
162 (stdscr, win) = draw_window()
163 stdscr.timeout(1000)
164 selected = cf.default
166 while (timeout == -1 or mytime < int(timeout)):
167 if timeout != -1 and mytime != -1:
168 stdscr.addstr(20, 5, "Will boot selected entry in %2d seconds"
169 %(int(timeout) - mytime))
170 else:
171 stdscr.addstr(20, 5, " " * 80)
173 fill_entries(win, cf, selected)
174 c = stdscr.getch()
175 if mytime != -1:
176 mytime += 1
177 # if c == ord('q'):
178 # selected = -1
179 # break
180 if c == ord('c'):
181 # FIXME: needs to go to command line mode
182 continue
183 elif c == ord('a'):
184 # FIXME: needs to go to append mode
185 continue
186 elif c == ord('e'):
187 # FIXME: needs to go to edit mode
188 continue
189 elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
190 break
191 elif c == curses.KEY_UP:
192 mytime = -1
193 selected -= 1
194 elif c == curses.KEY_DOWN:
195 mytime = -1
196 selected += 1
197 else:
198 pass
200 # bound at the top and bottom
201 if selected < 0:
202 selected = 0
203 elif selected >= len(cf.images):
204 selected = len(cf.images) - 1
206 if selected >= 0:
207 return selected
209 if __name__ == "__main__":
210 sel = None
212 def run_main(scr, *args):
213 global sel
214 sel = main(cf)
216 def usage():
217 print >> sys.stderr, "Usage: %s [-q|--quiet] [--output=] [--entry=] <image>" %(sys.argv[0],)
219 try:
220 opts, args = getopt.gnu_getopt(sys.argv[1:], 'qh::',
221 ["quiet", "help", "output=", "entry=",
222 "isconfig"])
223 except getopt.GetoptError:
224 usage()
225 sys.exit(1)
227 if len(args) < 1:
228 usage()
229 sys.exit(1)
230 file = args[0]
232 output = None
233 entry = None
234 interactive = True
235 isconfig = False
236 for o, a in opts:
237 if o in ("-q", "--quiet"):
238 interactive = False
239 elif o in ("-h", "--help"):
240 usage()
241 sys.exit()
242 elif o in ("--output",):
243 output = a
244 elif o in ("--entry",):
245 entry = a
246 # specifying the entry to boot implies non-interactive
247 interactive = False
248 elif o in ("--isconfig",):
249 isconfig = True
251 if output is None or output == "-":
252 fd = sys.stdout.fileno()
253 else:
254 fd = os.open(output, os.O_WRONLY)
256 cf = get_config(file, isconfig)
257 if interactive:
258 curses.wrapper(run_main)
259 else:
260 sel = cf.default
262 # set the entry to boot as requested
263 if entry is not None:
264 idx = get_entry_idx(cf, entry)
265 if idx is not None and idx > 0 and idx < len(cf.images):
266 sel = idx
268 img = cf.images[sel]
269 print "Going to boot %s" %(img.title)
270 print " kernel: %s" %(img.kernel[1],)
271 if img.initrd:
272 print " initrd: %s" %(img.initrd[1],)
274 offset = 0
275 if is_disk_image(file):
276 offset = get_active_offset(file)
277 if offset == -1:
278 raise RuntimeError, "Unable to find active partition on disk"
280 # read the kernel and initrd onto the hostfs
281 fs = None
282 for fstype in grub.fsys.fstypes.values():
283 if fstype.sniff_magic(file, offset):
284 fs = fstype.open_fs(file, offset)
285 break
287 if fs is None:
288 raise RuntimeError, "Unable to open filesystem"
290 kernel = fs.open_file(img.kernel[1],).read()
291 (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.")
292 os.write(tfd, kernel)
293 os.close(tfd)
294 sxp = "linux (kernel %s)" %(fn,)
296 if img.initrd:
297 initrd = fs.open_file(img.initrd[1],).read()
298 (tfd, fn) = tempfile.mkstemp(prefix="initrd.")
299 os.write(tfd, initrd)
300 os.close(tfd)
301 sxp += "(ramdisk %s)" %(fn,)
302 else:
303 initrd = None
304 sxp += "(args '%s')" %(img.args,)
306 sys.stdout.flush()
307 os.write(fd, sxp)