Added
Link Here
|
1 |
/* |
2 |
* Sun Public License Notice |
3 |
* |
4 |
* The contents of this file are subject to the Sun Public License |
5 |
* Version 1.0 (the "License"). You may not use this file except in |
6 |
* compliance with the License. A copy of the License is available at |
7 |
* http://www.sun.com/ |
8 |
* |
9 |
* The Original Code is NetBeans. The Initial Developer of the Original |
10 |
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun |
11 |
* Microsystems, Inc. All Rights Reserved. |
12 |
*/ |
13 |
|
14 |
package org.netbeans; |
15 |
|
16 |
import java.io.*; |
17 |
import java.net.InetAddress; |
18 |
import java.net.ServerSocket; |
19 |
import java.net.Socket; |
20 |
import java.net.URL; |
21 |
import java.security.NoSuchAlgorithmException; |
22 |
import java.security.SecureRandom; |
23 |
import java.util.ArrayList; |
24 |
import java.util.Arrays; |
25 |
import java.util.Collections; |
26 |
import java.util.Enumeration; |
27 |
import java.util.Iterator; |
28 |
import java.util.List; |
29 |
|
30 |
/** |
31 |
* Command Line Interface and User Directory Locker support class. |
32 |
* Subclasses may be registered into the system to handle special command-line options. |
33 |
* To be registered, use <samp>META-INF/services/org.netbeans.CLIHandler</code> |
34 |
* in a JAR file in the startup or dynamic class path (e.g. <samp>lib/ext/</samp> |
35 |
* or <samp>lib/</samp>). |
36 |
* @author Jaroslav Tulach |
37 |
* @since org.netbeans.core/1 1.18 |
38 |
* @see "#32054" |
39 |
* @see <a href="http://openide.netbeans.org/proposals/arch/cli.html">Specification</a> |
40 |
*/ |
41 |
public abstract class CLIHandler extends Object { |
42 |
/** lenght of the key used for connecting */ |
43 |
private static final int KEY_LENGTH = 10; |
44 |
/** ok reply */ |
45 |
private static final int REPLY_OK = 1; |
46 |
/** sends exit code */ |
47 |
private static final int REPLY_EXIT = 2; |
48 |
/** fail reply */ |
49 |
private static final int REPLY_FAIL = 0; |
50 |
|
51 |
/** request to read from input stream */ |
52 |
private static final int REPLY_READ = 10; |
53 |
/** request to write */ |
54 |
private static final int REPLY_WRITE = 11; |
55 |
/** request to find out how much data is available */ |
56 |
private static final int REPLY_AVAILABLE = 12; |
57 |
|
58 |
/** |
59 |
* Used during bootstrap sequence. Should only be used by core, not modules. |
60 |
*/ |
61 |
public static final int WHEN_BOOT = 1; |
62 |
/** |
63 |
* Used during later initialization or while NetBeans is up and running. |
64 |
*/ |
65 |
public static final int WHEN_INIT = 2; |
66 |
|
67 |
private int when; |
68 |
|
69 |
/** |
70 |
* Create a CLI handler and indicate its preferred timing. |
71 |
* @param when when to run the handler: {@link #WHEN_BOOT} or {@link #WHEN_INIT} |
72 |
*/ |
73 |
protected CLIHandler(int when) { |
74 |
this.when = when; |
75 |
} |
76 |
|
77 |
/** |
78 |
* Process some set of command-line arguments. |
79 |
* Unrecognized or null arguments should be ignored. |
80 |
* Recognized arguments should be nulled out. |
81 |
* @param args arguments |
82 |
* @return error value or 0 if everything is all right |
83 |
*/ |
84 |
protected abstract int cli(Args args); |
85 |
|
86 |
private static void showHelp(PrintWriter w, List handlers) { |
87 |
w.println("-?"); |
88 |
w.println("-help"); |
89 |
w.println(" Show this help information."); |
90 |
Iterator it = handlers.iterator(); |
91 |
while (it.hasNext()) { |
92 |
((CLIHandler)it.next()).usage(w); |
93 |
} |
94 |
} |
95 |
|
96 |
/** |
97 |
* Print usage information for this handler. |
98 |
* @param w a writer to print to |
99 |
*/ |
100 |
protected abstract void usage(PrintWriter w); |
101 |
|
102 |
/** For testing purposes we can block the |
103 |
* algorithm in any place in the initialize method. |
104 |
*/ |
105 |
private static void enterState(int state, Integer block) { |
106 |
if (block == null) return; |
107 |
|
108 |
// for easier debugging of CLIHandlerTest |
109 |
//System.err.println("state: " + state + " block: " + block + " thread: " + Thread.currentThread()); |
110 |
|
111 |
synchronized (block) { |
112 |
if (state == block.intValue()) { |
113 |
block.notifyAll(); |
114 |
try { |
115 |
block.wait(); |
116 |
} catch (InterruptedException ex) { |
117 |
throw new IllegalStateException(); |
118 |
} |
119 |
} |
120 |
} |
121 |
} |
122 |
|
123 |
/** Notification of available handlers. |
124 |
* @return non-zero if one of the handlers fails |
125 |
*/ |
126 |
private static int notifyHandlers(Args args, List handlers, int when, boolean failOnUnknownOptions, boolean consume) { |
127 |
try { |
128 |
//System.err.println("notifyHandlers: handlers=" + handlers + " when=" + when + " args=" + Arrays.asList(args.getArguments())); |
129 |
if (failOnUnknownOptions) { |
130 |
String[] argv = args.getArguments(); |
131 |
for (int i = 0; i < argv.length; i++) { |
132 |
if (argv[i].equals("-?") || argv[i].equals("-help")) { // NOI18N |
133 |
PrintWriter w = new PrintWriter(args.getOutputStream()); |
134 |
showHelp(w, handlers); |
135 |
w.flush(); |
136 |
return 2; |
137 |
} |
138 |
} |
139 |
} |
140 |
int r = 0; |
141 |
Iterator it = handlers.iterator(); |
142 |
while (it.hasNext()) { |
143 |
CLIHandler h = (CLIHandler)it.next(); |
144 |
if (h.when != when) continue; |
145 |
|
146 |
r = h.cli(args); |
147 |
//System.err.println("notifyHandlers: exit code " + r + " from " + h); |
148 |
if (r != 0) { |
149 |
return r; |
150 |
} |
151 |
} |
152 |
if (failOnUnknownOptions) { |
153 |
String[] argv = args.getArguments(); |
154 |
for (int i = 0; i < argv.length; i++) { |
155 |
if (argv[i] != null) { |
156 |
// Unhandled option. |
157 |
PrintWriter w = new PrintWriter(args.getOutputStream()); |
158 |
w.println("Unknown option: " + argv[i]); // NOI18N |
159 |
showHelp(w, handlers); |
160 |
w.flush(); |
161 |
return 2; |
162 |
} |
163 |
} |
164 |
} |
165 |
return 0; |
166 |
} finally { |
167 |
args.reset(consume); |
168 |
} |
169 |
} |
170 |
|
171 |
/** |
172 |
* Represents result of initialization. |
173 |
* @see #initialize(String[], ClassLoader) |
174 |
* @see #initialize(Args, Integer, List) |
175 |
*/ |
176 |
static final class Status { |
177 |
public static final int CANNOT_CONNECT = -255; |
178 |
|
179 |
private final File lockFile; |
180 |
private final int port; |
181 |
private final int exitCode; |
182 |
/** |
183 |
* General failure. |
184 |
*/ |
185 |
Status() { |
186 |
this(0); |
187 |
} |
188 |
/** |
189 |
* Failure due to a parse problem. |
190 |
* @param c bad status code (not 0) |
191 |
* @see #cli(Args) |
192 |
*/ |
193 |
Status(int c) { |
194 |
this(null, 0, c); |
195 |
} |
196 |
/** |
197 |
* Some measure of success. |
198 |
* @param l the lock file (not null) |
199 |
* @param p the server port (not 0) |
200 |
* @param c a status code (0 or not) |
201 |
*/ |
202 |
Status(File l, int p, int c) { |
203 |
lockFile = l; |
204 |
port = p; |
205 |
exitCode = c; |
206 |
} |
207 |
/** |
208 |
* Get the lock file, if available. |
209 |
* @return the lock file, or null if there is none |
210 |
*/ |
211 |
public File getLockFile() { |
212 |
return lockFile; |
213 |
} |
214 |
/** |
215 |
* Get the server port, if available. |
216 |
* @return a port number for the server, or 0 if there is no port open |
217 |
*/ |
218 |
public int getServerPort() { |
219 |
return port; |
220 |
} |
221 |
/** |
222 |
* Get the CLI parse status. |
223 |
* @return 0 for success, some other value for error conditions |
224 |
*/ |
225 |
public int getExitCode() { |
226 |
return exitCode; |
227 |
} |
228 |
} |
229 |
|
230 |
|
231 |
/** Initializes the system by creating lock file. |
232 |
* |
233 |
* @param args the command line arguments to recognize |
234 |
* @param classloader to find command CLIHandlers in |
235 |
* @param doAllInit if true, run all WHEN_INIT handlers now; else wait for {@link #finishInitialization} |
236 |
* @param failOnUnknownOptions if true, fail (status 2) if some options are not recognized (also checks for -? and -help) |
237 |
* @param cleanLockFile removes lock file if it appears to be dead |
238 |
* @return the file to be used as lock file or null parsing of args failed |
239 |
*/ |
240 |
static Status initialize(String[] args, ClassLoader loader, boolean doAllInit, boolean failOnUnknownOptions, boolean cleanLockFile) { |
241 |
return initialize(new Args(args, System.in, System.err, System.getProperty ("user.dir")), (Integer)null, allCLIs(loader), doAllInit, failOnUnknownOptions, cleanLockFile); |
242 |
} |
243 |
|
244 |
/** |
245 |
* What to do later when {@link #finishInitialization} is called. |
246 |
* May remain null. |
247 |
*/ |
248 |
private static Runnable doLater = null; |
249 |
|
250 |
/** Initializes the system by creating lock file. |
251 |
* |
252 |
* @param args the command line arguments to recognize |
253 |
* @param block the state we want to block in |
254 |
* @param handlers all handlers to use |
255 |
* @param doAllInit if true, run all WHEN_INIT handlers now; else wait for {@link #finishInitialization} |
256 |
* @param failOnUnknownOptions if true, fail (status 2) if some options are not recognized (also checks for -? and -help) |
257 |
* @param cleanLockFile removes lock file if it appears to be dead |
258 |
* @return a status summary |
259 |
*/ |
260 |
static Status initialize(final Args args, Integer block, final List handlers, boolean doAllInit, final boolean failOnUnknownOptions, boolean cleanLockFile) { |
261 |
// initial parsing of args |
262 |
{ |
263 |
int r = notifyHandlers(args, handlers, WHEN_BOOT, false, failOnUnknownOptions); |
264 |
if (r != 0) { |
265 |
return new Status(r); |
266 |
} |
267 |
} |
268 |
|
269 |
// get the value |
270 |
String home = System.getProperty("netbeans.user"); // NOI18N |
271 |
if (home == null) { |
272 |
home = System.getProperty("user.home"); // NOI18N |
273 |
} |
274 |
File lockFile = new File(home, "lock"); // NOI18N |
275 |
|
276 |
for (int i = 0; i < 5; i++) { |
277 |
// try few times to succeed |
278 |
try { |
279 |
if (lockFile.exists()) { |
280 |
enterState(5, block); |
281 |
throw new IOException("EXISTS"); // NOI18N |
282 |
} |
283 |
lockFile.getParentFile().mkdirs(); |
284 |
lockFile.createNewFile(); |
285 |
lockFile.deleteOnExit(); |
286 |
try { |
287 |
// try to make it only user-readable (on Unix) |
288 |
// since people are likely to leave a+r on their userdir |
289 |
File chmod = new File("/bin/chmod"); // NOI18N |
290 |
if (!chmod.isFile()) { |
291 |
// Linux uses /bin, Solaris /usr/bin, others hopefully one of those |
292 |
chmod = new File("/usr/bin/chmod"); // NOI18N |
293 |
} |
294 |
if (chmod.isFile()) { |
295 |
int chmoded = Runtime.getRuntime().exec(new String[] { |
296 |
chmod.getAbsolutePath(), |
297 |
"go-rwx", // NOI18N |
298 |
lockFile.getAbsolutePath() |
299 |
}).waitFor(); |
300 |
if (chmoded != 0) { |
301 |
throw new IOException("could not run " + chmod + " go-rwx " + lockFile); // NOI18N |
302 |
} |
303 |
} |
304 |
} catch (InterruptedException e) { |
305 |
throw (IOException)new IOException(e.toString()).initCause(e); |
306 |
} |
307 |
|
308 |
enterState(10, block); |
309 |
|
310 |
byte[] arr = new byte[KEY_LENGTH]; |
311 |
try { |
312 |
SecureRandom.getInstance("SHA1PRNG").nextBytes(arr); // NOI18N |
313 |
} catch (NoSuchAlgorithmException e) { |
314 |
// #36966: IBM JDK doesn't have it. |
315 |
try { |
316 |
SecureRandom.getInstance("IBMSecureRandom").nextBytes(arr); // NOI18N |
317 |
} catch (NoSuchAlgorithmException e2) { |
318 |
// OK, disable server... |
319 |
System.err.println("WARNING: remote IDE automation features cannot be cryptographically secured, so disabling; please reopen http://www.netbeans.org/issues/show_bug.cgi?id=36966"); // NOI18N |
320 |
e.printStackTrace(); |
321 |
return new Status(); |
322 |
} |
323 |
} |
324 |
|
325 |
Server server = new Server(arr, block, handlers, failOnUnknownOptions); |
326 |
|
327 |
DataOutputStream os = new DataOutputStream(new FileOutputStream(lockFile)); |
328 |
int p = server.getLocalPort(); |
329 |
os.writeInt(p); |
330 |
|
331 |
enterState(20, block); |
332 |
|
333 |
os.write(arr); |
334 |
os.close(); |
335 |
|
336 |
int exitCode; |
337 |
if (doAllInit) { |
338 |
exitCode = notifyHandlers(args, handlers, WHEN_INIT, failOnUnknownOptions, failOnUnknownOptions); |
339 |
} else { |
340 |
doLater = new Runnable() { |
341 |
public void run() { |
342 |
int r = notifyHandlers(args, handlers, WHEN_INIT, failOnUnknownOptions, failOnUnknownOptions); |
343 |
if (r != 0) { |
344 |
// Not much to do about it. |
345 |
System.err.println("Post-initialization command-line options could not be run."); // NOI18N |
346 |
//System.err.println("r=" + r + " args=" + java.util.Arrays.asList(args.getArguments())); |
347 |
} |
348 |
} |
349 |
}; |
350 |
exitCode = 0; |
351 |
} |
352 |
|
353 |
enterState(0, block); |
354 |
return new Status(lockFile, server.getLocalPort(), exitCode); |
355 |
|
356 |
} catch (IOException ex) { |
357 |
if (!"EXISTS".equals(ex.getMessage())) { // NOI18N |
358 |
ex.printStackTrace(); |
359 |
} |
360 |
// already exists, try to read |
361 |
byte[] key = null; |
362 |
int port = -1; |
363 |
try { |
364 |
DataInputStream is = new DataInputStream(new FileInputStream(lockFile)); |
365 |
port = is.readInt(); |
366 |
key = new byte[KEY_LENGTH]; |
367 |
is.readFully(key); |
368 |
is.close(); |
369 |
} catch (IOException ex2) { |
370 |
// ok, try to read it once more |
371 |
} |
372 |
|
373 |
if (key != null && port != -1) { |
374 |
try { |
375 |
// ok, try to connect |
376 |
enterState(28, block); |
377 |
Socket socket = new Socket(InetAddress.getLocalHost(), port); |
378 |
// wait max of 1s for reply |
379 |
socket.setSoTimeout(5000); |
380 |
DataOutputStream os = new DataOutputStream(socket.getOutputStream()); |
381 |
os.write(key); |
382 |
os.flush(); |
383 |
|
384 |
enterState(30, block); |
385 |
|
386 |
DataInputStream replyStream = new DataInputStream(socket.getInputStream()); |
387 |
COMMUNICATION: for (;;) { |
388 |
enterState(32, block); |
389 |
int reply = replyStream.read(); |
390 |
//System.err.println("reply=" + reply); |
391 |
enterState(34, block); |
392 |
|
393 |
switch (reply) { |
394 |
case REPLY_FAIL: |
395 |
enterState(36, block); |
396 |
break COMMUNICATION; |
397 |
case REPLY_OK: |
398 |
enterState(38, block); |
399 |
// write the arguments |
400 |
String[] arr = args.getArguments(); |
401 |
os.writeInt(arr.length); |
402 |
for (int a = 0; a < arr.length; a++) { |
403 |
os.writeUTF(arr[a]); |
404 |
} |
405 |
os.writeUTF (args.getCurrentDirectory().toString()); |
406 |
os.flush(); |
407 |
break; |
408 |
case REPLY_EXIT: |
409 |
int exitCode = replyStream.readInt(); |
410 |
if (exitCode == 0) { |
411 |
// to signal end of the world |
412 |
exitCode = -1; |
413 |
} |
414 |
|
415 |
os.close(); |
416 |
replyStream.close(); |
417 |
|
418 |
enterState(0, block); |
419 |
return new Status(lockFile, port, exitCode); |
420 |
case REPLY_READ: { |
421 |
enterState(42, block); |
422 |
int howMuch = replyStream.readInt(); |
423 |
byte[] byteArr = new byte[howMuch]; |
424 |
args.getInputStream().read(byteArr); |
425 |
os.write(byteArr); |
426 |
os.flush(); |
427 |
break; |
428 |
} |
429 |
case REPLY_WRITE: { |
430 |
enterState(44, block); |
431 |
int howMuch = replyStream.readInt(); |
432 |
byte[] byteArr = new byte[howMuch]; |
433 |
replyStream.read(byteArr); |
434 |
args.getOutputStream().write(byteArr); |
435 |
break; |
436 |
} |
437 |
case REPLY_AVAILABLE: |
438 |
enterState(46, block); |
439 |
os.writeInt(args.getInputStream().available()); |
440 |
os.flush(); |
441 |
break; |
442 |
case -1: |
443 |
// EOF. Why does this happen? |
444 |
break; |
445 |
default: |
446 |
throw new IllegalStateException ("Reply: " + reply); // NOI18N |
447 |
} |
448 |
} |
449 |
|
450 |
// connection ok, but secret key not recognized |
451 |
// delete the lock file |
452 |
} catch (java.net.SocketTimeoutException ex2) { |
453 |
// connection failed, the port is dead |
454 |
enterState(33, block); |
455 |
} catch (IOException ex2) { |
456 |
// some strange exception |
457 |
ex2.printStackTrace(); |
458 |
enterState(33, block); |
459 |
} |
460 |
if (cleanLockFile) { |
461 |
// remove the file and try once more |
462 |
lockFile.delete(); |
463 |
} else { |
464 |
return new Status (Status.CANNOT_CONNECT); |
465 |
} |
466 |
} |
467 |
} |
468 |
|
469 |
try { |
470 |
enterState(83, block); |
471 |
Thread.sleep((int)(Math.random() * 1000.00)); |
472 |
enterState(85, block); |
473 |
} catch (InterruptedException ex) { |
474 |
// means nothing |
475 |
} |
476 |
} |
477 |
|
478 |
// failure |
479 |
return new Status(); |
480 |
} |
481 |
|
482 |
/** |
483 |
* Run any {@link #WHEN_INIT} handlers that were passed to the original command line. |
484 |
* Should be called when the system is up and ready. |
485 |
* Cancels any existing actions, in case it is called twice. |
486 |
*/ |
487 |
static void finishInitialization() { |
488 |
if (doLater != null) { |
489 |
doLater.run(); |
490 |
doLater = null; |
491 |
} |
492 |
} |
493 |
|
494 |
/** For a given classloader finds all registered CLIHandlers. |
495 |
*/ |
496 |
private static List allCLIs(ClassLoader loader) { |
497 |
/* should be, but we cannot use it yet, as openide is not separated: |
498 |
return new ArrayList(Lookups.metaInfServices(loader).lookup(new Lookup.Template(CLIHandler.class)).allInstances()); |
499 |
*/ |
500 |
List res = new ArrayList(); |
501 |
Enumeration en; |
502 |
try { |
503 |
en = loader.getResources("META-INF/services/org.netbeans.CLIHandler"); // NOI18N |
504 |
} catch (IOException ex) { |
505 |
ex.printStackTrace(); |
506 |
return Collections.EMPTY_LIST; |
507 |
} |
508 |
while (en.hasMoreElements()) { |
509 |
URL url = (URL)en.nextElement(); |
510 |
try { |
511 |
InputStream is = url.openStream(); |
512 |
try { |
513 |
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); // NOI18N |
514 |
while (true) { |
515 |
String line = reader.readLine(); |
516 |
if (line == null) break; |
517 |
|
518 |
// Ignore blank lines and comments. |
519 |
line = line.trim(); |
520 |
if (line.length() == 0) continue; |
521 |
|
522 |
boolean remove = false; |
523 |
if (line.charAt(0) == '#') { |
524 |
if (line.length() == 1 || line.charAt(1) != '-') { |
525 |
continue; |
526 |
} |
527 |
|
528 |
// line starting with #- is a sign to remove that class from lookup |
529 |
remove = true; |
530 |
line = line.substring(2); |
531 |
} |
532 |
Class inst = Class.forName(line, false, loader); |
533 |
|
534 |
Object obj = inst.newInstance(); |
535 |
res.add((CLIHandler)obj); |
536 |
} |
537 |
} finally { |
538 |
is.close(); |
539 |
} |
540 |
} catch (Exception e) { |
541 |
e.printStackTrace(); |
542 |
} |
543 |
} |
544 |
|
545 |
return res; |
546 |
} |
547 |
|
548 |
/** Class that represents available arguments to the CLI |
549 |
* handlers. |
550 |
*/ |
551 |
public static final class Args extends Object { |
552 |
private String[] args; |
553 |
private final String[] argsBackup; |
554 |
private InputStream is; |
555 |
private OutputStream os; |
556 |
private File currentDir; |
557 |
|
558 |
Args(String[] args, InputStream is, OutputStream os, String currentDir) { |
559 |
argsBackup = args; |
560 |
reset(false); |
561 |
this.is = is; |
562 |
this.os = os; |
563 |
this.currentDir = new File (currentDir); |
564 |
} |
565 |
|
566 |
/** |
567 |
* Restore the arguments list to a clean state. |
568 |
* If not consuming arguments, it is just set to the original list. |
569 |
* If consuming arguments, any nulled-out arguments are removed from the list. |
570 |
*/ |
571 |
void reset(boolean consume) { |
572 |
if (consume) { |
573 |
String[] a = args; |
574 |
if (a == null) { |
575 |
a = argsBackup; |
576 |
} |
577 |
List l = new ArrayList(Arrays.asList(a)); |
578 |
l.removeAll(Collections.singleton(null)); |
579 |
args = (String[])l.toArray(new String[l.size()]); |
580 |
} else { |
581 |
args = (String[])argsBackup.clone(); |
582 |
} |
583 |
} |
584 |
|
585 |
/** |
586 |
* Get the command-line arguments. |
587 |
* You may not modify the returned array except to set some elements |
588 |
* to null as you recognize them. |
589 |
* @return array of string arguments, may contain nulls |
590 |
*/ |
591 |
public String[] getArguments() { |
592 |
return args; |
593 |
} |
594 |
|
595 |
/** |
596 |
* Get an output stream to which data may be sent. |
597 |
* @return stream to write to |
598 |
*/ |
599 |
public OutputStream getOutputStream() { |
600 |
return os; |
601 |
} |
602 |
|
603 |
public File getCurrentDirectory () { |
604 |
return currentDir; |
605 |
} |
606 |
|
607 |
/** |
608 |
* Get an input stream that may supply additional data. |
609 |
* @return stream to read from |
610 |
*/ |
611 |
public InputStream getInputStream() { |
612 |
return is; |
613 |
} |
614 |
} // end of Args |
615 |
|
616 |
/** Server that creates local socket and communicates with it. |
617 |
*/ |
618 |
private static final class Server extends Thread { |
619 |
private byte[] key; |
620 |
private ServerSocket socket; |
621 |
private Integer block; |
622 |
private List handlers; |
623 |
private Socket work; |
624 |
private static volatile int counter; |
625 |
private final boolean failOnUnknownOptions; |
626 |
|
627 |
public Server(byte[] key, Integer block, List handlers, boolean failOnUnknownOptions) throws IOException { |
628 |
super("CLI Requests Server"); // NOI18N |
629 |
this.key = key; |
630 |
this.setDaemon(true); |
631 |
this.block = block; |
632 |
this.handlers = handlers; |
633 |
this.failOnUnknownOptions = failOnUnknownOptions; |
634 |
|
635 |
socket = new ServerSocket(0); |
636 |
|
637 |
start(); |
638 |
} |
639 |
|
640 |
public Server(Socket request, byte[] key, Integer block, List handlers, boolean failOnUnknownOptions) throws IOException { |
641 |
super("CLI Handler Thread Handler: " + ++counter); // NOI18N |
642 |
this.key = key; |
643 |
this.setDaemon(true); |
644 |
this.block = block; |
645 |
this.handlers = handlers; |
646 |
this.work = request; |
647 |
this.failOnUnknownOptions = failOnUnknownOptions; |
648 |
|
649 |
start(); |
650 |
} |
651 |
|
652 |
public int getLocalPort() { |
653 |
return socket.getLocalPort(); |
654 |
} |
655 |
|
656 |
public void run() { |
657 |
if (work != null) { |
658 |
// I am a worker not listener server |
659 |
try { |
660 |
handleConnect(work); |
661 |
} catch (IOException ex) { |
662 |
ex.printStackTrace(); |
663 |
} |
664 |
return; |
665 |
} |
666 |
|
667 |
|
668 |
while (true) { |
669 |
try { |
670 |
enterState(65, block); |
671 |
Socket s = socket.accept(); |
672 |
|
673 |
// spans new request handler |
674 |
new Server(s, key, block, handlers, failOnUnknownOptions); |
675 |
} catch (IOException ex) { |
676 |
ex.printStackTrace(); |
677 |
} |
678 |
} |
679 |
} |
680 |
|
681 |
|
682 |
private void handleConnect(Socket s) throws IOException { |
683 |
|
684 |
byte[] check = new byte[key.length]; |
685 |
DataInputStream is = new DataInputStream(s.getInputStream()); |
686 |
|
687 |
enterState(70, block); |
688 |
|
689 |
is.readFully(check); |
690 |
|
691 |
enterState(90, block); |
692 |
|
693 |
DataOutputStream os = new DataOutputStream(s.getOutputStream()); |
694 |
|
695 |
if (Arrays.equals(check, key)) { |
696 |
enterState(93, block); |
697 |
os.write(REPLY_OK); |
698 |
os.flush(); |
699 |
|
700 |
// continue with arguments |
701 |
int numberOfArguments = is.readInt(); |
702 |
String[] args = new String[numberOfArguments]; |
703 |
for (int i = 0; i < args.length; i++) { |
704 |
args[i] = is.readUTF(); |
705 |
} |
706 |
String currentDir = is.readUTF (); |
707 |
|
708 |
Args arguments = new Args(args, new IS(is, os), new OS(is, os), currentDir); |
709 |
int res = notifyHandlers(arguments, handlers, WHEN_INIT, failOnUnknownOptions, false); |
710 |
|
711 |
if (res == 0) { |
712 |
enterState(98, block); |
713 |
} else { |
714 |
enterState(99, block); |
715 |
} |
716 |
|
717 |
os.write(REPLY_EXIT); |
718 |
os.writeInt(res); |
719 |
} else { |
720 |
enterState(103, block); |
721 |
os.write(REPLY_FAIL); |
722 |
} |
723 |
|
724 |
|
725 |
enterState(120, block); |
726 |
|
727 |
os.close(); |
728 |
is.close(); |
729 |
} |
730 |
|
731 |
private static final class IS extends InputStream { |
732 |
private DataInputStream is; |
733 |
private DataOutputStream os; |
734 |
|
735 |
public IS(DataInputStream is, DataOutputStream os) { |
736 |
this.is = is; |
737 |
this.os = os; |
738 |
} |
739 |
|
740 |
public int read() throws IOException { |
741 |
byte[] arr = new byte[1]; |
742 |
if (read(arr) == 1) { |
743 |
return arr[0]; |
744 |
} else { |
745 |
return -1; |
746 |
} |
747 |
} |
748 |
|
749 |
public void close() throws IOException { |
750 |
super.close(); |
751 |
} |
752 |
|
753 |
public int available() throws IOException { |
754 |
// ask for data |
755 |
os.write(REPLY_AVAILABLE); |
756 |
os.flush(); |
757 |
// read provided data |
758 |
return is.readInt(); |
759 |
} |
760 |
|
761 |
public int read(byte[] b) throws IOException { |
762 |
return read(b, 0, b.length); |
763 |
} |
764 |
|
765 |
public int read(byte[] b, int off, int len) throws IOException { |
766 |
// ask for data |
767 |
os.write(REPLY_READ); |
768 |
os.writeInt(len); |
769 |
os.flush(); |
770 |
// read provided data |
771 |
return is.read(b, off, len); |
772 |
} |
773 |
|
774 |
} // end of IS |
775 |
|
776 |
private static final class OS extends OutputStream { |
777 |
private DataOutputStream os; |
778 |
|
779 |
public OS(DataInputStream is, DataOutputStream os) { |
780 |
this.os = os; |
781 |
} |
782 |
|
783 |
public void write(int b) throws IOException { |
784 |
byte[] arr = { (byte)b }; |
785 |
write(arr); |
786 |
} |
787 |
|
788 |
public void write(byte[] b) throws IOException { |
789 |
write(b, 0, b.length); |
790 |
} |
791 |
|
792 |
public void close() throws IOException { |
793 |
super.close(); |
794 |
} |
795 |
|
796 |
public void flush() throws IOException { |
797 |
os.flush(); |
798 |
} |
799 |
|
800 |
public void write(byte[] b, int off, int len) throws IOException { |
801 |
os.write(REPLY_WRITE); |
802 |
os.writeInt(len); |
803 |
os.write(b, off, len); |
804 |
} |
805 |
|
806 |
} // end of OS |
807 |
|
808 |
} // end of Server |
809 |
|
810 |
|
811 |
} |