# # XendBootloader.py - Framework to run a boot loader for picking the kernel # # Copyright 2005-2006 Red Hat, Inc. # Jeremy Katz # # 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, select, errno, stat, signal, tty import random import shlex from xen.xend import sxp from xen.util import mkdir, oshelp from XendLogging import log from XendError import VmError import pty, termios, fcntl from xen.lowlevel import ptsname def bootloader(blexec, disk, dom, quiet = False, blargs = '', kernel = '', ramdisk = '', kernel_args = ''): """Run the boot loader executable on the given disk and return a config image. @param blexec Binary to use as the boot loader @param disk Disk to run the boot loader on. @param dom DomainInfo representing the domain being booted. @param quiet Run in non-interactive mode, just booting the default. @param blargs Arguments to pass to the bootloader.""" if not os.access(blexec, os.X_OK): msg = "Bootloader isn't executable" log.error(msg) raise VmError(msg) if not os.access(disk, os.R_OK): msg = "Disk isn't accessible" log.error(msg) raise VmError(msg) if os.uname()[0] == "NetBSD" and disk.startswith('/dev/'): disk = "/r".join(disk.rsplit("/",1)) mkdir.parents("/var/run/xend/boot/", stat.S_IRWXU) while True: fifo = "/var/run/xend/boot/xenbl.%s" %(random.randint(0, 32000),) try: os.mkfifo(fifo, 0600) except OSError, e: if (e.errno != errno.EEXIST): raise break # We need to present the bootloader's tty as a pty slave that xenconsole # can access. Since the bootloader itself needs a pty slave, # we end up with a connection like this: # # xenconsole -- (slave pty1 master) <-> (master pty2 slave) -- bootloader # # where we copy characters between the two master fds, as well as # listening on the bootloader's fifo for the results. (m1, s1) = pty.openpty() # On Solaris, the pty master side will get cranky if we try # to write to it while there is no slave. To work around this, # keep the slave descriptor open until we're done. Set it # to raw terminal parameters, otherwise it will echo back # characters, which will confuse the I/O loop below. # Furthermore, a raw master pty device has no terminal # semantics on Solaris, so don't try to set any attributes # for it. if os.uname()[0] != 'SunOS' and os.uname()[0] != 'NetBSD': tty.setraw(m1) os.close(s1) else: tty.setraw(s1) fcntl.fcntl(m1, fcntl.F_SETFL, os.O_NDELAY) slavename = ptsname.ptsname(m1) dom.storeDom("console/tty", slavename) # Release the domain lock here, because we definitely don't want # a stuck bootloader to deny service to other xend clients. from xen.xend import XendDomain domains = XendDomain.instance() domains.domains_lock.release() (child, m2) = pty.fork() if (not child): args = [ blexec ] if kernel: args.append("--kernel=%s" % kernel) if ramdisk: args.append("--ramdisk=%s" % ramdisk) if kernel_args: args.append("--args=%s" % kernel_args) if quiet: args.append("-q") args.append("--output=%s" % fifo) if blargs: args.extend(shlex.split(blargs)) args.append(disk) try: log.debug("Launching bootloader as %s." % str(args)) env = os.environ.copy() env['TERM'] = 'vt100' oshelp.close_fds() os.execvpe(args[0], args, env) except OSError, e: print e pass os._exit(1) # record that this domain is bootloading dom.bootloader_pid = child # On Solaris, the master pty side does not have terminal semantics, # so don't try to set any attributes, as it will fail. if os.uname()[0] != 'SunOS': tty.setraw(m2); fcntl.fcntl(m2, fcntl.F_SETFL, os.O_NDELAY); while True: try: r = os.open(fifo, os.O_RDONLY) except OSError, e: if e.errno == errno.EINTR: continue break fcntl.fcntl(r, fcntl.F_SETFL, os.O_NDELAY); ret = "" inbuf=""; outbuf=""; # filedescriptors: # r - input from the bootloader (bootstring output) # m1 - input/output from/to xenconsole # m2 - input/output from/to pty that controls the bootloader # The filedescriptors are NDELAY, so it's ok to try to read # bigger chunks than may be available, to keep e.g. curses # screen redraws in the bootloader efficient. m1 is the side that # gets xenconsole input, which will be keystrokes, so a small number # is sufficient. m2 is pygrub output, which will be curses screen # updates, so a larger number (1024) is appropriate there. # # For writeable descriptors, only include them in the set for select # if there is actual data to write, otherwise this would loop too fast, # eating up CPU time. while True: wsel = [] if len(outbuf) != 0: wsel = wsel + [m1] if len(inbuf) != 0: wsel = wsel + [m2] sel = select.select([r, m1, m2], wsel, []) try: if m1 in sel[0]: s = os.read(m1, 16) inbuf += s if m2 in sel[1]: n = os.write(m2, inbuf) inbuf = inbuf[n:] except OSError, e: if e.errno == errno.EIO: pass try: if m2 in sel[0]: s = os.read(m2, 1024) outbuf += s if m1 in sel[1]: n = os.write(m1, outbuf) outbuf = outbuf[n:] except OSError, e: if e.errno == errno.EIO: pass if r in sel[0]: s = os.read(r, 128) ret = ret + s if len(s) == 0: break del inbuf del outbuf os.waitpid(child, 0) os.close(r) os.close(m2) os.close(m1) if os.uname()[0] == 'SunOS' or os.uname()[0] == 'NetBSD': os.close(s1) os.unlink(fifo) # Re-acquire the lock to cover the changes we're about to make # when we return to domain creation. domains.domains_lock.acquire() if dom.bootloader_pid is None: msg = "Domain was died while the bootloader was running." log.error(msg) raise VmError, msg dom.bootloader_pid = None if len(ret) == 0: msg = "Boot loader didn't return any data!" log.error(msg) raise VmError, msg pin = sxp.Parser() pin.input(ret) pin.input_eof() blcfg = pin.val return blcfg def bootloader_tidy(dom): if hasattr(dom, "bootloader_pid") and dom.bootloader_pid is not None: pid = dom.bootloader_pid dom.bootloader_pid = None os.kill(pid, signal.SIGKILL)