diff -r 5a3960e15d4a tools/console/client/main.c --- a/tools/console/client/main.c Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/console/client/main.c Wed Jan 17 10:35:34 2007 +0000 @@ -71,6 +71,43 @@ static void usage(const char *program) { , program); } +static int get_pty_fd(struct xs_handle *xs, char *path, int seconds) +/* Check for a pty in xenstore, open it and return its fd. + * Assumes there is already a watch set in the store for this path. */ +{ + struct timeval tv; + fd_set watch_fdset; + int xs_fd = xs_fileno(xs), pty_fd = -1; + int start, now; + unsigned int len = 0; + char *pty_path, **watch_paths;; + + start = now = time(NULL); + do { + tv.tv_usec = 0; + tv.tv_sec = (start + seconds) - now; + FD_ZERO(&watch_fdset); + FD_SET(xs_fd, &watch_fdset); + if (select(xs_fd + 1, &watch_fdset, NULL, NULL, &tv)) { + /* Read the watch to drain the buffer */ + watch_paths = xs_read_watch(xs, &len); + free(watch_paths); + /* We only watch for one thing, so no need to + * disambiguate: just read the pty path */ + pty_path = xs_read(xs, XBT_NULL, path, &len); + if (pty_path != NULL) { + pty_fd = open(pty_path, O_RDWR | O_NOCTTY); + if (pty_fd == -1) + err(errno, "Could not open tty `%s'", + pty_path); + free(pty_path); + } + } + } while (pty_fd == -1 && (now = time(NULL)) < start + seconds); + return pty_fd; +} + + /* don't worry too much if setting terminal attributes fail */ static void init_term(int fd, struct termios *old) { @@ -91,23 +128,37 @@ static void restore_term(int fd, struct tcsetattr(fd, TCSAFLUSH, old); } -static int console_loop(int fd) -{ - int ret; +static int console_loop(int fd, struct xs_handle *xs, char *pty_path) +{ + int ret, xs_fd = xs_fileno(xs), max_fd; do { fd_set fds; FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); - FD_SET(fd, &fds); - - ret = select(fd + 1, &fds, NULL, NULL, NULL); + max_fd = STDIN_FILENO; + FD_SET(xs_fd, &fds); + if (xs_fd > max_fd) max_fd = xs_fd; + if (fd != -1) FD_SET(fd, &fds); + if (fd > max_fd) max_fd = fd; + + ret = select(max_fd + 1, &fds, NULL, NULL, NULL); if (ret == -1) { if (errno == EINTR || errno == EAGAIN) { continue; } return -1; + } + + if (FD_ISSET(xs_fileno(xs), &fds)) { + int newfd = get_pty_fd(xs, pty_path, 0); + close(fd); + if (newfd == -1) + /* Console PTY has become invalid */ + return 0; + fd = newfd; + continue; } if (FD_ISSET(STDIN_FILENO, &fds)) { @@ -128,12 +179,13 @@ static int console_loop(int fd) } if (!write_sync(fd, msg, len)) { - perror("write() failed"); - return -1; - } - } - - if (FD_ISSET(fd, &fds)) { + close(fd); + fd = -1; + continue; + } + } + + if (fd != -1 && FD_ISSET(fd, &fds)) { ssize_t len; char msg[512]; @@ -143,7 +195,9 @@ static int console_loop(int fd) (errno == EINTR || errno == EAGAIN)) { continue; } - return -1; + close(fd); + fd = -1; + continue; } if (!write_sync(STDOUT_FILENO, msg, len)) { @@ -168,12 +222,10 @@ int main(int argc, char **argv) { 0 }, }; - char *str_pty, *path; - int spty; - unsigned int len = 0; + char *path; + int spty, xsfd; struct xs_handle *xs; char *end; - time_t now; while((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { switch(ch) { @@ -213,7 +265,6 @@ int main(int argc, char **argv) if (path == NULL) err(ENOMEM, "realloc"); strcat(path, "/console/tty"); - str_pty = xs_read(xs, XBT_NULL, path, &len); /* FIXME consoled currently does not assume domain-0 doesn't have a console which is good when we break domain-0 up. To keep us @@ -224,38 +275,24 @@ int main(int argc, char **argv) exit(EINVAL); } + /* Set a watch on this domain's console pty */ + if (!xs_watch(xs, path, "")) + err(errno, "Can't set watch for console pty"); + xsfd = xs_fileno(xs); + /* Wait a little bit for tty to appear. There is a race - condition that occurs after xend creates a domain. This - code might be running before consoled has noticed the new - domain and setup a pty for it. - - A xenstore watch would slightly improve responsiveness but - a timeout would still be needed since we don't want to - block forever if given an invalid domain or worse yet, a - domain that someone else has connected to. */ - - now = time(0); - while (str_pty == NULL && (now + 5) > time(0)) { - struct timeval tv = { 0, 250000 }; - select(0, NULL, NULL, NULL, &tv); /* pause briefly */ - - str_pty = xs_read(xs, XBT_NULL, path, &len); - } - - if (str_pty == NULL) { + condition that occurs after xend creates a domain. This code + might be running before consoled has noticed the new domain + and setup a pty for it. */ + spty = get_pty_fd(xs, path, 5); + if (spty == -1) { err(errno, "Could not read tty from store"); } - spty = open(str_pty, O_RDWR | O_NOCTTY); - if (spty == -1) { - err(errno, "Could not open tty `%s'", str_pty); - } - free(str_pty); + init_term(STDIN_FILENO, &attr); + console_loop(spty, xs, path); + restore_term(STDIN_FILENO, &attr); + free(path); - - init_term(STDIN_FILENO, &attr); - console_loop(spty); - restore_term(STDIN_FILENO, &attr); - return 0; } diff -r 5a3960e15d4a tools/misc/xend --- a/tools/misc/xend Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/misc/xend Wed Jan 17 10:35:34 2007 +0000 @@ -44,7 +44,7 @@ for p in ['python%s' % sys.version[:3], if os.path.exists(os.path.join(d, AUXBIN)): sys.path.append(d) import xen.util.auxbin - libpath = xen.util.auxbin.libpath() + libpath = os.path.join(xen.util.auxbin.libpath(), p) sys.path = sys.path[:-1] sys.path.append(libpath) break diff -r 5a3960e15d4a tools/pygrub/src/pygrub --- a/tools/pygrub/src/pygrub Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/pygrub/src/pygrub Wed Jan 17 10:35:34 2007 +0000 @@ -201,7 +201,9 @@ class Grub: enable_cursor(False) self.entry_win = curses.newwin(10, 74, 2, 1) self.text_win = curses.newwin(10, 70, 12, 5) - + curses.def_prog_mode() + + curses.reset_prog_mode() self.screen.clear() self.screen.refresh() diff -r 5a3960e15d4a tools/python/xen/xend/XendBootloader.py --- a/tools/python/xen/xend/XendBootloader.py Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/python/xen/xend/XendBootloader.py Wed Jan 17 13:04:39 2007 +0000 @@ -12,7 +12,7 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -import os, select, errno, stat +import os, select, errno, stat, signal import random import shlex from xen.xend import sxp @@ -21,12 +21,15 @@ from XendLogging import log from XendLogging import log from XendError import VmError -def bootloader(blexec, disk, quiet = False, blargs = '', kernel = '', +import pty, ptsname, termios, fcntl + +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.""" @@ -50,7 +53,33 @@ def bootloader(blexec, disk, quiet = Fal raise break - child = os.fork() + # 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. + + # Termios runes for very raw access to the pty master fds. + attr = [ 0, 0, termios.CS8 | termios.CREAD | termios.CLOCAL, + 0, 0, 0, [0] * 32 ] + + (m1, s1) = pty.openpty() + termios.tcsetattr(m1, termios.TCSANOW, attr) + fcntl.fcntl(m1, fcntl.F_SETFL, os.O_NDELAY); + os.close(s1) + 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: @@ -74,6 +103,11 @@ def bootloader(blexec, disk, quiet = Fal pass os._exit(1) + # record that this domain is bootloading + dom.bootloader_pid = child + + termios.tcsetattr(m2, termios.TCSANOW, attr) + fcntl.fcntl(m2, fcntl.F_SETFL, os.O_NDELAY); while True: try: r = os.open(fifo, os.O_RDONLY) @@ -82,16 +116,52 @@ def bootloader(blexec, disk, quiet = Fal continue break ret = "" + inbuf=""; outbuf=""; while True: - select.select([r], [], []) - s = os.read(r, 1024) - ret = ret + s - if len(s) == 0: - break - + sel = select.select([r, m1, m2], [m1, m2], []) + try: + if m1 in sel[0]: + s = os.read(m1, 1) + inbuf += s + if m2 in sel[1] and len(inbuf) != 0: + os.write(m2, inbuf[0]) + inbuf = inbuf[1:] + except OSError, e: + if e.errno == errno.EIO: + pass + try: + if m2 in sel[0]: + s = os.read(m2, 1) + outbuf += s + if m1 in sel[1] and len(outbuf) != 0: + os.write(m1, outbuf[0]) + outbuf = outbuf[1:] + except OSError, e: + if e.errno == errno.EIO: + pass + if r in sel[0]: + s = os.read(r, 1) + 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) 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!" @@ -103,3 +173,12 @@ def bootloader(blexec, disk, quiet = Fal 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) + + diff -r 5a3960e15d4a tools/python/xen/xend/XendDomain.py --- a/tools/python/xen/xend/XendDomain.py Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/python/xen/xend/XendDomain.py Wed Jan 17 12:32:55 2007 +0000 @@ -115,7 +115,6 @@ class XendDomain: dom0info['name'] = DOM0_NAME dom0 = XendDomainInfo.recreate(dom0info, True) - self._add_domain(dom0) except IndexError: raise XendError('Unable to find Domain 0') @@ -172,7 +171,6 @@ class XendDomain: if dom['domid'] != DOM0_ID: try: new_dom = XendDomainInfo.recreate(dom, False) - self._add_domain(new_dom) except Exception: log.exception("Failed to create reference to running " "domain id: %d" % dom['domid']) @@ -397,7 +395,6 @@ class XendDomain: elif domid not in self.domains and dom['dying'] != 1: try: new_dom = XendDomainInfo.recreate(dom, False) - self._add_domain(new_dom) except VmError: log.exception("Unable to recreate domain") try: @@ -416,10 +413,10 @@ class XendDomain: running_domids = [d['domid'] for d in running if d['dying'] != 1] for domid, dom in self.domains.items(): if domid not in running_domids and domid != DOM0_ID: - self._remove_domain(dom, domid) - - - def _add_domain(self, info): + self.remove_domain(dom, domid) + + + def add_domain(self, info): """Add a domain to the list of running domains @requires: Expects to be protected by the domains_lock. @@ -434,7 +431,7 @@ class XendDomain: if info.get_uuid() in self.managed_domains: self._managed_domain_register(info) - def _remove_domain(self, info, domid = None): + def remove_domain(self, info, domid = None): """Remove the domain from the list of running domains @requires: Expects to be protected by the domains_lock. @@ -473,7 +470,6 @@ class XendDomain: try: security.refresh_ssidref(config) dominfo = XendDomainInfo.restore(config) - self._add_domain(dominfo) return dominfo finally: self.domains_lock.release() @@ -848,7 +844,6 @@ class XendDomain: os.open(chkpath, os.O_RDONLY), dominfo, paused = start_paused) - self._add_domain(dominfo) os.unlink(chkpath) except OSError, ex: raise XendError("Failed to read stored checkpoint file") @@ -873,7 +868,6 @@ class XendDomain: self._refresh() dominfo = XendDomainInfo.create(config) - self._add_domain(dominfo) self.domain_sched_credit_set(dominfo.getDomid(), dominfo.getWeight(), dominfo.getCap()) @@ -893,7 +887,6 @@ class XendDomain: self._refresh() dominfo = XendDomainInfo.create_from_dict(config_dict) - self._add_domain(dominfo) self.domain_sched_credit_set(dominfo.getDomid(), dominfo.getWeight(), dominfo.getCap()) @@ -950,7 +943,6 @@ class XendDomain: POWER_STATE_NAMES[dominfo.state]) dominfo.start(is_managed = True) - self._add_domain(dominfo) finally: self.domains_lock.release() dominfo.waitForDevices() @@ -983,7 +975,7 @@ class XendDomain: (dominfo.getName(), dominfo.info.get('uuid'))) self._managed_domain_unregister(dominfo) - self._remove_domain(dominfo) + self.remove_domain(dominfo) XendDevices.destroy_device_state(dominfo) except Exception, ex: raise XendError(str(ex)) diff -r 5a3960e15d4a tools/python/xen/xend/XendDomainInfo.py --- a/tools/python/xen/xend/XendDomainInfo.py Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/python/xen/xend/XendDomainInfo.py Wed Jan 17 12:59:50 2007 +0000 @@ -41,7 +41,7 @@ from xen.xend import XendRoot, XendNode, from xen.xend import XendRoot, XendNode, XendConfig from xen.xend.XendConfig import scrub_password -from xen.xend.XendBootloader import bootloader +from xen.xend.XendBootloader import bootloader, bootloader_tidy from xen.xend.XendError import XendError, VmError from xen.xend.XendDevices import XendDevices from xen.xend.xenstore.xstransact import xstransact, complete @@ -120,7 +120,6 @@ def create_from_dict(config_dict): log.exception('Domain construction failed') vm.destroy() raise - return vm def recreate(info, priv): @@ -206,6 +205,11 @@ def recreate(info, priv): vm._registerWatches() vm.refreshShutdown(xeninfo) + + # register the domain in the list + from xen.xend import XendDomain + XendDomain.instance().add_domain(vm) + return vm @@ -1343,6 +1347,9 @@ class XendDomainInfo: # Set maximum number of vcpus in domain xc.domain_max_vcpus(self.domid, int(self.info['vcpus_number'])) + # register the domain in the list + from xen.xend import XendDomain + XendDomain.instance().add_domain(self) def _introduceDomain(self): assert self.domid is not None @@ -1440,6 +1447,7 @@ class XendDomainInfo: try: self.unwatchShutdown() self._releaseDevices() + bootloader_tidy(self) if self.image: try: @@ -1529,6 +1537,9 @@ class XendDomainInfo: self.info[state] = 0 except: log.exception("XendDomainInfo.destroy: xc.domain_destroy failed.") + + from xen.xend import XendDomain + XendDomain.instance().remove_domain(self) self.cleanupDomain() @@ -1615,7 +1626,7 @@ class XendDomainInfo: fn = BOOTLOADER_LOOPBACK_DEVICE try: - blcfg = bootloader(blexec, fn, True, + blcfg = bootloader(blexec, fn, self, False, bootloader_args, kernel, ramdisk, args) finally: if mounted: diff -r 5a3960e15d4a tools/python/xen/xm/create.py --- a/tools/python/xen/xm/create.py Wed Jan 17 10:35:33 2007 +0000 +++ b/tools/python/xen/xm/create.py Wed Jan 17 15:36:49 2007 +0000 @@ -24,6 +24,7 @@ import sys import sys import socket import re +import time import xmlrpclib from xen.xend import sxp @@ -705,26 +706,6 @@ def configure_hvm(config_image, vals): config_image.append([a, vals.__dict__[a]]) config_image.append(['vncpasswd', vals.vncpasswd]) -def run_bootloader(vals, config_image): - if not os.access(vals.bootloader, os.F_OK): - err("Bootloader '%s' does not exist" % vals.bootloader) - if not os.access(vals.bootloader, os.X_OK): - err("Bootloader '%s' isn't executable" % vals.bootloader) - if len(vals.disk) < 1: - err("No disks configured and boot loader requested") - (uname, dev, mode, backend) = vals.disk[0] - file = blkif.blkdev_uname_to_file(uname) - - if vals.bootentry: - warn("The bootentry option is deprecated. Use bootargs and pass " - "--entry= directly.") - vals.bootargs = "--entry=%s" %(vals.bootentry,) - - kernel = sxp.child_value(config_image, 'kernel') - ramdisk = sxp.child_value(config_image, 'ramdisk') - args = sxp.child_value(config_image, 'args') - return bootloader(vals.bootloader, file, not vals.console_autoconnect, - vals.bootargs, kernel, ramdisk, args) def make_config(vals): """Create the domain configuration. @@ -767,11 +748,6 @@ def make_config(vals): if vals.bootloader == "pygrub": vals.bootloader = osdep.pygrub_path - # if a kernel is specified, we're using the bootloader - # non-interactively, and need to let xend run it so we preserve the - # real kernel choice. - if not vals.kernel: - config_image = run_bootloader(vals, config_image) config.append(['bootloader', vals.bootloader]) if vals.bootargs: config.append(['bootloader_args', vals.bootargs]) @@ -1262,9 +1238,28 @@ def main(argv): if not create_security_check(config): raise security.ACMError('Security Configuration prevents domain from starting') else: + if opts.vals.console_autoconnect: + cpid = os.fork() + if cpid != 0: + for i in range(10): + # Catch failure of the create process + time.sleep(1) + (p, rv) = os.waitpid(cpid, os.WNOHANG) + if os.WIFEXITED(rv): + if os.WEXITSTATUS(rv) != 0: + sys.exit(os.WEXITSTATUS(rv)) + try: + # Acquire the console of the created dom + name = sxp.child_value(config, 'name', -1) + dom = server.xend.domain(name) + domid = int(sxp.child_value(dom, 'domid', '-1')) + console.execConsole(domid) + except: + pass + print("Could not start console\n"); + sys.exit(0) dom = make_domain(opts, config) - if opts.vals.console_autoconnect: - console.execConsole(dom) - + + if __name__ == '__main__': main(sys.argv)