zone-vnc-console revision 4046
4c221b0da1816acf2ca302b10092df059484468dvboxsync#!/usr/bin/python2.6
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
4c221b0da1816acf2ca302b10092df059484468dvboxsync#
4c221b0da1816acf2ca302b10092df059484468dvboxsync# Licensed under the Apache License, Version 2.0 (the "License"); you may
4c221b0da1816acf2ca302b10092df059484468dvboxsync# not use this file except in compliance with the License. You may obtain
4c221b0da1816acf2ca302b10092df059484468dvboxsync# a copy of the License at
4c221b0da1816acf2ca302b10092df059484468dvboxsync#
4c221b0da1816acf2ca302b10092df059484468dvboxsync# http://www.apache.org/licenses/LICENSE-2.0
4c221b0da1816acf2ca302b10092df059484468dvboxsync#
4c221b0da1816acf2ca302b10092df059484468dvboxsync# Unless required by applicable law or agreed to in writing, software
4c221b0da1816acf2ca302b10092df059484468dvboxsync# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4c221b0da1816acf2ca302b10092df059484468dvboxsync# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4c221b0da1816acf2ca302b10092df059484468dvboxsync# License for the specific language governing permissions and limitations
4c221b0da1816acf2ca302b10092df059484468dvboxsync# under the License.
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport errno
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport os
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport pwd
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport smf_include
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport subprocess
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport sys
4c221b0da1816acf2ca302b10092df059484468dvboxsyncimport time
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsyncfrom subprocess import CalledProcessError, check_call, Popen
4c221b0da1816acf2ca302b10092df059484468dvboxsyncfrom tempfile import mkstemp
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsyncGTF = "/usr/bin/gtf"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncSVCCFG = "/usr/sbin/svccfg"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncSVCPROP = "/usr/bin/svcprop"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncVNCSERVER = "/usr/bin/vncserver"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXRANDR = "/usr/bin/xrandr"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXSTARTUPHDR = "# WARNING: THIS FILE GENERATED BY SMF.\n" + \
4c221b0da1816acf2ca302b10092df059484468dvboxsync "# DO NOT EDIT THIS FILE. EDITS WILL BE LOST.\n"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXRESOURCES = "[[ -f ~/.Xresources ]] && /usr/bin/xrdb -merge ~/.Xresources\n"
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXTERM = "/usr/bin/xterm"
4c221b0da1816acf2ca302b10092df059484468dvboxsync# Borderless, Monospsce font, point size 14, white foreground on black
4c221b0da1816acf2ca302b10092df059484468dvboxsync# background are reasonable defaults.
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXTERMOPTS = ' -b 0 -fa Monospace -fs 14 -fg white -bg black -title ' + \
4c221b0da1816acf2ca302b10092df059484468dvboxsync '"Zone Console: $ZONENAME"'
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXWININFO = "/usr/bin/xwininfo"
4c221b0da1816acf2ca302b10092df059484468dvboxsync# Enclose command in comments to prevent xterm consuming zlogin opts
4c221b0da1816acf2ca302b10092df059484468dvboxsyncZLOGINOPTS = ' -e "/usr/bin/pfexec /usr/sbin/zlogin -C -E $ZONENAME"\n'
4c221b0da1816acf2ca302b10092df059484468dvboxsyncXSTARTUP = XSTARTUPHDR + XRESOURCES + XTERM + XTERMOPTS + ZLOGINOPTS
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsyncdef start():
4c221b0da1816acf2ca302b10092df059484468dvboxsync check_vncserver()
4c221b0da1816acf2ca302b10092df059484468dvboxsync homedir = os.environ.get('HOME')
4c221b0da1816acf2ca302b10092df059484468dvboxsync if not homedir:
4c221b0da1816acf2ca302b10092df059484468dvboxsync homedir = pwd.getpwuid(os.getuid()).pw_dir
4c221b0da1816acf2ca302b10092df059484468dvboxsync os.putenv("HOME", homedir)
4c221b0da1816acf2ca302b10092df059484468dvboxsync set_xstartup(homedir)
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync try:
4c221b0da1816acf2ca302b10092df059484468dvboxsync fmri = os.environ['SMF_FMRI']
4c221b0da1816acf2ca302b10092df059484468dvboxsync zonename = fmri.rsplit(':', 1)[1]
4c221b0da1816acf2ca302b10092df059484468dvboxsync os.putenv("ZONENAME", zonename)
4c221b0da1816acf2ca302b10092df059484468dvboxsync desktop_name = zonename + ' console'
4c221b0da1816acf2ca302b10092df059484468dvboxsync # NOTE: 'geometry' below is that which matches the size of standard
4c221b0da1816acf2ca302b10092df059484468dvboxsync # 80 character undecorated xterm window using font style specified in
4c221b0da1816acf2ca302b10092df059484468dvboxsync # XTERMOPTS. The geometry doesn't matter too much because the display
4c221b0da1816acf2ca302b10092df059484468dvboxsync # will be resized using xrandr once the xterm geometry is established.
4c221b0da1816acf2ca302b10092df059484468dvboxsync cmd = [VNCSERVER, "-name", desktop_name, "-SecurityTypes=None",
4c221b0da1816acf2ca302b10092df059484468dvboxsync "-geometry", "964x580", "-localhost", "-autokill"]
4c221b0da1816acf2ca302b10092df059484468dvboxsync vnc = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
4c221b0da1816acf2ca302b10092df059484468dvboxsync env=None)
4c221b0da1816acf2ca302b10092df059484468dvboxsync out, err = vnc.communicate()
4c221b0da1816acf2ca302b10092df059484468dvboxsync vncret = vnc.wait()
4c221b0da1816acf2ca302b10092df059484468dvboxsync if vncret != 0:
4c221b0da1816acf2ca302b10092df059484468dvboxsync print "Error starting VNC server: " + err
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_ERR_FATAL
4c221b0da1816acf2ca302b10092df059484468dvboxsync except Exception as e:
4c221b0da1816acf2ca302b10092df059484468dvboxsync print e
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_ERR_FATAL
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync output = err.splitlines()
4c221b0da1816acf2ca302b10092df059484468dvboxsync for line in output:
4c221b0da1816acf2ca302b10092df059484468dvboxsync if line.startswith("New '%s' desktop is" % desktop_name):
4c221b0da1816acf2ca302b10092df059484468dvboxsync display = line.rpartition(' ')[2]
4c221b0da1816acf2ca302b10092df059484468dvboxsync host, display_num = display.split(':', 1)
4c221b0da1816acf2ca302b10092df059484468dvboxsync # set host prop
4c221b0da1816acf2ca302b10092df059484468dvboxsync port = 5900 + int(display_num)
4c221b0da1816acf2ca302b10092df059484468dvboxsync print "VNC port: %d" % port
4c221b0da1816acf2ca302b10092df059484468dvboxsync # set port num prop
4c221b0da1816acf2ca302b10092df059484468dvboxsync cmd = [SVCCFG, '-s', fmri, 'setprop', 'vnc/port', '=', 'integer:',
4c221b0da1816acf2ca302b10092df059484468dvboxsync str(port)]
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync svccfg = subprocess.Popen(cmd, stdout=subprocess.PIPE,
4c221b0da1816acf2ca302b10092df059484468dvboxsync stderr=subprocess.PIPE)
4c221b0da1816acf2ca302b10092df059484468dvboxsync out, err = svccfg.communicate()
4c221b0da1816acf2ca302b10092df059484468dvboxsync retcode = svccfg.wait()
4c221b0da1816acf2ca302b10092df059484468dvboxsync if retcode != 0:
4c221b0da1816acf2ca302b10092df059484468dvboxsync print "Error updating 'vnc/port' property: " + err
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_ERR_FATAL
4c221b0da1816acf2ca302b10092df059484468dvboxsync resize_xserver(display, zonename)
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_OK
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsyncdef stop():
4c221b0da1816acf2ca302b10092df059484468dvboxsync try:
4c221b0da1816acf2ca302b10092df059484468dvboxsync # first kill the SMF contract
4c221b0da1816acf2ca302b10092df059484468dvboxsync check_call(["/usr/bin/pkill", "-c", sys.argv[2]])
4c221b0da1816acf2ca302b10092df059484468dvboxsync except CalledProcessError as cpe:
4c221b0da1816acf2ca302b10092df059484468dvboxsync # 1 is returncode if no SMF contract processes were matched,
4c221b0da1816acf2ca302b10092df059484468dvboxsync # meaning they have already terminated.
4c221b0da1816acf2ca302b10092df059484468dvboxsync if cpe.returncode != 1:
4c221b0da1816acf2ca302b10092df059484468dvboxsync print "failed to kill the SMF contract: %s" % cpe
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_ERR_FATAL
4c221b0da1816acf2ca302b10092df059484468dvboxsync
4c221b0da1816acf2ca302b10092df059484468dvboxsync try:
4c221b0da1816acf2ca302b10092df059484468dvboxsync fmri = os.environ['SMF_FMRI']
4c221b0da1816acf2ca302b10092df059484468dvboxsync # reset port num prop to initial zero value
4c221b0da1816acf2ca302b10092df059484468dvboxsync cmd = [SVCCFG, '-s', fmri, 'setprop', 'vnc/port', '=', 'integer:',
4c221b0da1816acf2ca302b10092df059484468dvboxsync '0']
4c221b0da1816acf2ca302b10092df059484468dvboxsync svccfg = subprocess.Popen(cmd, stdout=subprocess.PIPE,
4c221b0da1816acf2ca302b10092df059484468dvboxsync stderr=subprocess.PIPE,)
4c221b0da1816acf2ca302b10092df059484468dvboxsync out, err = svccfg.communicate()
4c221b0da1816acf2ca302b10092df059484468dvboxsync retcode = svccfg.wait()
4c221b0da1816acf2ca302b10092df059484468dvboxsync if retcode != 0:
4c221b0da1816acf2ca302b10092df059484468dvboxsync print "Error resetting 'vnc/port' property: " + err
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_ERR_FATAL
4c221b0da1816acf2ca302b10092df059484468dvboxsync except Exception as e:
4c221b0da1816acf2ca302b10092df059484468dvboxsync print e
4c221b0da1816acf2ca302b10092df059484468dvboxsync return smf_include.SMF_EXIT_ERR_FATAL
def check_vncserver():
if not os.path.exists(VNCSERVER):
print("VNC console service not available on this compute node. "
"%s is missing. Run 'pkg install x11/server/xvnc'"
% VNCSERVER)
return smf_include.SMF_EXIT_ERR_FATAL
if not os.path.exists(XTERM):
print("VNC console service not available on this compute node. "
"%s is missing. Run 'pkg install terminal/xterm'"
% XTERM)
return smf_include.SMF_EXIT_ERR_FATAL
def set_xstartup(homedir):
vncdir = os.path.join(homedir, '.vnc')
xstartup_path = os.path.join(vncdir, 'xstartup')
try:
os.mkdir(vncdir)
except OSError as ose:
if ose.errno != errno.EEXIST:
raise
# Always clobber xstartup
# stemp tuple = [fd, path]
stemp = mkstemp(dir=vncdir)
os.write(stemp[0], XSTARTUP)
os.close(stemp[0])
os.chmod(stemp[1], 0700)
os.rename(stemp[1], xstartup_path)
def resize_xserver(display, zonename):
""" Try to determine xterm window geometry and resize the Xvnc display
to match using XRANDR. Treat failure as non-fatal since an
incorrectly sized console is arguably better than none.
"""
class UninitializedWindowError(Exception):
pass
class UnmappedWindowError(Exception):
pass
def _get_window_geometry(display, windowname):
""" Find the xterm xwindow by name/title and extract its geometry
Returns: tuple of window [width, height]
Raises:
UninitializedWindowError if window not yet initialized
UnmappedWindowError if window is not viewable/mapped
"""
cmd = [XWININFO, '-d', display, '-name', windowname]
xwininfo = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = xwininfo.communicate()
retcode = xwininfo.wait()
if retcode != 0:
print "Error finding console xwindow info: " + err
raise UninitializedWindowError
width = None
height = None
mapped = False
for line in out.splitlines():
line = line.strip()
if line.startswith("Map State:"):
if line.split()[-1] != "IsViewable":
# Window is not mapped yet.
raise UnmappedWindowError
else:
mapped = True
if line.startswith("Width:"):
width = int(line.split()[1])
elif line.startswith("Height:"):
height = int(line.split()[1])
if width and height and mapped:
return [width, height]
else:
# What, no width and height???
print "No window geometry info returned by " + XWINFINFO
raise UnmappedWindowError
retries = 10
sleep = 1
uninit_count = 0
unmap_count = 0
width = 0
height = 0
while uninit_count < retries and unmap_count < retries:
try:
width, height = _get_window_geometry(display,
'Zone Console: ' + zonename)
print "Discovered xterm geometry: %d x %d" % (width, height)
break
except UninitializedWindowError:
if uninit_count < retries:
print "xterm window not initialized yet. Retrying in %ds" \
% sleep
uninit_count += 1
time.sleep(sleep)
continue
else:
print "xterm window is taking too long to initialize"
break
except UnmappedWindowError:
if unmap_count < retries:
print "Discovered xterm not mapped yet. Retrying in %ds" \
% sleep
unmap_count += 1
time.sleep(sleep)
continue
else:
print "Discovered xterm window is taking too long to map"
break
else:
print "Too many failed attempts to discover xterm window geometry"
return
# Generate a mode line for width and height, with a refresh of 60.0Hz
cmd = [GTF, str(width), str(height), '60.0', '-x']
gtf = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = gtf.communicate()
retcode = gtf.wait()
if retcode != 0:
print "Error creating new modeline for VNC display: " + err
return
for line in out.splitlines():
line = line.strip()
if line.startswith('Modeline'):
modeline = line.split('Modeline')[1]
print "New optimal modeline for Xvnc server: " + modeline
mode = modeline.split()
break
# Create a new mode for the Xvnc server using the modeline generated by gtf
cmd = [XRANDR, '-d', display, '--newmode']
cmd.extend(mode)
newmode = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = newmode.communicate()
retcode = newmode.wait()
if retcode != 0:
print "Error creating new xrandr modeline for VNC display: " + err
return
# Add the new mode to the default display output
modename = mode[0]
cmd = [XRANDR, '-d', display, '--addmode', 'default', modename]
addmode = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = addmode.communicate()
retcode = addmode.wait()
if retcode != 0:
print "Error adding new xrandr modeline for VNC display: " + err
return
# Activate the new mode on the default display output
cmd = [XRANDR, '-d', display, '--output', 'default', '--mode', modename]
addmode = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = addmode.communicate()
retcode = addmode.wait()
if retcode != 0:
print "Error setting new xrandr modeline for VNC display: " + err
return
if __name__ == "__main__":
os.putenv("LC_ALL", "C")
smf_include.smf_main()