001: /*
002: * ExecCmd.java --
003: *
004: * This file contains the Jacl implementation of the built-in Tcl "exec"
005: * command. The exec command is not available on the Mac.
006: *
007: * Copyright (c) 1997 Sun Microsystems, Inc.
008: *
009: * See the file "license.terms" for information on usage and
010: * redistribution of this file, and for a DISCLAIMER OF ALL
011: * WARRANTIES.
012: *
013: * RCS: @(#) $Id: ExecCmd.java,v 1.13 2006/06/30 07:57:18 mdejong Exp $
014: */
015:
016: package tcl.lang;
017:
018: import java.util.*;
019: import java.io.*;
020:
021: /*
022: * This class implements the built-in "exec" command in Tcl.
023: */
024:
025: class ExecCmd implements Command {
026:
027: private static boolean debug = false;
028:
029: /*
030: *----------------------------------------------------------------------
031: *
032: * cmdProc --
033: *
034: * This procedure is invoked to process the "exec" Tcl command.
035: * See the user documentation for details on what it does.
036: *
037: * Results:
038: * None.
039: *
040: * Side effects:
041: * See the user documentation.
042: *
043: *----------------------------------------------------------------------
044: */
045:
046: public void cmdProc(Interp interp, // The current interpreter.
047: TclObject argv[]) // The arguments to exec.
048: throws TclException // A standard Tcl exception.
049: {
050: int firstWord; // Index to the first non-switch arg
051: int argLen = argv.length; // No of args to copy to argStrs
052: int exit; // denotes exit status of process
053: int errorBytes; // number of bytes of process stderr
054: boolean background = false; // Indicates a bg process
055: boolean keepNewline = false; // Retains newline in pipline output
056: Process p; // The exec-ed process
057: String argStr; // Conversion of argv to a string
058: StringBuffer sbuf;
059:
060: // Check for a leading "-keepnewline" argument.
061:
062: for (firstWord = 1; firstWord < argLen; firstWord++) {
063: argStr = argv[firstWord].toString();
064: if ((argStr.length() > 0) && (argStr.charAt(0) == '-')) {
065: if (argStr.equals("-keepnewline")) {
066: keepNewline = true;
067: } else if (argStr.equals("--")) {
068: firstWord++;
069: break;
070: } else {
071: throw new TclException(interp, "bad switch \""
072: + argStr + "\": must be -keepnewline or --");
073: }
074: } else {
075: break;
076: }
077: }
078:
079: if (argLen <= firstWord) {
080: throw new TclNumArgsException(interp, 1, argv,
081: "?switches? arg ?arg ...?");
082: }
083:
084: // See if the command is to be run in background.
085: // Currently this does nothing, it is just for compatibility
086:
087: if (argv[argLen - 1].toString().equals("&")) {
088: argLen--;
089: background = true;
090: }
091:
092: try {
093: p = execCmd(interp, argv, firstWord, argLen);
094:
095: // If user wanted to run in the background, then
096: // don't wait for or attach to the stdout or stderr
097: // streams. We don't actually know what the pid is
098: // so just return a placeholder.
099:
100: if (background) {
101: interp.setResult("pid0");
102: return;
103: }
104:
105: sbuf = new StringBuffer();
106:
107: // Create a pair of threads to read stdout and stderr
108: // of the subprocess. Each stream needs to be handled
109: // by a serarate thread.
110:
111: ExecInputStreamReader stdout_reader = new ExecInputStreamReader(
112: p.getInputStream());
113:
114: ExecInputStreamReader stderr_reader = new ExecInputStreamReader(
115: p.getErrorStream());
116:
117: // Start reading threads, wait for process to terminate in
118: // this thread, then make sure other threads have terminated.
119:
120: stdout_reader.start();
121: stderr_reader.start();
122:
123: if (debug) {
124: System.out
125: .println("started reader threads, invoking waitFor()");
126: }
127:
128: exit = p.waitFor();
129:
130: if (debug) {
131: System.out.println("waitFor() returned " + exit);
132: System.out.println("joining reader threads");
133: }
134:
135: stdout_reader.join();
136: stderr_reader.join();
137:
138: // Get stdout and stderr from other threads.
139:
140: int numBytes;
141: numBytes = stdout_reader.appendBytes(sbuf);
142: if (debug) {
143: System.out.println("appended " + numBytes
144: + " bytes from stdout stream");
145: }
146: numBytes = stderr_reader.appendBytes(sbuf);
147: if (debug) {
148: System.out.println("appended " + numBytes
149: + " bytes from stderr stream");
150: }
151:
152: errorBytes = stderr_reader.getInBytes();
153:
154: // Check for the special case where there is no error
155: // data but the process returns an error result
156:
157: if ((errorBytes == 0) && (exit != 0)) {
158: sbuf.append("child process exited abnormally");
159: }
160:
161: // If the last character of the result buffer is a newline, then
162: // remove the newline character (the newline would just confuse
163: // things). Finally, we set pass the result to the interpreter.
164:
165: int length = sbuf.length();
166: if (!keepNewline && (length > 0)
167: && (sbuf.charAt(length - 1) == '\n')) {
168: sbuf.setLength(length - 1);
169: }
170:
171: // Tcl supports lots of child status conditions.
172: // Unfortunately, we can only find the child's
173: // exit status using the Java API
174:
175: if (exit != 0) {
176: TclObject childstatus = TclList.newInstance();
177: TclList.append(interp, childstatus, TclString
178: .newInstance("CHILDSTATUS"));
179:
180: // We don't know how to find the child's pid
181: TclList.append(interp, childstatus, TclString
182: .newInstance("?PID?"));
183:
184: TclList.append(interp, childstatus, TclInteger
185: .newInstance(exit));
186:
187: interp.setErrorCode(childstatus);
188: }
189:
190: // when the subprocess writes to its stderr stream or returns
191: // a non zero result we generate an error
192:
193: if ((exit != 0) || (errorBytes != 0)) {
194: throw new TclException(interp, sbuf.toString());
195: }
196:
197: // otherwise things went well so set the result
198:
199: interp.setResult(sbuf.toString());
200: } catch (IOException e) {
201: // if exec fails we end up catching the exception here
202:
203: throw new TclException(interp, "couldn't execute \""
204: + argv[firstWord].toString()
205: + "\": no such file or directory");
206:
207: } catch (InterruptedException e) {
208: // Do Nothing...
209: }
210: }
211:
212: /*
213: *----------------------------------------------------------------------
214: *
215: * readStreamIntoBuffer --
216: *
217: * This utility function will read the contents of an InputStream
218: * into a StringBuffer. When done it returns the number of bytes
219: * read from the InputStream. The assumption is an unbuffered stream
220: *
221: * Results:
222: * Returns the number of bytes read from the stream to the buffer
223: *
224: * Side effects:
225: * Data is read from the InputStream.
226: *
227: *----------------------------------------------------------------------
228: */
229:
230: static int readStreamIntoBuffer(InputStream in, StringBuffer sbuf) {
231: int numRead = 0;
232: BufferedReader br = new BufferedReader(
233: new InputStreamReader(in));
234:
235: try {
236: String line = br.readLine();
237:
238: while (line != null) {
239: sbuf.append(line);
240: numRead += line.length();
241: sbuf.append('\n');
242: numRead++;
243: line = br.readLine();
244: }
245: } catch (IOException e) {
246: // do nothing just return numRead
247: if (debug) {
248: System.out.println("IOException during stream read()");
249: e.printStackTrace(System.out);
250: }
251: } finally {
252: try {
253: br.close();
254: } catch (IOException e) {
255: if (debug) {
256: System.out
257: .println("IOException during stream close()");
258: e.printStackTrace(System.out);
259: }
260: }
261: }
262:
263: return numRead;
264: }
265:
266: /*
267: *----------------------------------------------------------------------
268: *
269: * execCmd --
270: *
271: * This procedure is invoked to process the "exec" call assuming the
272: * Runtime.exec( String[] cmdArr, String[] envArr, File currDir )
273: * API exists (introduced in JDK 1.3).
274: *
275: * Results:
276: * Returns the new process.
277: *
278: * Side effects:
279: * See the user documentation.
280: *
281: *----------------------------------------------------------------------
282: */
283:
284: private Process execCmd(Interp interp, TclObject argv[], int first,
285: int last) throws IOException {
286: String[] strv = new String[last - first];
287:
288: for (int i = first, j = 0; i < last; j++, i++) {
289: strv[j] = argv[i].toString();
290: }
291:
292: return Runtime.getRuntime().exec(strv, null,
293: interp.getWorkingDir());
294: }
295:
296: } // end ExecCmd
297:
298: /*
299: *----------------------------------------------------------------------
300: *
301: * ExecInputStreamReader --
302: *
303: * Read data from an input stream into a StringBuffer.
304: * This code is executed in its own thread since some
305: * JDK implementation would deadlock when reading
306: * from a stream after waitFor is invoked.
307: *----------------------------------------------------------------------
308: */
309:
310: class ExecInputStreamReader extends Thread {
311: InputStream in;
312: StringBuffer sb;
313: int inBytes;
314:
315: ExecInputStreamReader(InputStream in) {
316: this .in = in;
317: this .sb = new StringBuffer();
318: inBytes = 0;
319: }
320:
321: public void run() {
322: inBytes = ExecCmd.readStreamIntoBuffer(in, sb);
323: }
324:
325: int appendBytes(StringBuffer dest) {
326: int bytes = sb.length();
327: dest.append(sb.toString());
328: return bytes;
329: }
330:
331: int getInBytes() {
332: return inBytes;
333: }
334: }
|