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