ia64/xen-unstable

view tools/pygrub/src/pygrub @ 7238:971e7c7411b3

Raise an exception if an error appears on the pipes to our children, and make
sure that the child's pipes are closed even under that exception. Move the
handling of POLLHUP to the end of the loop, so that we guarantee to read any
remaining data from the child if POLLHUP and POLLIN appear at the same time.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author emellor@ewan
date Thu Oct 06 10:13:11 2005 +0100 (2005-10-06)
parents e173a853dc46
children d5775d0bcf1e
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 curses.use_default_colors()
33 try:
34 curses.curs_set(0)
35 except _curses.error:
36 pass
38 stdscr.addstr(1, 4, "pyGRUB version %s" %(PYGRUB_VER,))
40 win = curses.newwin(10, 74, 2, 1)
41 win.box()
42 win.refresh()
44 stdscr.addstr(12, 5, "Use the U and D keys to select which entry is highlighted.")
45 stdscr.addstr(13, 5, "Press enter to boot the selected OS. 'e' to edit the")
46 stdscr.addstr(14, 5, "commands before booting, 'a' to modify the kernel arguments ")
47 stdscr.addstr(15, 5, "before booting, or 'c' for a command line.")
48 stdscr.addch(12, 13, curses.ACS_UARROW)
49 stdscr.addch(12, 19, curses.ACS_DARROW)
50 (y, x) = stdscr.getmaxyx()
51 stdscr.move(y - 1, x - 1)
53 stdscr.refresh()
54 return (stdscr, win)
56 def fill_entries(win, cfg, selected):
57 y = 0
59 for i in cfg.images:
60 if (0, y) > win.getmaxyx():
61 break
62 if y == selected:
63 attr = curses.A_REVERSE
64 else:
65 attr = 0
66 win.addstr(y + 1, 2, i.title.ljust(70), attr)
67 y += 1
68 win.refresh()
70 def select(win, line):
71 win.attron(curses.A_REVERSE)
72 win.redrawln(line + 1, 1)
73 win.refresh()
75 def is_disk_image(file):
76 fd = os.open(file, os.O_RDONLY)
77 buf = os.read(fd, 512)
78 os.close(fd)
80 if len(buf) >= 512 and struct.unpack("H", buf[0x1fe: 0x200]) == (0xaa55,):
81 return True
82 return False
84 SECTOR_SIZE=512
85 def get_active_offset(file):
86 """Find the offset for the start of the first active partition in the
87 disk image file."""
88 fd = os.open(file, os.O_RDONLY)
89 buf = os.read(fd, 512)
90 for poff in (446, 462, 478, 494): # partition offsets
91 # active partition has 0x80 as the first byte
92 if struct.unpack("<c", buf[p:p+1]) == ('\x80',):
93 return struct.unpack("<", buf[p+8:p+12])[0] * SECTOR_SIZE
94 return -1
96 def get_config(fn):
97 if not os.access(fn, os.R_OK):
98 raise RuntimeError, "Unable to access %s" %(fn,)
100 cf = grub.GrubConf.GrubConfigFile()
102 offset = 0
103 if is_disk_image(fn):
104 offset = get_active_offset(fn)
105 if offset == -1:
106 raise RuntimeError, "Unable to find active partition on disk"
108 # open the image and read the grub config
109 fs = None
110 for fstype in grub.fsys.fstypes.values():
111 if fstype.sniff_magic(fn, offset):
112 fs = fstype.open_fs(fn, offset)
113 break
115 if fs is not None:
116 if fs.file_exist("/boot/grub/menu.lst"):
117 grubfile = "/boot/grub/menu.lst"
118 elif fs.file_exist("/boot/grub/grub.conf"):
119 grubfile = "/boot/grub/grub.conf"
120 else:
121 raise RuntimeError, "we couldn't find /boot/grub{menu.lst,grub.conf} " + \
122 "in the image provided. halt!"
123 f = fs.open_file(grubfile)
124 buf = f.read()
125 f.close()
126 fs.close()
127 # then parse the grub config
128 cf.parse(buf)
129 else:
130 # set the config file and parse it
131 cf.filename = fn
132 cf.parse()
134 return cf
136 def get_entry_idx(cf, entry):
137 # first, see if the given entry is numeric
138 try:
139 idx = string.atoi(entry)
140 return idx
141 except ValueError:
142 pass
144 # it's not, now check the labels for a match
145 for i in range(len(cf.images)):
146 if entry == cf.images[i].title:
147 return i
149 return None
151 def main(cf = None):
152 mytime = 0
154 (stdscr, win) = draw_window()
155 stdscr.timeout(1000)
156 selected = cf.default
158 while (mytime < int(cf.timeout)):
159 if cf.timeout != -1 and mytime != -1:
160 stdscr.addstr(20, 5, "Will boot selected entry in %2d seconds"
161 %(int(cf.timeout) - mytime))
162 else:
163 stdscr.addstr(20, 5, " " * 80)
165 fill_entries(win, cf, selected)
166 c = stdscr.getch()
167 if mytime != -1:
168 mytime += 1
169 # if c == ord('q'):
170 # selected = -1
171 # break
172 elif c == ord('c'):
173 # FIXME: needs to go to command line mode
174 continue
175 elif c == ord('a'):
176 # FIXME: needs to go to append mode
177 continue
178 elif c == ord('e'):
179 # FIXME: needs to go to edit mode
180 continue
181 elif c in (curses.KEY_ENTER, ord('\n'), ord('\r')):
182 break
183 elif c == curses.KEY_UP:
184 mytime = -1
185 selected -= 1
186 elif c == curses.KEY_DOWN:
187 mytime = -1
188 selected += 1
189 else:
190 pass
192 # bound at the top and bottom
193 if selected < 0:
194 selected = 0
195 elif selected >= len(cf.images):
196 selected = len(cf.images) - 1
198 if selected >= 0:
199 return selected
201 if __name__ == "__main__":
202 sel = None
204 def run_main(scr, *args):
205 global sel
206 sel = main(cf)
208 def usage():
209 print >> sys.stderr, "Usage: %s [-q|--quiet] [--output=] [--entry=] <image>" %(sys.argv[0],)
211 try:
212 opts, args = getopt.gnu_getopt(sys.argv[1:], 'qh::',
213 ["quiet", "help", "output=", "entry="])
214 except getopt.GetoptError:
215 usage()
216 sys.exit(1)
218 if len(args) < 1:
219 usage()
220 sys.exit(1)
221 file = args[0]
223 output = None
224 entry = None
225 interactive = True
226 for o, a in opts:
227 if o in ("-q", "--quiet"):
228 interactive = False
229 elif o in ("-h", "--help"):
230 usage()
231 sys.exit()
232 elif o in ("--output",):
233 output = a
234 elif o in ("--entry",):
235 entry = a
236 # specifying the entry to boot implies non-interactive
237 interactive = False
239 if output is None or output == "-":
240 fd = sys.stdout.fileno()
241 else:
242 fd = os.open(output, os.O_WRONLY)
244 cf = get_config(file)
245 if interactive:
246 curses.wrapper(run_main)
247 else:
248 sel = cf.default
250 # set the entry to boot as requested
251 if entry is not None:
252 idx = get_entry_idx(cf, entry)
253 if idx is not None and idx > 0 and idx < len(cf.images):
254 sel = idx
256 img = cf.images[sel]
257 print "Going to boot %s" %(img.title)
258 print " kernel: %s" %(img.kernel[1],)
259 if img.initrd:
260 print " initrd: %s" %(img.initrd[1],)
262 offset = 0
263 if is_disk_image(file):
264 offset = get_active_offset(fn)
265 if offset == -1:
266 raise RuntimeError, "Unable to find active partition on disk"
268 # read the kernel and initrd onto the hostfs
269 fs = None
270 for fstype in grub.fsys.fstypes.values():
271 if fstype.sniff_magic(file, offset):
272 fs = fstype.open_fs(file, offset)
273 break
275 if fs is None:
276 raise RuntimeError, "Unable to open filesystem"
278 kernel = fs.open_file(img.kernel[1],).read()
279 (tfd, fn) = tempfile.mkstemp(prefix="vmlinuz.")
280 os.write(tfd, kernel)
281 os.close(tfd)
282 sxp = "linux (kernel %s)" %(fn,)
284 if img.initrd:
285 initrd = fs.open_file(img.initrd[1],).read()
286 (tfd, fn) = tempfile.mkstemp(prefix="initrd.")
287 os.write(tfd, initrd)
288 os.close(tfd)
289 sxp += "(ramdisk %s)" %(fn,)
290 else:
291 initrd = None
292 sxp += "(args '%s')" %(img.args,)
294 sys.stdout.flush()
295 os.write(fd, sxp)