--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/visual-panels/core/src/java/vpanels/client/com/oracle/solaris/vp/client/swing/App.java Thu May 24 04:16:47 2012 -0400
@@ -0,0 +1,744 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ */
+
+package com.oracle.solaris.vp.client.swing;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.logging.*;
+import com.oracle.solaris.afunix.*;
+import com.oracle.solaris.vp.client.common.*;
+import com.oracle.solaris.vp.panel.common.*;
+import com.oracle.solaris.vp.panel.common.action.*;
+import com.oracle.solaris.vp.panel.common.control.*;
+import com.oracle.solaris.vp.util.cli.*;
+import com.oracle.solaris.vp.util.misc.*;
+import com.oracle.solaris.vp.util.misc.finder.Finder;
+
+/**
+ * The {@code App} class is a thread that listens for AF_UNIX
+ * connections from other processes. Multiple addresses of panels to
+ * display, one per newline-terminated lines, may be sent through these
+ * connections.
+ */
+public class App extends Thread implements AppConstants {
+ //
+ // Inner classes
+ //
+
+ public static class CommandLineOptionsBean {
+ //
+ // Instance data
+ //
+
+ private String host = RadLoginManager.LOCAL_HOST;
+ private String user = RadLoginManager.LOCAL_USER;
+ private String role;
+ private String zone;
+ private String zoneUser;
+ private String zoneRole;
+ private String address = null;
+ private boolean noRemote;
+ private HelpFormatter help;
+ private Properties properties = new Properties();
+ private boolean inParam = false;
+
+ //
+ // Constructors
+ //
+
+ public CommandLineOptionsBean(HelpFormatter help) {
+ this.help = help;
+ }
+
+ //
+ // CommandLineOptionsBean methods
+ //
+
+ public String getAddress() {
+ return address;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public boolean getNoremote() {
+ return noRemote;
+ }
+
+ public Properties getProperties() {
+ return properties;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public String getZone() {
+ return zone;
+ }
+
+ public String getZonerole() {
+ return zoneRole;
+ }
+
+ public String getZoneuser() {
+ return zoneUser;
+ }
+
+ public void setAddress(String address) {
+ boolean absolute = address.startsWith("/");
+ if (this.address == null) {
+ if (!absolute) {
+ // Assume non-relative path is a standalone shortcut
+ address = String.format("/%s/%s",
+ Control.encode(AppRootControl.ID, null), address);
+ }
+ this.address = address;
+ } else {
+ /*
+ * If the address has already been set, we accumulate
+ * additional operands as query parameters or subsequent
+ * path elements.
+ */
+ if (absolute) {
+ this.address = this.address + address;
+ inParam = false;
+ } else {
+ try {
+ /*
+ * Ideally we'd use Control.encode, but mapping
+ * from our input to what it requires would be
+ * much less straightforward than what follows.
+ */
+ String[] parts = address.split("=", 2);
+ StringBuilder b = new StringBuilder(this.address);
+ b.append(inParam ? "&" : "?");
+ b.append(URLEncoder.encode(parts[0], Control.ENCODING));
+ b.append('=');
+ if (parts.length > 1)
+ b.append(URLEncoder.encode(parts[1],
+ Control.ENCODING));
+ this.address = b.toString();
+ inParam = true;
+ } catch (UnsupportedEncodingException ex) {
+ }
+ }
+ }
+ }
+
+ public void setHelp() {
+ System.out.println(help.getHelp());
+ System.exit(0);
+ }
+
+ public void setHost(String host) {
+ this.host = host;
+ }
+
+ public void setNoremote() {
+ noRemote = true;
+ }
+
+ public void setProperty(String property) {
+ String value;
+ int index = property.indexOf("=");
+ if (index == -1) {
+ value = "";
+ } else {
+ value = property.substring(index + 1);
+ property = property.substring(0, index - 1);
+ }
+
+ properties.setProperty(property, value);
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public void setVersion() {
+ showVersion();
+ System.exit(0);
+ }
+
+ public void setZone(String zone) {
+ this.zone = zone;
+ }
+
+ public void setZonerole(String zoneRole) {
+ this.zoneRole = zoneRole;
+ }
+
+ public void setZoneuser(String zoneUser) {
+ this.zoneUser = zoneUser;
+ }
+ }
+
+ private static class InstanceInfo implements Serializable {
+ //
+ // Instance data
+ //
+
+ public String address;
+ public Properties properties;
+ public String host;
+ public String user;
+ public String role;
+ public String zone;
+ public String zoneUser;
+ public String zoneRole;
+
+ //
+ // Constructors
+ //
+
+ public InstanceInfo(String address, Properties properties, String host,
+ String user, String role, String zone, String zoneUser,
+ String zoneRole) {
+
+ this.address = address;
+ this.properties = properties;
+ this.host = host;
+ this.user = user;
+ this.role = role;
+ this.zone = zone;
+ this.zoneUser = zoneUser;
+ this.zoneRole = zoneRole;
+ }
+
+ //
+ // Object methods
+ //
+
+ @Override
+ public String toString() {
+ return "address = " + quote(address) +
+ ", host = " + quote(host) +
+ ", user = " + quote(user) +
+ ", role = " + quote(role) +
+ ", zone = " + quote(zone) +
+ ", zoneUser = " + quote(zoneUser) +
+ ", zoneRole = " + quote(zoneRole);
+ }
+
+ //
+ // Static methods
+ //
+
+ private static String quote(String str) {
+ if (str == null) {
+ return "(null)";
+ }
+ return "\"" + str + "\"";
+ }
+ }
+
+ //
+ // Static data
+ //
+
+ private static final String ARG_NONOPT_ADDRESS = "address";
+ private static final String ARG_SHORT_HOST = "h";
+ private static final String ARG_LONG_HOST = "host";
+ private static final String ARG_SHORT_NOREMOTE = "n";
+ private static final String ARG_LONG_NOREMOTE = "no-remote";
+ private static final String ARG_SHORT_ROLE = "r";
+ private static final String ARG_LONG_ROLE = "role";
+ private static final String ARG_SHORT_USER = "u";
+ private static final String ARG_LONG_USER = "user";
+ private static final String ARG_SHORT_VERSION = "v";
+ private static final String ARG_LONG_VERSION = "version";
+ private static final String ARG_SHORT_ZONE = "z";
+ private static final String ARG_LONG_ZONE = "zone";
+ private static final String ARG_SHORT_ZONEUSER = "U";
+ private static final String ARG_LONG_ZONEUSER = "zoneuser";
+ private static final String ARG_SHORT_ZONEROLE = "R";
+ private static final String ARG_LONG_ZONEROLE = "zonerole";
+ private static final String ARG_SHORT_HELP = "?";
+ private static final String ARG_LONG_HELP = "help";
+ private static final String ARG_SHORT_PROPERTY = "D";
+ private static final String ARG_LONG_PROPERTY = "property";
+
+ private static final String COMMAND_NAME = "vp";
+ private static final String COMMAND_DESC =
+ Finder.getString("cli.description");
+
+ private static OptionChoiceGroup options;
+ static {
+ OptionElement addressOption = new NoOptOptionElement(
+ true, ARG_NONOPT_ADDRESS, Finder.getString("cli.arg.address"), -1);
+
+ OptionElement propOption = new OptionElement(ARG_SHORT_PROPERTY,
+ ARG_LONG_PROPERTY, false, "property", "");
+ propOption.setDocumented(false);
+ propOption.setUseLimit(-1);
+
+ OptionElement hostOption = new OptionElement(ARG_SHORT_HOST,
+ ARG_LONG_HOST, false, "host", Finder.getString("cli.arg.host"));
+
+ OptionElement noRemoteOption = new OptionElement(ARG_SHORT_NOREMOTE,
+ ARG_LONG_NOREMOTE, false, Finder.getString("cli.arg.noremote"));
+
+ OptionElement userOption = new OptionElement(ARG_SHORT_USER,
+ ARG_LONG_USER, false, "user", Finder.getString("cli.arg.user"));
+
+ OptionElement roleOption = new OptionElement(ARG_SHORT_ROLE,
+ ARG_LONG_ROLE, false, "role", Finder.getString("cli.arg.role"));
+
+ OptionElement zoneOption = new OptionElement(ARG_SHORT_ZONE,
+ ARG_LONG_ZONE, false, "zone", Finder.getString("cli.arg.zone"));
+
+ OptionElement zoneUserOption = new OptionElement(ARG_SHORT_ZONEUSER,
+ ARG_LONG_ZONEUSER, false, "zoneuser",
+ Finder.getString("cli.arg.zoneuser"));
+
+ OptionElement zoneRoleOption = new OptionElement(ARG_SHORT_ZONEROLE,
+ ARG_LONG_ZONEROLE, false, "zonerole",
+ Finder.getString("cli.arg.zonerole"));
+
+ OptionElement versionOption = new OptionElement(ARG_SHORT_VERSION,
+ ARG_LONG_VERSION, false, Finder.getString("cli.arg.version"));
+
+ OptionElement helpOption = new OptionElement(ARG_SHORT_HELP,
+ ARG_LONG_HELP, false, Finder.getString("cli.arg.help"));
+
+ OptionListGroup mainGroup = new OptionListGroup(false,
+ propOption, hostOption, userOption, roleOption, zoneOption,
+ zoneUserOption, zoneRoleOption, noRemoteOption, addressOption);
+
+ options = new OptionChoiceGroup(true, mainGroup, versionOption,
+ helpOption);
+ }
+
+ public static final String VP_USER_DIR =
+ System.getProperty("user.home") + File.separator + ".vp";
+
+ static {
+ // Set up client logging
+ String fileName = VP_USER_DIR + File.separator + "log";
+
+ // Root Logger
+ Logger log = Logger.getLogger("");
+
+ try {
+ File parent = new File(fileName).getParentFile();
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ throw new IOException(
+ "could not create directory: " +
+ parent.getAbsolutePath());
+ }
+ }
+
+ Handler fHandler = new FileHandler(fileName);
+ fHandler.setFormatter(new SimpleFormatter());
+ log.addHandler(fHandler);
+
+ // Don't output to the console by default
+ for (Handler cHandler : log.getHandlers()) {
+ if (cHandler instanceof ConsoleHandler) {
+ log.removeHandler(cHandler);
+ }
+ }
+ } catch (IOException e) {
+ Logger.getLogger(App.class.getName()).log(Level.WARNING,
+ "unable to create log file: " + fileName);
+ }
+ }
+
+ public static final File TRUSTSTORE_FILE =
+ new File(VP_USER_DIR, "truststore");
+
+ public static final File VP_UNIX;
+ static {
+ String hostName = NetUtil.getHostName();
+ if (hostName == null) {
+ hostName = "localhost";
+ }
+
+ // May be null or malformed if not an X client
+ String display = System.getenv("DISPLAY");
+
+ if (display != null && !display.matches(".*:\\d+(\\.\\d)?$")) {
+ String error = "invalid DISPLAY environment variable: " + display;
+ Logger.getLogger(App.class.getName()).log(Level.WARNING, error);
+ display = null;
+ }
+
+ if (display == null) {
+ display = ":0.0";
+ } else {
+ if (display.matches(".*:\\d+$")) {
+ display += ".0";
+ }
+
+ if (display.startsWith(hostName)) {
+ display = display.substring(hostName.length());
+ }
+ }
+
+ String name = String.format(".unix-%s-%s", hostName, display);
+
+ VP_UNIX = new File(VP_USER_DIR, name);
+ }
+
+ public static final String VP_USER_PREFS =
+ VP_USER_DIR + File.separator + "vp.init";
+
+ private static final Preferences prefs = new Preferences(VP_USER_PREFS);
+
+ //
+ // Instance data
+ //
+
+ private UnixSocketServer server;
+ private boolean closing;
+ private final List<AppInstance> instances = new ArrayList<AppInstance>();
+ private ConnectionManager connManager = new ConnectionManager();
+ private LoginHistoryManager loginHistoryManager =
+ new LoginHistoryManager(connManager, new File(VP_USER_DIR, "history"));
+
+ //
+ // Constructors
+ //
+
+ public App() {
+ addVersionToThreadName(this);
+ }
+
+ //
+ // Runnable methods
+ //
+
+ @Override
+ public void run() {
+ Logger log = Logger.getLogger(getClass().getName());
+
+ // Start singleton application instance...
+ try {
+ server = UnixDomainSocket.bind(VP_UNIX, 0600);
+
+ while (true) {
+ final UnixSocket socket = server.accept();
+ String peerUser = socket.getPeerUser();
+
+ if (!RadLoginManager.LOCAL_USER.equals(peerUser)) {
+ String error = String.format(
+ "user %s attempted to connect as %s", peerUser,
+ RadLoginManager.LOCAL_USER);
+ log.log(Level.WARNING, error);
+ } else {
+ // Spawn on a separate thread so as not to block other
+ // incoming to conections -- not so much because we
+ // anticipate a lot of traffic, but because we can't
+ // guarantee that some poorly-behaved client won't keep its
+ // connection open indefinitely.
+ new Thread() {
+ @Override
+ public void run() {
+ readFully(socket);
+ }
+ }.start();
+ }
+ }
+ } catch (AFUNIXNotSupportedException e) {
+ // Bummer
+ return;
+ } catch (IOException e) {
+ if (!closing) {
+ // Major bummer
+ log.log(Level.SEVERE,
+ "error communicating via incoming AF_UNIX connection", e);
+ }
+ return;
+ }
+ }
+
+ //
+ // App methods
+ //
+
+ public void abortableExit() throws ActionAbortedException {
+ synchronized (instances) {
+ for (int i = instances.size() - 1; i >= 0; i--) {
+ // Throws ActionAbortedException
+ instances.get(i).closeInstance(false);
+ }
+ }
+ exit();
+ }
+
+ public void exit(int exitCode) {
+ try {
+ if (server != null) {
+ closing = true;
+ server.close();
+ }
+ } catch (IOException ignore) {
+ }
+
+ try {
+ prefs.store();
+ } catch (IOException e) {
+ Logger.getLogger(getClass().getName()).log(Level.WARNING,
+ "could not write preferences", e);
+ }
+
+ System.exit(exitCode);
+ }
+
+ public void exit() {
+ exit(0);
+ }
+
+ private void exitIfNoIntances(int exitCode) {
+ if (instances.isEmpty()) {
+ exit(exitCode);
+ }
+ }
+
+ public ConnectionManager getConnectionManager() {
+ return connManager;
+ }
+
+ public LoginHistoryManager getLoginHistoryManager() {
+ return loginHistoryManager;
+ }
+
+ protected Preferences getPreferences() {
+ return prefs;
+ }
+
+ protected void instanceClosed(AppInstance instance) {
+ synchronized (instances) {
+ if (instances.remove(instance)) {
+ exitIfNoIntances(0);
+ }
+ }
+ }
+
+ protected void instanceCreated(AppInstance instance) {
+ synchronized (instances) {
+ instances.add(instance);
+ }
+ }
+
+ public void newInstance(InstanceInfo info) {
+ AppInstance instance = null;
+ boolean success = false;
+
+ try {
+ LoginProperty<String> host =
+ new LoginProperty<String>(info.host, false);
+ host.setEditableOnError(true);
+
+ LoginProperty<String> user =
+ new LoginProperty<String>(info.user, false);
+ user.setEditableOnError(true);
+
+ LoginProperty<String> role =
+ new LoginProperty<String>(info.role, false);
+ role.setEditableOnError(true);
+
+ LoginProperty<String> zone =
+ new LoginProperty<String>(info.zone, false);
+ zone.setEditableOnError(true);
+
+ LoginProperty<String> zoneUser =
+ new LoginProperty<String>(info.zoneUser, false);
+ zoneUser.setEditableOnError(true);
+
+ LoginProperty<String> zoneRole =
+ new LoginProperty<String>(info.zoneRole, false);
+ zoneRole.setEditableOnError(true);
+
+ boolean zonePromptVal = zone.getValue() != null ||
+ zoneUser.getValue() != null || zoneRole.getValue() != null;
+ LoginProperty<Boolean> zonePrompt =
+ new LoginProperty<Boolean>(zonePromptVal, false);
+
+ LoginRequest request = new LoginRequest(host, user, role,
+ zonePrompt, zone, zoneUser, zoneRole);
+
+ instance = new AppInstance(this, info.properties, request);
+
+ SimpleNavigable[] path = Navigator.toArray(info.address);
+ instance.getNavigator().goToAsyncAndWait(true, null, path);
+ success = true;
+
+ // User is aware of this because he explicitly caused it
+ } catch (ActionAbortedException e) {
+
+ // User has been advised of this by RadLoginManager
+ } catch (ActionFailedException e) {
+
+ // User has been advised of this by SwingNavigator
+ } catch (NavigationException e) {
+
+ // Unexpected error - write to log
+ } catch (RuntimeException e) {
+ Logger log = Logger.getLogger(getClass().getName());
+ log.log(Level.SEVERE, "could not launch: " + info, e);
+
+ // Unexpected error - write to log
+ } catch (Error e) {
+ Logger log = Logger.getLogger(getClass().getName());
+ log.log(Level.SEVERE, "could not launch: " + info, e);
+
+ } finally {
+ if (!success && instance != null &&
+ // Partial navigation failures aren't deal-breakers
+ !NavigationUtil.isPanelStarted(instance.getNavigator())) {
+
+ instance.close();
+ }
+ }
+ }
+
+ public void readFully(UnixSocket socket) {
+ ObjectInputStream in = null;
+ Logger log = Logger.getLogger(getClass().getName());
+
+ try {
+ in = new ObjectInputStream(socket.getInputStream());
+ InstanceInfo info = (InstanceInfo)in.readObject();
+ newInstance(info);
+ } catch (ClassNotFoundException e) {
+ log.log(Level.SEVERE,
+ "unexpected or invalid object read from AF_UNIX connection", e);
+
+ } catch (IOException e) {
+ log.log(Level.SEVERE, "I/O error", e);
+
+ } finally {
+ IOUtil.closeIgnore(in);
+ }
+ }
+
+ //
+ // Static methods
+ //
+
+ private static void addVersionToThreadName(Thread thread) {
+ // Add useful information to a thread dump
+ String name = String.format(
+ "%s (app version %s)", thread.getName(), VERSION);
+
+ thread.setName(name);
+ }
+
+ public static void showVersion() {
+ System.out.println(Finder.getString("cli.error.version", VERSION));
+ }
+
+ public static void main(String args[]) {
+ addVersionToThreadName(Thread.currentThread());
+
+ CommandLineParser parser = new PosixCommandLineParser();
+ UsageFormatter usage = new UsageFormatter(
+ COMMAND_NAME, options, parser.getOptionFormatter());
+ HelpFormatter help = new HelpFormatter(COMMAND_DESC, usage);
+ CommandLineOptionsBean bean = new CommandLineOptionsBean(help);
+
+ if (System.getProperty("vpanels.debug.version") != null) {
+ showVersion();
+ }
+
+ try {
+ // Populate bean
+ CommandLineProcessor.process(args, options, parser, bean);
+ }
+
+ catch (Exception e) {
+ CommandUtil.exit(e, usage);
+ }
+
+ InstanceInfo info = new InstanceInfo(bean.getAddress(),
+ bean.getProperties(), bean.getHost(), bean.getUser(),
+ bean.getRole(), bean.getZone(), bean.getZoneuser(),
+ bean.getZonerole());
+
+ if (!bean.getNoremote()) {
+ // Check for running instance
+ try {
+ UnixSocket socket = UnixDomainSocket.connect(VP_UNIX);
+
+ // Send arguments to running instance...
+ ObjectOutputStream out = new ObjectOutputStream(
+ socket.getOutputStream());
+
+ out.writeObject(info);
+ out.close();
+ System.exit(0);
+ } catch (AFUNIXNotSupportedException e) {
+ // AF_UNIX sockets are not supported on this platform
+ } catch (IOException e) {
+ // We are the only running instance
+ }
+ }
+
+ try {
+ URL policy = Finder.getResource("panel.policy");
+ if (policy == null) {
+ throw new IOException("panel.policy not found");
+ }
+ PanelClassLoader.loadPermissions(policy);
+ } catch (IOException e) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE,
+ "unable to read panel.policy", e);
+ String message = Finder.getString("init.error.security.io");
+ System.err.println(message);
+ System.exit(1);
+ } catch (PermissionParseException e) {
+ Logger.getLogger(App.class.getName()).log(Level.SEVERE,
+ "unable to parse panel.policy", e);
+ String message = Finder.getString("init.error.security.parse");
+ System.err.println(message);
+ System.exit(1);
+ }
+
+ System.setSecurityManager(new SecurityManager());
+
+ App app = new App();
+ app.newInstance(info);
+ app.exitIfNoIntances(1);
+
+ if (!bean.getNoremote()) {
+ // Start daemon thread listening for connections
+ app.start();
+ }
+ }
+}