diff --git a/tools/python/xen/util/utils.py b/tools/python/xen/util/utils.py index b6c7d2ae40..e13d29bb2d 100644 --- a/tools/python/xen/util/utils.py +++ b/tools/python/xen/util/utils.py @@ -1,6 +1,50 @@ import traceback import sys +import os def exception_string(e): (ty,v,tb) = sys.exc_info() return traceback.format_exception_only(ty,v) + +def daemonize(prog, args, stdin_tmpfile=None): + """Runs a program as a daemon with the list of arguments. Returns the PID + of the daemonized program, or returns 0 on error. + """ + r, w = os.pipe() + pid = os.fork() + + if pid == 0: + os.close(r) + w = os.fdopen(w, 'w') + os.setsid() + try: + pid2 = os.fork() + except: + pid2 = None + if pid2 == 0: + os.chdir("/") + null_fd = os.open("/dev/null", os.O_RDWR) + if stdin_tmpfile is not None: + os.dup2(stdin_tmpfile.fileno(), 0) + else: + os.dup2(null_fd, 0) + os.dup2(null_fd, 1) + os.dup2(null_fd, 2) + for fd in range(3, 256): + try: + os.close(fd) + except: + pass + os.execvp(prog, args) + os._exit(1) + else: + w.write(str(pid2 or 0)) + w.close() + os._exit(0) + os.close(w) + r = os.fdopen(r) + daemon_pid = int(r.read()) + r.close() + os.waitpid(pid, 0) + return daemon_pid + diff --git a/tools/python/xen/xm/console.py b/tools/python/xen/xm/console.py index f971644fe9..8abcb1d9a6 100644 --- a/tools/python/xen/xm/console.py +++ b/tools/python/xen/xm/console.py @@ -15,10 +15,71 @@ # Copyright (C) 2005 XenSource Ltd #============================================================================ +import xen.util.auxbin +import xen.lowlevel.xs +import os +import sys +import signal +from xen.util import utils XENCONSOLE = "xenconsole" -import xen.util.auxbin - def execConsole(domid): xen.util.auxbin.execute(XENCONSOLE, [str(domid)]) + + +class OurXenstoreConnection: + def __init__(self): + self.handle = xen.lowlevel.xs.xs() + def read_eventually(self, path): + watch = None + trans = None + try: + signal.alarm(10) + watch = self.handle.watch(path, None) + while True: + result = self.handle.read('0', path) + if result is not None: + return result + self.handle.read_watch() + self.handle.unwatch(path, watch) + signal.alarm(0) + except: + signal.alarm(0) + if watch is not None: self.handle.unwatch(path, watch) + raise + def read_maybe(self, path): + return self.handle.read('0', path) + +def runVncViewer(domid, do_autopass, do_daemonize=False): + xs = OurXenstoreConnection() + d = '/local/domain/%d/' % domid + vnc_port = xs.read_eventually(d + 'console/vnc-port') + vfb_backend = xs.read_maybe(d + 'device/vfb/0/backend') + vnc_listen = None + vnc_password = None + vnc_password_tmpfile = None + cmdl = ['vncviewer'] + if vfb_backend is not None: + vnc_listen = xs.read_maybe(vfb_backend + '/vnclisten') + if do_autopass: + vnc_password = xs.read_maybe(vfb_backend + '/vncpasswd') + if vnc_password is not None: + cmdl.append('-autopass') + vnc_password_tmpfile = os.tmpfile() + print >>vnc_password_tmpfile, vnc_password + vnc_password_tmpfile.seek(0) + vnc_password_tmpfile.flush() + if vnc_listen is None: + vnc_listen = 'localhost' + cmdl.append('%s:%d' % (vnc_listen, int(vnc_port) - 5900)) + if do_daemonize: + pid = utils.daemonize('vncviewer', cmdl, vnc_password_tmpfile) + if pid == 0: + puts >>sys.stderr, 'failed to invoke vncviewer' + os._exit(-1) + else: + print 'invoking ', ' '.join(cmdl) + if vnc_password_tmpfile is not None: + os.dup2(vnc_password_tmpfile.fileno(), 0) + os.execvp('vncviewer', cmdl) diff --git a/tools/python/xen/xm/create.py b/tools/python/xen/xm/create.py index 3652d8c775..9d00a81226 100644 --- a/tools/python/xen/xm/create.py +++ b/tools/python/xen/xm/create.py @@ -36,10 +36,12 @@ from xen.util import blkif from xen.util import vscsi_util import xen.util.xsm.xsm as security from xen.xm.main import serverType, SERVER_XEN_API, get_single_vm +from xen.util import utils from xen.xm.opts import * from main import server +from main import domain_name_to_domid import console @@ -118,6 +120,14 @@ gopts.opt('console_autoconnect', short='c', fn=set_true, default=0, use="Connect to the console after the domain is created.") +gopts.opt('vncviewer', + fn=set_true, default=0, + use="Connect to the VNC display after the domain is created.") + +gopts.opt('vncviewer-autopass', + fn=set_true, default=0, + use="Pass VNC password to viewer via stdin and -autopass.") + gopts.var('vncpasswd', val='NAME', fn=set_value, default=None, use="Password for VNC console on HVM domain.") @@ -128,7 +138,7 @@ gopts.var('vncviewer', val='no|yes', "The address of the vncviewer is passed to the domain on the " "kernel command line using 'VNC_SERVER=:'. The port " "used by vnc is 5500 + DISPLAY. A display value with a free port " - "is chosen if possible.\nOnly valid when vnc=1.") + "is chosen if possible.\nOnly valid when vnc=1.\nDEPRECATED") gopts.var('vncconsole', val='no|yes', fn=set_bool, default=None, @@ -1108,44 +1118,6 @@ def choose_vnc_display(): return None vncpid = None -def daemonize(prog, args): - """Runs a program as a daemon with the list of arguments. Returns the PID - of the daemonized program, or returns 0 on error. - """ - r, w = os.pipe() - pid = os.fork() - - if pid == 0: - os.close(r) - w = os.fdopen(w, 'w') - os.setsid() - try: - pid2 = os.fork() - except: - pid2 = None - if pid2 == 0: - os.chdir("/") - for fd in range(0, 256): - try: - os.close(fd) - except: - pass - os.open("/dev/null", os.O_RDWR) - os.dup2(0, 1) - os.dup2(0, 2) - os.execvp(prog, args) - os._exit(1) - else: - w.write(str(pid2 or 0)) - w.close() - os._exit(0) - os.close(w) - r = os.fdopen(r) - daemon_pid = int(r.read()) - r.close() - os.waitpid(pid, 0) - return daemon_pid - def spawn_vnc(display): """Spawns a vncviewer that listens on the specified display. On success, returns the port that the vncviewer is listening on and sets the global @@ -1154,7 +1126,7 @@ def spawn_vnc(display): vncargs = (["vncviewer", "-log", "*:stdout:0", "-listen", "%d" % (VNC_BASE_PORT + display) ]) global vncpid - vncpid = daemonize("vncviewer", vncargs) + vncpid = utils.daemonize("vncviewer", vncargs) if vncpid == 0: return 0 @@ -1362,6 +1334,11 @@ def main(argv): elif not opts.is_xml: dom = make_domain(opts, config) + if opts.vals.vncviewer: + domid = domain_name_to_domid(sxp.child_value(config, 'name', -1)) + vncviewer_autopass = getattr(opts.vals,'vncviewer-autopass', False) + console.runVncViewer(domid, vncviewer_autopass, True) + def do_console(domain_name): cpid = os.fork() if cpid != 0: @@ -1373,13 +1350,7 @@ def do_console(domain_name): if os.WEXITSTATUS(rv) != 0: sys.exit(os.WEXITSTATUS(rv)) try: - # Acquire the console of the created dom - if serverType == SERVER_XEN_API: - domid = server.xenapi.VM.get_domid( - get_single_vm(domain_name)) - else: - dom = server.xend.domain(domain_name) - domid = int(sxp.child_value(dom, 'domid', '-1')) + domid = domain_name_to_domid(domain_name) console.execConsole(domid) except: pass diff --git a/tools/python/xen/xm/main.py b/tools/python/xen/xm/main.py index b7383d2190..36268d66a2 100644 --- a/tools/python/xen/xm/main.py +++ b/tools/python/xen/xm/main.py @@ -64,6 +64,9 @@ import inspect from xen.xend import XendOptions xoptions = XendOptions.instance() +import signal +signal.signal(signal.SIGINT, signal.SIG_DFL) + # getopt.gnu_getopt is better, but only exists in Python 2.3+. Use # getopt.getopt if gnu_getopt is not available. This will mean that options # may only be specified before positional arguments. @@ -97,6 +100,8 @@ SUBCOMMAND_HELP = { 'console' : ('[-q|--quiet] ', 'Attach to \'s console.'), + 'vncviewer' : ('[--[vncviewer-]autopass] ', + 'Attach to \'s VNC server.'), 'create' : (' [options] [vars]', 'Create a domain based on .'), 'destroy' : ('', @@ -243,6 +248,10 @@ SUBCOMMAND_OPTIONS = { 'console': ( ('-q', '--quiet', 'Do not print an error message if the domain does not exist'), ), + 'vncviewer': ( + ('', '--autopass', 'Pass VNC password to viewer via stdin and -autopass'), + ('', '--vncviewer-autopass', '(consistency alias for --autopass)'), + ), 'dmesg': ( ('-c', '--clear', 'Clear dmesg buffer as well as printing it'), ), @@ -260,6 +269,8 @@ SUBCOMMAND_OPTIONS = { 'start': ( ('-p', '--paused', 'Do not unpause domain after starting it'), ('-c', '--console_autoconnect', 'Connect to the console after the domain is created'), + ('', '--vncviewer', 'Connect to display via VNC after the domain is created'), + ('', '--vncviewer-autopass', 'Pass VNC password to viewer via stdin and -autopass'), ), 'resume': ( ('-p', '--paused', 'Do not unpause domain after resuming it'), @@ -277,6 +288,7 @@ SUBCOMMAND_OPTIONS = { common_commands = [ "console", + "vncviewer", "create", "new", "delete", @@ -304,6 +316,7 @@ common_commands = [ domain_commands = [ "console", + "vncviewer", "create", "new", "delete", @@ -1185,14 +1198,20 @@ def xm_start(args): paused = False console_autoconnect = False + vncviewer = False + vncviewer_autopass = False try: - (options, params) = getopt.gnu_getopt(args, 'cp', ['console_autoconnect','paused']) + (options, params) = getopt.gnu_getopt(args, 'cp', ['console_autoconnect','paused','vncviewer','vncviewer-autopass']) for (k, v) in options: if k in ('-p', '--paused'): paused = True if k in ('-c', '--console_autoconnect'): console_autoconnect = True + if k in ('--vncviewer'): + vncviewer = True + if k in ('--vncviewer-autopass'): + vncviewer_autopass = True if len(params) != 1: raise OptionError("Expects 1 argument") @@ -1205,6 +1224,9 @@ def xm_start(args): if console_autoconnect: start_do_console(dom) + if console_autoconnect: + console.runVncViewer(domid, vncviewer_autopass, True) + try: if serverType == SERVER_XEN_API: server.xenapi.VM.start(get_single_vm(dom), paused) @@ -1783,6 +1805,40 @@ def xm_console(args): console.execConsole(domid) +def domain_name_to_domid(domain_name): + if serverType == SERVER_XEN_API: + domid = server.xenapi.VM.get_domid( + get_single_vm(domain_name)) + else: + dom = server.xend.domain(domain_name) + domid = int(sxp.child_value(dom, 'domid', '-1')) + return domid + +def xm_vncviewer(args): + autopass = False; + + try: + (options, params) = getopt.gnu_getopt(args, '', ['autopass','vncviewer-autopass']) + except getopt.GetoptError, opterr: + err(opterr) + usage('vncviewer') + + for (k, v) in options: + if k in ['--autopass','--vncviewer-autopass']: + autopass = True + else: + assert False + + if len(params) != 1: + err('No domain given (or several parameters specified)') + usage('vncviewer') + + dom = params[0] + domid = domain_name_to_domid(dom) + + console.runVncViewer(domid, autopass) + + def xm_uptime(args): short_mode = 0 @@ -2617,6 +2673,7 @@ commands = { "event-monitor": xm_event_monitor, # console commands "console": xm_console, + "vncviewer": xm_vncviewer, # xenstat commands "top": xm_top, # domain commands