0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans;
0043:
0044: import java.io.DataInputStream;
0045: import java.io.DataOutputStream;
0046: import java.io.EOFException;
0047: import java.io.File;
0048: import java.io.FileInputStream;
0049: import java.io.FileOutputStream;
0050: import java.io.IOException;
0051: import java.io.InputStream;
0052: import java.io.InterruptedIOException;
0053: import java.io.OutputStream;
0054: import java.io.PrintWriter;
0055: import java.lang.reflect.InvocationTargetException;
0056: import java.lang.reflect.Method;
0057: import java.net.InetAddress;
0058: import java.net.ServerSocket;
0059: import java.net.Socket;
0060: import java.net.SocketException;
0061: import java.net.UnknownHostException;
0062: import java.security.NoSuchAlgorithmException;
0063: import java.security.SecureRandom;
0064: import java.util.ArrayList;
0065: import java.util.Arrays;
0066: import java.util.Collection;
0067: import java.util.Collections;
0068: import java.util.Iterator;
0069: import java.util.List;
0070: import java.util.Random;
0071: import java.util.logging.Level;
0072: import java.util.logging.Logger;
0073: import org.openide.util.RequestProcessor;
0074: import org.openide.util.Task;
0075:
0076: /**
0077: * Command Line Interface and User Directory Locker support class.
0078: * Subclasses may be registered into the system to handle special command-line options.
0079: * To be registered, use <samp>META-INF/services/org.netbeans.CLIHandler</code>
0080: * in a JAR file in the startup or dynamic class path (e.g. <samp>lib/ext/</samp>
0081: * or <samp>lib/</samp>).
0082: * @author Jaroslav Tulach
0083: * @since org.netbeans.core/1 1.18
0084: * @see "#32054"
0085: * @see <a href="http://openide.netbeans.org/proposals/arch/cli.html">Specification</a>
0086: */
0087: public abstract class CLIHandler extends Object {
0088: /** lenght of the key used for connecting */
0089: private static final int KEY_LENGTH = 10;
0090: /** ok reply */
0091: private static final int REPLY_OK = 1;
0092: /** sends exit code */
0093: private static final int REPLY_EXIT = 2;
0094: /** fail reply */
0095: private static final int REPLY_FAIL = 0;
0096: /** the server is active, but cannot compute the value now */
0097: private static final int REPLY_DELAY = 3;
0098:
0099: /** request to read from input stream */
0100: private static final int REPLY_READ = 10;
0101: /** request to write */
0102: private static final int REPLY_WRITE = 11;
0103: /** request to find out how much data is available */
0104: private static final int REPLY_AVAILABLE = 12;
0105: /** request to write to stderr */
0106: private static final int REPLY_ERROR = 13;
0107:
0108: /**
0109: * Used during bootstrap sequence. Should only be used by core, not modules.
0110: */
0111: public static final int WHEN_BOOT = 1;
0112: /**
0113: * Used during later initialization or while NetBeans is up and running.
0114: */
0115: public static final int WHEN_INIT = 2;
0116: /** Extra set of inits.
0117: */
0118: public static final int WHEN_EXTRA = 3;
0119:
0120: /** reference to our server.
0121: */
0122: private static Server server;
0123:
0124: /** Testing output of the threads.
0125: */
0126: private static Logger OUTPUT = Logger
0127: .getLogger("org.netbeans.CLIHandler"); // NOI18N
0128:
0129: private int when;
0130:
0131: /**
0132: * Create a CLI handler and indicate its preferred timing.
0133: * @param when when to run the handler: {@link #WHEN_BOOT} or {@link #WHEN_INIT}
0134: */
0135: protected CLIHandler(int when) {
0136: this .when = when;
0137: }
0138:
0139: /**
0140: * Process some set of command-line arguments.
0141: * Unrecognized or null arguments should be ignored.
0142: * Recognized arguments should be nulled out.
0143: * @param args arguments
0144: * @return error value or 0 if everything is all right
0145: */
0146: protected abstract int cli(Args args);
0147:
0148: protected static void showHelp(PrintWriter w, Collection handlers,
0149: int when) {
0150: Iterator it = handlers.iterator();
0151: while (it.hasNext()) {
0152: CLIHandler h = (CLIHandler) it.next();
0153: if (when != -1 && when != h.when) {
0154: continue;
0155: }
0156:
0157: h.usage(w);
0158: }
0159: }
0160:
0161: /**
0162: * Print usage information for this handler.
0163: * @param w a writer to print to
0164: */
0165: protected abstract void usage(PrintWriter w);
0166:
0167: /** For testing purposes we can block the
0168: * algorithm in any place in the initialize method.
0169: */
0170: private static void enterState(int state, Integer block) {
0171: if (OUTPUT.isLoggable(Level.FINEST)) {
0172: synchronized (OUTPUT) {
0173: // for easier debugging of CLIHandlerTest
0174: OUTPUT.finest("state: " + state + " thread: "
0175: + Thread.currentThread()); // NOI18N
0176: }
0177: }
0178:
0179: if (block == null)
0180: return;
0181:
0182: synchronized (block) {
0183: if (state == block.intValue()) {
0184: if (OUTPUT.isLoggable(Level.FINEST)) {
0185: OUTPUT.finest(state + " blocked"); // NOI18N
0186: }
0187: block.notifyAll();
0188: try {
0189: block.wait();
0190: } catch (InterruptedException ex) {
0191: throw new IllegalStateException();
0192: }
0193: } else {
0194: if (OUTPUT.isLoggable(Level.FINEST)) {
0195: OUTPUT.finest(state + " not blocked"); // NOI18N
0196: }
0197: }
0198: }
0199: }
0200:
0201: private static boolean checkHelp(Args args, Collection handlers) {
0202: String[] argv = args.getArguments();
0203: for (int i = 0; i < argv.length; i++) {
0204: if (argv[i] == null) {
0205: continue;
0206: }
0207:
0208: if (argv[i].equals("-?") || argv[i].equals("--help")
0209: || argv[i].equals("-help")) { // NOI18N
0210: PrintWriter w = new PrintWriter(args.getOutputStream());
0211: showHelp(w, handlers, -1);
0212: w.flush();
0213: return true;
0214: }
0215: }
0216:
0217: return false;
0218: }
0219:
0220: /** Notification of available handlers.
0221: * @return non-zero if one of the handlers fails
0222: */
0223: protected static int notifyHandlers(Args args, Collection handlers,
0224: int when, boolean failOnUnknownOptions, boolean consume) {
0225: try {
0226: int r = 0;
0227: Iterator it = handlers.iterator();
0228: while (it.hasNext()) {
0229: CLIHandler h = (CLIHandler) it.next();
0230: if (h.when != when)
0231: continue;
0232:
0233: r = h.cli(args);
0234: //System.err.println("notifyHandlers: exit code " + r + " from " + h);
0235: if (r != 0) {
0236: return r;
0237: }
0238: }
0239: String[] argv = args.getArguments();
0240: if (failOnUnknownOptions) {
0241: argv = args.getArguments();
0242: for (int i = 0; i < argv.length; i++) {
0243: if (argv[i] != null) {
0244: // Unhandled option.
0245: PrintWriter w = new PrintWriter(args
0246: .getOutputStream());
0247: w.println("Ignored unknown option: " + argv[i]); // NOI18N
0248:
0249: // XXX(-ttran) not good, this doesn't show the help for
0250: // switches handled by the launcher
0251: //
0252: //showHelp(w, handlers);
0253:
0254: w.flush();
0255: return 2;
0256: }
0257: }
0258: }
0259: return 0;
0260: } finally {
0261: args.reset(consume);
0262: }
0263: }
0264:
0265: /**
0266: * Represents result of initialization.
0267: * @see #initialize(String[], ClassLoader)
0268: * @see #initialize(Args, Integer, List)
0269: */
0270: static final class Status {
0271: public static final int CANNOT_CONNECT = -255;
0272:
0273: private final File lockFile;
0274: private final int port;
0275: private int exitCode;
0276: private Task parael;
0277:
0278: /**
0279: * General failure.
0280: */
0281: Status() {
0282: this (0);
0283: }
0284:
0285: /**
0286: * Failure due to a parse problem.
0287: * @param c bad status code (not 0)
0288: * @see #cli(Args)
0289: */
0290: Status(int c) {
0291: this (null, 0, c, null);
0292: }
0293:
0294: /**
0295: * Some measure of success.
0296: * @param l the lock file (not null)
0297: * @param p the server port (not 0)
0298: * @param c a status code (0 or not)
0299: */
0300: Status(File l, int p, int c, Task parael) {
0301: lockFile = l;
0302: port = p;
0303: exitCode = c;
0304: this .parael = parael;
0305: }
0306:
0307: private void waitFinished() {
0308: if (parael != null) {
0309: parael.waitFinished();
0310: }
0311: }
0312:
0313: /**
0314: * Get the lock file, if available.
0315: * @return the lock file, or null if there is none
0316: */
0317: public File getLockFile() {
0318: waitFinished();
0319: return lockFile;
0320: }
0321:
0322: /**
0323: * Get the server port, if available.
0324: * @return a port number for the server, or 0 if there is no port open
0325: */
0326: public int getServerPort() {
0327: return port;
0328: }
0329:
0330: /**
0331: * Get the CLI parse status.
0332: * @return 0 for success, some other value for error conditions
0333: */
0334: public int getExitCode() {
0335: return exitCode;
0336: }
0337: }
0338:
0339: /** Initializes the system by creating lock file.
0340: *
0341: * @param args the command line arguments to recognize
0342: * @param classloader to find command CLIHandlers in
0343: * @param failOnUnknownOptions if true, fail (status 2) if some options are not recognized (also checks for -? and -help)
0344: * @param cleanLockFile removes lock file if it appears to be dead
0345: * @return the file to be used as lock file or null parsing of args failed
0346: */
0347: static Status initialize(String[] args, InputStream is,
0348: OutputStream os, java.io.OutputStream err,
0349: MainImpl.BootClassLoader loader,
0350: boolean failOnUnknownOptions, boolean cleanLockFile,
0351: Runnable runWhenHome) {
0352: return initialize(new Args(args, is, os, err, System
0353: .getProperty("user.dir")), (Integer) null, loader
0354: .allCLIs(), failOnUnknownOptions, cleanLockFile,
0355: runWhenHome);
0356: }
0357:
0358: /**
0359: * What to do later when {@link #finishInitialization} is called.
0360: * May remain null, otherwise contains list of Execute
0361: */
0362: private static List<Execute> doLater = new ArrayList<Execute>();
0363:
0364: static interface Execute {
0365: /** @return returns exit code */
0366: public int exec();
0367: }
0368:
0369: /** Execute this runnable when finishInitialization method is called.
0370: */
0371: private static int registerFinishInstallation(Execute run) {
0372: boolean runNow;
0373:
0374: synchronized (CLIHandler.class) {
0375: if (doLater != null) {
0376: doLater.add(run);
0377: runNow = false;
0378: } else {
0379: runNow = true;
0380: }
0381: }
0382:
0383: if (runNow) {
0384: return run.exec();
0385: }
0386:
0387: return 0;
0388: }
0389:
0390: /**
0391: * Run any {@link #WHEN_INIT} handlers that were passed to the original command line.
0392: * Should be called when the system is up and ready.
0393: * Cancels any existing actions, in case it is called twice.
0394: * @return the result of executing the handlers
0395: */
0396: static int finishInitialization(boolean recreate) {
0397: OUTPUT.log(Level.FINER, "finishInitialization {0}", recreate);
0398: List toRun;
0399: synchronized (CLIHandler.class) {
0400: toRun = doLater;
0401: doLater = recreate ? new ArrayList<Execute>() : null;
0402: if (OUTPUT.isLoggable(Level.FINER)) {
0403: OUTPUT.finer("Notify: " + toRun);
0404: }
0405: if (!recreate) {
0406: CLIHandler.class.notifyAll();
0407: }
0408: }
0409:
0410: if (toRun != null) {
0411: Iterator it = toRun.iterator();
0412: while (it.hasNext()) {
0413: Execute r = (Execute) it.next();
0414: int result = r.exec();
0415: if (result != 0) {
0416: return result;
0417: }
0418: }
0419: }
0420: return 0;
0421: }
0422:
0423: /** Blocks for a while and waits if the finishInitialization method
0424: * was called.
0425: * @param timeout ms to wait
0426: * @return true if finishInitialization is over
0427: */
0428: private static synchronized boolean waitFinishInstallationIsOver(
0429: int timeout) {
0430: if (doLater != null) {
0431: try {
0432: CLIHandler.class.wait(timeout);
0433: } catch (InterruptedException ex) {
0434: // go on, never mind
0435: }
0436: }
0437: return doLater == null;
0438: }
0439:
0440: /** Stops the server.
0441: */
0442: public static synchronized void stopServer() {
0443: Server s = server;
0444: if (s != null) {
0445: s.stopServer();
0446: }
0447: }
0448:
0449: /** Enhanced search for localhost address that works also behind VPN
0450: */
0451: private static InetAddress localHostAddress() throws IOException {
0452: java.net.NetworkInterface net = java.net.NetworkInterface
0453: .getByName("lo");
0454: if (net == null || !net.getInetAddresses().hasMoreElements()) {
0455: return InetAddress.getLocalHost();
0456: } else {
0457: return net.getInetAddresses().nextElement();
0458: }
0459: }
0460:
0461: /** Initializes the system by creating lock file.
0462: *
0463: * @param args the command line arguments to recognize
0464: * @param block the state we want to block in
0465: * @param handlers all handlers to use
0466: * @param failOnUnknownOptions if true, fail (status 2) if some options are not recognized (also checks for -? and -help)
0467: * @param cleanLockFile removes lock file if it appears to be dead
0468: * @param runWhenHome runnable to be executed when netbeans.user property is set
0469: * @return a status summary
0470: */
0471: static Status initialize(final Args args, final Integer block,
0472: final Collection handlers,
0473: final boolean failOnUnknownOptions, boolean cleanLockFile,
0474: Runnable runWhenHome) {
0475: // initial parsing of args
0476: {
0477: int r = notifyHandlers(args, handlers, WHEN_BOOT, false,
0478: failOnUnknownOptions);
0479: if (r != 0) {
0480: return new Status(r);
0481: }
0482: }
0483:
0484: // get the value
0485: String home = System.getProperty("netbeans.user"); // NOI18N
0486: if (home == null) {
0487: home = System.getProperty("user.home"); // NOI18N
0488: System.setProperty("netbeans.user", home); // NOI18N
0489: }
0490:
0491: if ("memory".equals(home)) { // NOI18N
0492: return new Status(0);
0493: }
0494:
0495: if (runWhenHome != null) {
0496: // notify that we have successfully identified the home property
0497: runWhenHome.run();
0498: }
0499:
0500: File lockFile = new File(home, "lock"); // NOI18N
0501:
0502: for (int i = 0; i < 5; i++) {
0503: // try few times to succeed
0504: try {
0505: if (lockFile.exists()) {
0506: enterState(5, block);
0507: throw new IOException("EXISTS"); // NOI18N
0508: }
0509:
0510: if (i == 0 && checkHelp(args, handlers)) {
0511: return new Status(2);
0512: }
0513:
0514: lockFile.getParentFile().mkdirs();
0515: lockFile.createNewFile();
0516: lockFile.deleteOnExit();
0517: secureAccess(lockFile);
0518:
0519: enterState(10, block);
0520:
0521: final byte[] arr = new byte[KEY_LENGTH];
0522: new Random().nextBytes(arr);
0523:
0524: server = new Server(arr, block, handlers,
0525: failOnUnknownOptions);
0526:
0527: final DataOutputStream os = new DataOutputStream(
0528: new FileOutputStream(lockFile));
0529: int p = server.getLocalPort();
0530: os.writeInt(p);
0531: os.flush();
0532:
0533: enterState(20, block);
0534:
0535: Task parael = new RequestProcessor("Secure CLI Port")
0536: .post(new Runnable() { // NOI18N
0537: public void run() {
0538: SecureRandom random = null;
0539: enterState(95, block);
0540: try {
0541: random = SecureRandom
0542: .getInstance("SHA1PRNG"); // NOI18N
0543: } catch (NoSuchAlgorithmException e) {
0544: // #36966: IBM JDK doesn't have it.
0545: try {
0546: random = SecureRandom
0547: .getInstance("IBMSecureRandom"); // NOI18N
0548: } catch (NoSuchAlgorithmException e2) {
0549: // OK, disable server...
0550: server.stopServer();
0551: }
0552: }
0553:
0554: enterState(96, block);
0555:
0556: if (random != null) {
0557: random.nextBytes(arr);
0558: }
0559:
0560: enterState(97, block);
0561:
0562: try {
0563: os.write(arr);
0564: os.flush();
0565:
0566: enterState(27, block);
0567: // if this turns to be slow due to lookup of getLocalHost
0568: // address, it can be done asynchronously as nobody needs
0569: // the address in the stream if the server is listening
0570: byte[] host = InetAddress
0571: .getLocalHost()
0572: .getAddress();
0573: if (block != null
0574: && block.intValue() == 667) {
0575: // this is here to emulate #64004
0576: throw new UnknownHostException(
0577: "dhcppc0"); // NOI18N
0578: }
0579: for (int all = 0; all < host.length; all++) {
0580: os.write(host[all]);
0581: }
0582: } catch (UnknownHostException unknownHost) {
0583: if (!"dhcppc0".equals(unknownHost
0584: .getMessage())) { // NOI18N, see above
0585: // if we just cannot get the address, we can go on
0586: unknownHost.printStackTrace();
0587: }
0588: } catch (IOException ex) {
0589: ex.printStackTrace();
0590: }
0591: try {
0592: os.close();
0593: } catch (IOException ex) {
0594: // ignore
0595: }
0596: }
0597: });
0598:
0599: int execCode = registerFinishInstallation(new Execute() {
0600: public int exec() {
0601: return notifyHandlers(args, handlers,
0602: WHEN_INIT, failOnUnknownOptions,
0603: failOnUnknownOptions);
0604: }
0605:
0606: public String toString() {
0607: return handlers.toString();
0608: }
0609: });
0610:
0611: enterState(0, block);
0612: return new Status(lockFile, server.getLocalPort(),
0613: execCode, parael);
0614: } catch (IOException ex) {
0615: if (!"EXISTS".equals(ex.getMessage())) { // NOI18N
0616: ex.printStackTrace();
0617: }
0618: // already exists, try to read
0619: byte[] key = null;
0620: byte[] serverAddress = null;
0621: int port = -1;
0622: DataInputStream is = null;
0623: try {
0624: enterState(21, block);
0625: if (OUTPUT.isLoggable(Level.FINER)) {
0626: OUTPUT.log(Level.FINER,
0627: "Reading lock file {0}", lockFile); // NOI18N
0628: }
0629: is = new DataInputStream(new FileInputStream(
0630: lockFile));
0631: port = is.readInt();
0632: enterState(22, block);
0633: key = new byte[KEY_LENGTH];
0634: is.readFully(key);
0635: enterState(23, block);
0636: byte[] x = new byte[4];
0637: is.readFully(x);
0638: enterState(24, block);
0639: serverAddress = x;
0640: } catch (EOFException eof) {
0641: // not yet fully written down
0642: if (port != -1) {
0643: try {
0644: enterState(94, block);
0645: // just wait a while
0646: Thread.sleep(2000);
0647: } catch (InterruptedException inter) {
0648: inter.printStackTrace();
0649: }
0650: continue;
0651: }
0652: } catch (IOException ex2) {
0653: // ok, try to read it once more
0654: enterState(26, block);
0655: } finally {
0656: if (is != null) {
0657: try {
0658: is.close();
0659: } catch (IOException ex3) {
0660: // ignore here
0661: }
0662: }
0663: enterState(25, block);
0664: }
0665:
0666: if (key != null && port != -1) {
0667: try {
0668: // ok, try to connect
0669: enterState(28, block);
0670: Socket socket = new Socket(localHostAddress(),
0671: port);
0672: // wait max of 1s for reply
0673: socket.setSoTimeout(5000);
0674: DataOutputStream os = new DataOutputStream(
0675: socket.getOutputStream());
0676: os.write(key);
0677: os.flush();
0678:
0679: enterState(30, block);
0680:
0681: DataInputStream replyStream = new DataInputStream(
0682: socket.getInputStream());
0683: byte[] outputArr = new byte[4096];
0684:
0685: COMMUNICATION: for (;;) {
0686: enterState(32, block);
0687: int reply = replyStream.read();
0688: //System.err.println("reply=" + reply);
0689: enterState(34, block);
0690:
0691: switch (reply) {
0692: case REPLY_FAIL:
0693: enterState(36, block);
0694: break COMMUNICATION;
0695: case REPLY_OK:
0696: enterState(38, block);
0697: // write the arguments
0698: String[] arr = args.getArguments();
0699: os.writeInt(arr.length);
0700: for (int a = 0; a < arr.length; a++) {
0701: os.writeUTF(arr[a]);
0702: }
0703: os.writeUTF(args.getCurrentDirectory()
0704: .toString());
0705: os.flush();
0706: break;
0707: case REPLY_EXIT:
0708: int exitCode = replyStream.readInt();
0709: if (exitCode == 0) {
0710: // to signal end of the world
0711: exitCode = -1;
0712: }
0713:
0714: os.close();
0715: replyStream.close();
0716:
0717: enterState(0, block);
0718: return new Status(lockFile, port,
0719: exitCode, null);
0720: case REPLY_READ: {
0721: enterState(42, block);
0722: int howMuch = replyStream.readInt();
0723: if (howMuch > outputArr.length) {
0724: outputArr = new byte[howMuch];
0725: }
0726: int really = args.getInputStream()
0727: .read(outputArr, 0, howMuch);
0728: os.write(really);
0729: if (really > 0) {
0730: os.write(outputArr, 0, really);
0731: }
0732: os.flush();
0733: break;
0734: }
0735: case REPLY_WRITE: {
0736: enterState(44, block);
0737: int howMuch = replyStream.readInt();
0738: if (howMuch > outputArr.length) {
0739: outputArr = new byte[howMuch];
0740: }
0741: replyStream.read(outputArr, 0, howMuch);
0742: args.getOutputStream().write(outputArr,
0743: 0, howMuch);
0744: break;
0745: }
0746: case REPLY_ERROR: {
0747: enterState(45, block);
0748: int howMuch = replyStream.readInt();
0749: if (howMuch > outputArr.length) {
0750: outputArr = new byte[howMuch];
0751: }
0752: replyStream.read(outputArr, 0, howMuch);
0753: args.getErrorStream().write(outputArr,
0754: 0, howMuch);
0755: break;
0756: }
0757: case REPLY_AVAILABLE:
0758: enterState(46, block);
0759: os.writeInt(args.getInputStream()
0760: .available());
0761: os.flush();
0762: break;
0763: case REPLY_DELAY:
0764: enterState(47, block);
0765: // ok, try once more
0766: break;
0767: case -1:
0768: enterState(48, block);
0769: // EOF. Why does this happen?
0770: break;
0771: default:
0772: enterState(49, block);
0773: assert false : reply;
0774: }
0775: }
0776:
0777: // connection ok, butlockFile secret key not recognized
0778: // delete the lock file
0779: } catch (java.net.SocketTimeoutException ex2) {
0780: // connection failed, the port is dead
0781: enterState(33, block);
0782: } catch (java.net.ConnectException ex2) {
0783: // connection failed, the port is dead
0784: enterState(33, block);
0785: } catch (IOException ex2) {
0786: // some strange exception
0787: ex2.printStackTrace();
0788: enterState(33, block);
0789: }
0790:
0791: boolean isSameHost = true;
0792: if (serverAddress != null) {
0793: try {
0794: isSameHost = Arrays.equals(InetAddress
0795: .getLocalHost().getAddress(),
0796: serverAddress);
0797: } catch (UnknownHostException ex5) {
0798: // ok, we will not try to connect
0799: enterState(999, block);
0800: }
0801: }
0802:
0803: if (cleanLockFile || isSameHost) {
0804: // remove the file and try once more
0805: lockFile.delete();
0806: } else {
0807: return new Status(Status.CANNOT_CONNECT);
0808: }
0809: }
0810: }
0811:
0812: try {
0813: enterState(83, block);
0814: Thread.sleep((int) (Math.random() * 1000.00));
0815: enterState(85, block);
0816: } catch (InterruptedException ex) {
0817: // means nothing
0818: }
0819: }
0820:
0821: // failure
0822: return new Status();
0823: }
0824:
0825: /** Make the file readable just to its owner.
0826: */
0827: private static void secureAccess(final File file)
0828: throws IOException {
0829: // using 1.6 method if available to make the file readable just
0830: // to its owner
0831: boolean success = false;
0832:
0833: String vm = System.getProperty("java.version"); // NOI18N
0834: if (vm != null && vm.startsWith("1.6")) { // NOI18N
0835: try {
0836: Method m = File.class.getMethod("setReadable",
0837: new Class[] { Boolean.TYPE, Boolean.TYPE }); // NOI18N
0838: Object s1 = m.invoke(file, new Object[] {
0839: Boolean.FALSE, Boolean.FALSE });
0840: Object s2 = m.invoke(file, new Object[] { Boolean.TRUE,
0841: Boolean.TRUE });
0842: success = Boolean.TRUE.equals(s1)
0843: && Boolean.TRUE.equals(s2);
0844: } catch (InvocationTargetException ex) {
0845: ex.printStackTrace();
0846: } catch (IllegalAccessException ex) {
0847: ex.printStackTrace();
0848: } catch (NoSuchMethodException ex) {
0849: ex.printStackTrace();
0850: }
0851: }
0852: if (success) {
0853: return;
0854: }
0855: try {
0856: // try to make it only user-readable (on Unix)
0857: // since people are likely to leave a+r on their userdir
0858: File chmod = new File("/bin/chmod"); // NOI18N
0859: if (!chmod.isFile()) {
0860: // Linux uses /bin, Solaris /usr/bin, others hopefully one of those
0861: chmod = new File("/usr/bin/chmod"); // NOI18N
0862: }
0863: if (chmod.isFile()) {
0864: int chmoded = Runtime.getRuntime().exec(
0865: new String[] { chmod.getAbsolutePath(),
0866: "go-rwx", // NOI18N
0867: file.getAbsolutePath() }).waitFor();
0868: if (chmoded != 0) {
0869: throw new IOException("could not run " + chmod
0870: + " go-rwx " + file); // NOI18N
0871: }
0872: }
0873: } catch (InterruptedException e) {
0874: throw (IOException) new IOException(e.toString())
0875: .initCause(e);
0876: }
0877: }
0878:
0879: /** Class that represents available arguments to the CLI
0880: * handlers.
0881: */
0882: public static final class Args extends Object {
0883: private String[] args;
0884: private final String[] argsBackup;
0885: private InputStream is;
0886: private OutputStream os;
0887: private OutputStream err;
0888: private File currentDir;
0889: private boolean closed;
0890:
0891: Args(String[] args, InputStream is, OutputStream os,
0892: java.io.OutputStream err, String currentDir) {
0893: argsBackup = args;
0894: reset(false);
0895: this .is = is;
0896: this .os = os;
0897: this .err = err;
0898: this .currentDir = new File(currentDir);
0899: }
0900:
0901: /**
0902: * Restore the arguments list to a clean state.
0903: * If not consuming arguments, it is just set to the original list.
0904: * If consuming arguments, any nulled-out arguments are removed from the list.
0905: */
0906: void reset(boolean consume) {
0907: if (consume) {
0908: String[] a = args;
0909: if (a == null) {
0910: a = argsBackup;
0911: }
0912: List<String> l = new ArrayList<String>(Arrays.asList(a));
0913: l.removeAll(Collections.singleton(null));
0914: args = l.toArray(new String[l.size()]);
0915: } else {
0916: args = argsBackup.clone();
0917: }
0918: }
0919:
0920: /** Closes the connection.
0921: */
0922: final void close() {
0923: closed = true;
0924: }
0925:
0926: /**
0927: * Get the command-line arguments.
0928: * You may not modify the returned array except to set some elements
0929: * to null as you recognize them.
0930: * @return array of string arguments, may contain nulls
0931: */
0932: public String[] getArguments() {
0933: return args;
0934: }
0935:
0936: /**
0937: * Get an output stream to which data may be sent.
0938: * @return stream to write to
0939: */
0940: public OutputStream getOutputStream() {
0941: return os;
0942: }
0943:
0944: /** Access to error stream.
0945: * @return the stream to write error messages to
0946: */
0947: public OutputStream getErrorStream() {
0948: return err;
0949: }
0950:
0951: public File getCurrentDirectory() {
0952: return currentDir;
0953: }
0954:
0955: /**
0956: * Get an input stream that may supply additional data.
0957: * @return stream to read from
0958: */
0959: public InputStream getInputStream() {
0960: return is;
0961: }
0962:
0963: /** Is open? True if the connection is still alive. Can be
0964: * used with long running computations to find out if the
0965: * consumer of the output has not been interupted.
0966: *
0967: * @return true if the connection is still alive
0968: */
0969: public boolean isOpen() {
0970: return !closed;
0971: }
0972:
0973: } // end of Args
0974:
0975: /** Server that creates local socket and communicates with it.
0976: */
0977: private static final class Server extends Thread {
0978: private byte[] key;
0979: private ServerSocket socket;
0980: private Integer block;
0981: private Collection handlers;
0982: private Socket work;
0983: private static volatile int counter;
0984: private final boolean failOnUnknownOptions;
0985:
0986: private static long lastReply;
0987: /** by default wait 100ms before sending a REPLY_FAIL message */
0988: private static long failDelay = 100;
0989:
0990: public Server(byte[] key, Integer block, Collection handlers,
0991: boolean failOnUnknownOptions) throws IOException {
0992: super ("CLI Requests Server"); // NOI18N
0993: this .key = key;
0994: this .setDaemon(true);
0995: this .block = block;
0996: this .handlers = handlers;
0997: this .failOnUnknownOptions = failOnUnknownOptions;
0998:
0999: socket = new ServerSocket(0, 50, localHostAddress());
1000: start();
1001: }
1002:
1003: public Server(Socket request, byte[] key, Integer block,
1004: Collection handlers, boolean failOnUnknownOptions)
1005: throws IOException {
1006: super ("CLI Handler Thread Handler: " + ++counter); // NOI18N
1007: this .key = key;
1008: this .setDaemon(true);
1009: this .block = block;
1010: this .handlers = handlers;
1011: this .work = request;
1012: this .failOnUnknownOptions = failOnUnknownOptions;
1013:
1014: start();
1015: }
1016:
1017: public int getLocalPort() {
1018: return socket.getLocalPort();
1019: }
1020:
1021: public void run() {
1022: if (work != null) {
1023: // I am a worker not listener server
1024: try {
1025: handleConnect(work);
1026: } catch (IOException ex) {
1027: ex.printStackTrace();
1028: }
1029: return;
1030: }
1031:
1032: ServerSocket toClose = socket;
1033: if (toClose == null) {
1034: return;
1035: }
1036:
1037: while (socket != null) {
1038: try {
1039: enterState(65, block);
1040: Socket s = socket.accept();
1041: if (socket == null) {
1042: enterState(66, block);
1043: s.getOutputStream().write(REPLY_FAIL);
1044: enterState(67, block);
1045: s.close();
1046: continue;
1047: }
1048:
1049: // spans new request handler
1050: new Server(s, key, block, handlers,
1051: failOnUnknownOptions);
1052: } catch (InterruptedIOException ex) {
1053: if (socket != null) {
1054: ex.printStackTrace();
1055: }
1056: // otherwise ignore, we've just been asked by the stopServer
1057: // to stop
1058: } catch (java.net.SocketException ex) {
1059: if (socket != null) {
1060: ex.printStackTrace();
1061: }
1062: } catch (IOException ex) {
1063: ex.printStackTrace();
1064: }
1065: }
1066:
1067: try {
1068: toClose.close();
1069: } catch (IOException ex) {
1070: ex.printStackTrace();
1071: }
1072: }
1073:
1074: final void stopServer() {
1075: socket = null;
1076: // interrupts the listening server
1077: interrupt();
1078: }
1079:
1080: private void handleConnect(Socket s) throws IOException {
1081:
1082: byte[] check = new byte[key.length];
1083: DataInputStream is = new DataInputStream(s.getInputStream());
1084:
1085: enterState(70, block);
1086:
1087: is.readFully(check);
1088:
1089: enterState(90, block);
1090:
1091: final DataOutputStream os = new DataOutputStream(s
1092: .getOutputStream());
1093:
1094: if (Arrays.equals(check, key)) {
1095: while (!waitFinishInstallationIsOver(2000)) {
1096: os.write(REPLY_DELAY);
1097: os.flush();
1098: }
1099:
1100: enterState(93, block);
1101: os.write(REPLY_OK);
1102: os.flush();
1103:
1104: // continue with arguments
1105: int numberOfArguments = is.readInt();
1106: String[] args = new String[numberOfArguments];
1107: for (int i = 0; i < args.length; i++) {
1108: args[i] = is.readUTF();
1109: }
1110: final String currentDir = is.readUTF();
1111:
1112: final Args arguments = new Args(args, new IS(is, os),
1113: new OS(os, REPLY_WRITE),
1114: new OS(os, REPLY_ERROR), currentDir);
1115:
1116: class ComputingAndNotifying extends Thread {
1117: public int res;
1118: public boolean finished;
1119:
1120: public ComputingAndNotifying() {
1121: super ("Computes values in handlers");
1122: }
1123:
1124: public void run() {
1125: try {
1126: if (checkHelp(arguments, handlers)) {
1127: res = 2;
1128: } else {
1129: res = notifyHandlers(arguments,
1130: handlers, WHEN_INIT,
1131: failOnUnknownOptions, false);
1132: }
1133:
1134: if (res == 0) {
1135: enterState(98, block);
1136: } else {
1137: enterState(99, block);
1138: }
1139: } finally {
1140: synchronized (this ) {
1141: finished = true;
1142: notifyAll();
1143: }
1144: }
1145: }
1146:
1147: public synchronized void waitForResultAndNotifyOthers() {
1148: // execute the handlers in another thread
1149: start();
1150: while (!finished) {
1151: try {
1152: wait(1000);
1153: os.write(REPLY_DELAY);
1154: os.flush();
1155: } catch (SocketException ex) {
1156: if (isClosedSocket(ex)) { // NOI18N
1157: // mark the arguments killed
1158: arguments.close();
1159: // interrupt this thread
1160: interrupt();
1161: } else {
1162: ex.printStackTrace();
1163: }
1164: } catch (InterruptedException ex) {
1165: ex.printStackTrace();
1166: } catch (IOException ex) {
1167: ex.printStackTrace();
1168: }
1169: }
1170: }
1171: }
1172: ComputingAndNotifying r = new ComputingAndNotifying();
1173: r.waitForResultAndNotifyOthers();
1174: try {
1175: os.write(REPLY_EXIT);
1176: os.writeInt(r.res);
1177: } catch (SocketException ex) {
1178: if (isClosedSocket(ex)) { // NOI18N
1179: // mark the arguments killed
1180: arguments.close();
1181: // interrupt r thread
1182: r.interrupt();
1183: } else {
1184: throw ex;
1185: }
1186: }
1187: } else {
1188: enterState(103, block);
1189: long toWait = lastReply + failDelay
1190: - System.currentTimeMillis();
1191: if (toWait > 0) {
1192: try {
1193: Thread.sleep(toWait);
1194: } catch (InterruptedException ex) {
1195: ex.printStackTrace();
1196: }
1197: failDelay *= 2;
1198: } else {
1199: failDelay = 100;
1200: }
1201: lastReply = System.currentTimeMillis();
1202: os.write(REPLY_FAIL);
1203: }
1204:
1205: enterState(120, block);
1206:
1207: os.close();
1208: is.close();
1209: }
1210:
1211: /** A method to find out on various systems whether an exception is
1212: * a signal of closed socket, especially if the peer is killed or exited.
1213: * @param ex the exception to investigate
1214: */
1215: static final boolean isClosedSocket(SocketException ex) {
1216: if (ex.getMessage().equals("Broken pipe")) { // NOI18N
1217: return true;
1218: }
1219: if (ex.getMessage().startsWith("Connection reset by peer")) { // NOI18N
1220: return true;
1221: }
1222:
1223: return false;
1224: }
1225:
1226: private static final class IS extends InputStream {
1227: private DataInputStream is;
1228: private DataOutputStream os;
1229:
1230: public IS(DataInputStream is, DataOutputStream os) {
1231: this .is = is;
1232: this .os = os;
1233: }
1234:
1235: public int read() throws IOException {
1236: byte[] arr = new byte[1];
1237: if (read(arr) == 1) {
1238: return arr[0];
1239: } else {
1240: return -1;
1241: }
1242: }
1243:
1244: public void close() throws IOException {
1245: super .close();
1246: }
1247:
1248: public int available() throws IOException {
1249: // ask for data
1250: os.write(REPLY_AVAILABLE);
1251: os.flush();
1252: // read provided data
1253: return is.readInt();
1254: }
1255:
1256: public int read(byte[] b) throws IOException {
1257: return read(b, 0, b.length);
1258: }
1259:
1260: public int read(byte[] b, int off, int len)
1261: throws IOException {
1262: // ask for data
1263: os.write(REPLY_READ);
1264: os.writeInt(len);
1265: os.flush();
1266: // read provided data
1267: int really = is.read();
1268: if (really > 0) {
1269: return is.read(b, off, really);
1270: } else {
1271: return really;
1272: }
1273: }
1274:
1275: } // end of IS
1276:
1277: private static final class OS extends OutputStream {
1278: private DataOutputStream os;
1279: private int type;
1280:
1281: public OS(DataOutputStream os, int type) {
1282: this .os = os;
1283: this .type = type;
1284: }
1285:
1286: public void write(int b) throws IOException {
1287: byte[] arr = { (byte) b };
1288: write(arr);
1289: }
1290:
1291: public void write(byte[] b) throws IOException {
1292: write(b, 0, b.length);
1293: }
1294:
1295: public void close() throws IOException {
1296: super .close();
1297: }
1298:
1299: public void flush() throws IOException {
1300: os.flush();
1301: }
1302:
1303: public void write(byte[] b, int off, int len)
1304: throws IOException {
1305: os.write(type);
1306: os.writeInt(len);
1307: os.write(b, off, len);
1308: }
1309:
1310: } // end of OS
1311:
1312: } // end of Server
1313:
1314: }
|