132
|
1 /*******************************************************************************
|
|
2 copyright: Copyright (c) 2006 Juan Jose Comellas. All rights reserved
|
|
3 license: BSD style: $(LICENSE)
|
|
4 author: Juan Jose Comellas <juanjo@comellas.com.ar>
|
|
5 *******************************************************************************/
|
|
6
|
|
7 module tango.sys.Process;
|
|
8
|
|
9 private import tango.io.FileConst;
|
|
10 private import tango.io.Console;
|
|
11 private import tango.io.Buffer;
|
|
12 private import tango.sys.Common;
|
|
13 private import tango.sys.Pipe;
|
|
14 private import tango.core.Exception;
|
|
15 private import tango.text.Util;
|
|
16 private import Integer = tango.text.convert.Integer;
|
|
17
|
|
18 private import tango.stdc.stdlib;
|
|
19 private import tango.stdc.string;
|
|
20 private import tango.stdc.stringz;
|
|
21
|
|
22 version (Posix)
|
|
23 {
|
|
24 private import tango.stdc.errno;
|
|
25 private import tango.stdc.posix.fcntl;
|
|
26 private import tango.stdc.posix.unistd;
|
|
27 private import tango.stdc.posix.sys.wait;
|
|
28 }
|
|
29
|
|
30 debug (Process)
|
|
31 {
|
|
32 private import tango.io.Stdout;
|
|
33 }
|
|
34
|
|
35
|
|
36 /**
|
|
37 * The Process class is used to start external programs and communicate with
|
|
38 * them via their standard input, output and error streams.
|
|
39 *
|
|
40 * You can pass either the command line or an array of arguments to execute,
|
|
41 * either in the constructor or to the args property. The environment
|
|
42 * variables can be set in a similar way using the env property and you can
|
|
43 * set the program's working directory via the workDir property.
|
|
44 *
|
|
45 * To actually start a process you need to use the execute() method. Once the
|
|
46 * program is running you will be able to write to its standard input via the
|
|
47 * stdin OutputStream and you will be able to read from its standard output and
|
|
48 * error through the stdout and stderr InputStream respectively.
|
|
49 *
|
|
50 * You can check whether the process is running or not with the isRunning()
|
|
51 * method and you can get its process ID via the pid property.
|
|
52 *
|
|
53 * After you are done with the process of if you just want to wait for it to
|
|
54 * end you need to call the wait() method, which will return once the process
|
|
55 * is no longer running.
|
|
56 *
|
|
57 * To stop a running process you must use kill() method. If you do this you
|
|
58 * cannot call the wait() method. Once the kill() method returns the process
|
|
59 * will be already dead.
|
|
60 *
|
|
61 * Examples:
|
|
62 * ---
|
|
63 * try
|
|
64 * {
|
|
65 * auto p = new Process ("ls -al", null);
|
|
66 * p.execute;
|
|
67 *
|
|
68 * Stdout.formatln ("Output from {}:", p.programName);
|
|
69 * Stdout.copy (p.stdout).flush;
|
|
70 * auto result = p.wait;
|
|
71 *
|
|
72 * Stdout.formatln ("Process '{}' ({}) exited with reason {}, status {}",
|
|
73 * p.programName, p.pid, cast(int) result.reason, result.status);
|
|
74 * }
|
|
75 * catch (ProcessException e)
|
|
76 * Stdout.formatln ("Process execution failed: {}", e);
|
|
77 * ---
|
|
78 */
|
|
79 class Process
|
|
80 {
|
|
81 /**
|
|
82 * Result returned by wait().
|
|
83 */
|
|
84 public struct Result
|
|
85 {
|
|
86 /**
|
|
87 * Reasons returned by wait() indicating why the process is no
|
|
88 * longer running.
|
|
89 */
|
|
90 public enum
|
|
91 {
|
|
92 Exit,
|
|
93 Signal,
|
|
94 Stop,
|
|
95 Continue,
|
|
96 Error
|
|
97 }
|
|
98
|
|
99 public int reason;
|
|
100 public int status;
|
|
101
|
|
102 /**
|
|
103 * Returns a string with a description of the process execution result.
|
|
104 */
|
|
105 public char[] toString()
|
|
106 {
|
|
107 char[] str;
|
|
108
|
|
109 switch (reason)
|
|
110 {
|
|
111 case Exit:
|
|
112 str = format("Process exited normally with return code ", status);
|
|
113 break;
|
|
114
|
|
115 case Signal:
|
|
116 str = format("Process was killed with signal ", status);
|
|
117 break;
|
|
118
|
|
119 case Stop:
|
|
120 str = format("Process was stopped with signal ", status);
|
|
121 break;
|
|
122
|
|
123 case Continue:
|
|
124 str = format("Process was resumed with signal ", status);
|
|
125 break;
|
|
126
|
|
127 case Error:
|
|
128 str = format("Process failed with error code ", reason) ~
|
|
129 " : " ~ SysError.lookup(status);
|
|
130 break;
|
|
131
|
|
132 default:
|
|
133 str = format("Unknown process result ", reason);
|
|
134 break;
|
|
135 }
|
|
136 return str;
|
|
137 }
|
|
138 }
|
|
139
|
|
140 static const uint DefaultStdinBufferSize = 512;
|
|
141 static const uint DefaultStdoutBufferSize = 8192;
|
|
142 static const uint DefaultStderrBufferSize = 512;
|
|
143
|
|
144 private char[][] _args;
|
|
145 private char[][char[]] _env;
|
|
146 private char[] _workDir;
|
|
147 private PipeConduit _stdin;
|
|
148 private PipeConduit _stdout;
|
|
149 private PipeConduit _stderr;
|
|
150 private bool _running = false;
|
|
151
|
|
152 version (Windows)
|
|
153 {
|
|
154 private PROCESS_INFORMATION *_info = null;
|
|
155 }
|
|
156 else
|
|
157 {
|
|
158 private pid_t _pid = cast(pid_t) -1;
|
|
159 }
|
|
160
|
|
161 /**
|
|
162 * Constructor (variadic version).
|
|
163 *
|
|
164 * Params:
|
|
165 * args = array of strings with the process' arguments; the first
|
|
166 * argument must be the process' name; the arguments can be
|
|
167 * empty.
|
|
168 *
|
|
169 * Examples:
|
|
170 * ---
|
|
171 * auto p = new Process("myprogram", "first argument", "second", "third")
|
|
172 * ---
|
|
173 */
|
|
174 public this(char[][] args ...)
|
|
175 {
|
|
176 _args = args;
|
|
177 }
|
|
178
|
|
179 /**
|
|
180 * Constructor.
|
|
181 *
|
|
182 * Params:
|
|
183 * command = string with the process' command line; arguments that have
|
|
184 * embedded whitespace must be enclosed in inside double-quotes (").
|
|
185 * env = associative array of strings with the process' environment
|
|
186 * variables; the variable name must be the key of each entry.
|
|
187 *
|
|
188 * Examples:
|
|
189 * ---
|
|
190 * char[] command = "myprogram \"first argument\" second third";
|
|
191 * char[][char[]] env;
|
|
192 *
|
|
193 * // Environment variables
|
|
194 * env["MYVAR1"] = "first";
|
|
195 * env["MYVAR2"] = "second";
|
|
196 *
|
|
197 * auto p = new Process(command, env)
|
|
198 * ---
|
|
199 */
|
|
200 public this(char[] command, char[][char[]] env)
|
|
201 in
|
|
202 {
|
|
203 assert(command.length > 0);
|
|
204 }
|
|
205 body
|
|
206 {
|
|
207 _args = splitArgs(command);
|
|
208 _env = env;
|
|
209 }
|
|
210
|
|
211 /**
|
|
212 * Constructor.
|
|
213 *
|
|
214 * Params:
|
|
215 * args = array of strings with the process' arguments; the first
|
|
216 * argument must be the process' name; the arguments can be
|
|
217 * empty.
|
|
218 * env = associative array of strings with the process' environment
|
|
219 * variables; the variable name must be the key of each entry.
|
|
220 *
|
|
221 * Examples:
|
|
222 * ---
|
|
223 * char[][] args;
|
|
224 * char[][char[]] env;
|
|
225 *
|
|
226 * // Process name
|
|
227 * args ~= "myprogram";
|
|
228 * // Process arguments
|
|
229 * args ~= "first argument";
|
|
230 * args ~= "second";
|
|
231 * args ~= "third";
|
|
232 *
|
|
233 * // Environment variables
|
|
234 * env["MYVAR1"] = "first";
|
|
235 * env["MYVAR2"] = "second";
|
|
236 *
|
|
237 * auto p = new Process(args, env)
|
|
238 * ---
|
|
239 */
|
|
240 public this(char[][] args, char[][char[]] env)
|
|
241 in
|
|
242 {
|
|
243 assert(args.length > 0);
|
|
244 assert(args[0].length > 0);
|
|
245 }
|
|
246 body
|
|
247 {
|
|
248 _args = args;
|
|
249 _env = env;
|
|
250 }
|
|
251
|
|
252 /**
|
|
253 * Indicate whether the process is running or not.
|
|
254 */
|
|
255 public bool isRunning()
|
|
256 {
|
|
257 return _running;
|
|
258 }
|
|
259
|
|
260 /**
|
|
261 * Return the running process' ID.
|
|
262 *
|
|
263 * Returns: an int with the process ID if the process is running;
|
|
264 * -1 if not.
|
|
265 */
|
|
266 public int pid()
|
|
267 {
|
|
268 version (Windows)
|
|
269 {
|
|
270 return (_info !is null ? cast(int) _info.dwProcessId : -1);
|
|
271 }
|
|
272 else // version (Posix)
|
|
273 {
|
|
274 return cast(int) _pid;
|
|
275 }
|
|
276 }
|
|
277
|
|
278 /**
|
|
279 * Return the process' executable filename.
|
|
280 */
|
|
281 public char[] programName()
|
|
282 {
|
|
283 return (_args !is null ? _args[0] : null);
|
|
284 }
|
|
285
|
|
286 /**
|
|
287 * Set the process' executable filename.
|
|
288 */
|
|
289 public void programName(char[] name)
|
|
290 {
|
|
291 if (_args.length == 0)
|
|
292 {
|
|
293 _args.length = 1;
|
|
294 }
|
|
295 _args[0] = name;
|
|
296 }
|
|
297
|
|
298 /**
|
|
299 * Return an array with the process' arguments.
|
|
300 */
|
|
301 public char[][] args()
|
|
302 {
|
|
303 return _args;
|
|
304 }
|
|
305
|
|
306 /**
|
|
307 * Set the process' arguments from the arguments received by the method.
|
|
308 *
|
|
309 * Remarks:
|
|
310 * The first element of the array must be the name of the process'
|
|
311 * executable.
|
|
312 *
|
|
313 * Examples:
|
|
314 * ---
|
|
315 * p.args("myprogram", "first", "second argument", "third");
|
|
316 * ---
|
|
317 */
|
|
318 public void args(char[][] args ...)
|
|
319 {
|
|
320 _args = args;
|
|
321 }
|
|
322
|
|
323 /**
|
|
324 * Return an associative array with the process' environment variables.
|
|
325 */
|
|
326 public char[][char[]] env()
|
|
327 {
|
|
328 return _env;
|
|
329 }
|
|
330
|
|
331 /**
|
|
332 * Set the process' environment variables from the associative array
|
|
333 * received by the method.
|
|
334 *
|
|
335 * Params:
|
|
336 * env = associative array of strings containing the environment
|
|
337 * variables for the process. The variable name should be the key
|
|
338 * used for each entry.
|
|
339 *
|
|
340 * Examples:
|
|
341 * ---
|
|
342 * char[][char[]] env;
|
|
343 *
|
|
344 * env["MYVAR1"] = "first";
|
|
345 * env["MYVAR2"] = "second";
|
|
346 *
|
|
347 * p.env = env;
|
|
348 * ---
|
|
349 */
|
|
350 public void env(char[][char[]] env)
|
|
351 {
|
|
352 _env = env;
|
|
353 }
|
|
354
|
|
355 /**
|
|
356 * Return an UTF-8 string with the process' command line.
|
|
357 */
|
|
358 public char[] toString()
|
|
359 {
|
|
360 char[] command;
|
|
361
|
|
362 for (uint i = 0; i < _args.length; ++i)
|
|
363 {
|
|
364 if (i > 0)
|
|
365 {
|
|
366 command ~= ' ';
|
|
367 }
|
|
368 if (contains(_args[i], ' ') || _args[i].length == 0)
|
|
369 {
|
|
370 command ~= '"';
|
|
371 command ~= _args[i];
|
|
372 command ~= '"';
|
|
373 }
|
|
374 else
|
|
375 {
|
|
376 command ~= _args[i];
|
|
377 }
|
|
378 }
|
|
379 return command;
|
|
380 }
|
|
381
|
|
382 /**
|
|
383 * Return the working directory for the process.
|
|
384 *
|
|
385 * Returns: a string with the working directory; null if the working
|
|
386 * directory is the current directory.
|
|
387 */
|
|
388 public char[] workDir()
|
|
389 {
|
|
390 return _workDir;
|
|
391 }
|
|
392
|
|
393 /**
|
|
394 * Set the working directory for the process.
|
|
395 *
|
|
396 * Params:
|
|
397 * dir = a string with the working directory; null if the working
|
|
398 * directory is the current directory.
|
|
399 */
|
|
400 public void workDir(char[] dir)
|
|
401 {
|
|
402 _workDir = dir;
|
|
403 }
|
|
404
|
|
405 /**
|
|
406 * Return the running process' standard input pipe.
|
|
407 *
|
|
408 * Returns: a write-only PipeConduit connected to the child
|
|
409 * process' stdin.
|
|
410 *
|
|
411 * Remarks:
|
|
412 * The stream will be null if no child process has been executed.
|
|
413 */
|
|
414 public PipeConduit stdin()
|
|
415 {
|
|
416 return _stdin;
|
|
417 }
|
|
418
|
|
419 /**
|
|
420 * Return the running process' standard output pipe.
|
|
421 *
|
|
422 * Returns: a read-only PipeConduit connected to the child
|
|
423 * process' stdout.
|
|
424 *
|
|
425 * Remarks:
|
|
426 * The stream will be null if no child process has been executed.
|
|
427 */
|
|
428 public PipeConduit stdout()
|
|
429 {
|
|
430 return _stdout;
|
|
431 }
|
|
432
|
|
433 /**
|
|
434 * Return the running process' standard error pipe.
|
|
435 *
|
|
436 * Returns: a read-only PipeConduit connected to the child
|
|
437 * process' stderr.
|
|
438 *
|
|
439 * Remarks:
|
|
440 * The stream will be null if no child process has been executed.
|
|
441 */
|
|
442 public PipeConduit stderr()
|
|
443 {
|
|
444 return _stderr;
|
|
445 }
|
|
446
|
|
447 /**
|
|
448 * Execute a process using the arguments as parameters to this method.
|
|
449 *
|
|
450 * Once the process is executed successfully, its input and output can be
|
|
451 * manipulated through the stdin, stdout and
|
|
452 * stderr member PipeConduit's.
|
|
453 *
|
|
454 * Throws:
|
|
455 * ProcessCreateException if the process could not be created
|
|
456 * successfully; ProcessForkException if the call to the fork()
|
|
457 * system call failed (on POSIX-compatible platforms).
|
|
458 *
|
|
459 * Remarks:
|
|
460 * The process must not be running and the provided list of arguments must
|
|
461 * not be empty. If there was any argument already present in the args
|
|
462 * member, they will be replaced by the arguments supplied to the method.
|
|
463 */
|
|
464 public void execute(char[][] args ...)
|
|
465 in
|
|
466 {
|
|
467 assert(!_running);
|
|
468 }
|
|
469 body
|
|
470 {
|
|
471 if (args.length > 0 && args[0] !is null)
|
|
472 {
|
|
473 _args = args;
|
|
474 }
|
|
475 executeInternal();
|
|
476 }
|
|
477
|
|
478 /**
|
|
479 * Execute a process using the command line arguments as parameters to
|
|
480 * this method.
|
|
481 *
|
|
482 * Once the process is executed successfully, its input and output can be
|
|
483 * manipulated through the stdin, stdout and
|
|
484 * stderr member PipeConduit's.
|
|
485 *
|
|
486 * Params:
|
|
487 * command = string with the process' command line; arguments that have
|
|
488 * embedded whitespace must be enclosed in inside double-quotes (").
|
|
489 * env = associative array of strings with the process' environment
|
|
490 * variables; the variable name must be the key of each entry.
|
|
491 *
|
|
492 * Throws:
|
|
493 * ProcessCreateException if the process could not be created
|
|
494 * successfully; ProcessForkException if the call to the fork()
|
|
495 * system call failed (on POSIX-compatible platforms).
|
|
496 *
|
|
497 * Remarks:
|
|
498 * The process must not be running and the provided list of arguments must
|
|
499 * not be empty. If there was any argument already present in the args
|
|
500 * member, they will be replaced by the arguments supplied to the method.
|
|
501 */
|
|
502 public void execute(char[] command, char[][char[]] env)
|
|
503 in
|
|
504 {
|
|
505 assert(!_running);
|
|
506 assert(command.length > 0);
|
|
507 }
|
|
508 body
|
|
509 {
|
|
510 _args = splitArgs(command);
|
|
511 _env = env;
|
|
512
|
|
513 executeInternal();
|
|
514 }
|
|
515
|
|
516 /**
|
|
517 * Execute a process using the command line arguments as parameters to
|
|
518 * this method.
|
|
519 *
|
|
520 * Once the process is executed successfully, its input and output can be
|
|
521 * manipulated through the stdin, stdout and
|
|
522 * stderr member PipeConduit's.
|
|
523 *
|
|
524 * Params:
|
|
525 * args = array of strings with the process' arguments; the first
|
|
526 * argument must be the process' name; the arguments can be
|
|
527 * empty.
|
|
528 * env = associative array of strings with the process' environment
|
|
529 * variables; the variable name must be the key of each entry.
|
|
530 *
|
|
531 * Throws:
|
|
532 * ProcessCreateException if the process could not be created
|
|
533 * successfully; ProcessForkException if the call to the fork()
|
|
534 * system call failed (on POSIX-compatible platforms).
|
|
535 *
|
|
536 * Remarks:
|
|
537 * The process must not be running and the provided list of arguments must
|
|
538 * not be empty. If there was any argument already present in the args
|
|
539 * member, they will be replaced by the arguments supplied to the method.
|
|
540 *
|
|
541 * Examples:
|
|
542 * ---
|
|
543 * auto p = new Process();
|
|
544 * char[][] args;
|
|
545 *
|
|
546 * args ~= "ls";
|
|
547 * args ~= "-l";
|
|
548 *
|
|
549 * p.execute(args, null);
|
|
550 * ---
|
|
551 */
|
|
552 public void execute(char[][] args, char[][char[]] env)
|
|
553 in
|
|
554 {
|
|
555 assert(!_running);
|
|
556 assert(args.length > 0);
|
|
557 }
|
|
558 body
|
|
559 {
|
|
560 _args = args;
|
|
561 _env = env;
|
|
562
|
|
563 executeInternal();
|
|
564 }
|
|
565
|
|
566 /**
|
|
567 * Execute a process using the arguments that were supplied to the
|
|
568 * constructor or to the args property.
|
|
569 *
|
|
570 * Once the process is executed successfully, its input and output can be
|
|
571 * manipulated through the stdin, stdout and
|
|
572 * stderr member PipeConduit's.
|
|
573 *
|
|
574 * Throws:
|
|
575 * ProcessCreateException if the process could not be created
|
|
576 * successfully; ProcessForkException if the call to the fork()
|
|
577 * system call failed (on POSIX-compatible platforms).
|
|
578 *
|
|
579 * Remarks:
|
|
580 * The process must not be running and the list of arguments must
|
|
581 * not be empty before calling this method.
|
|
582 */
|
|
583 protected void executeInternal()
|
|
584 in
|
|
585 {
|
|
586 assert(!_running);
|
|
587 assert(_args.length > 0 && _args[0] !is null);
|
|
588 }
|
|
589 body
|
|
590 {
|
|
591 version (Windows)
|
|
592 {
|
|
593 SECURITY_ATTRIBUTES sa;
|
|
594 STARTUPINFO startup;
|
|
595
|
|
596 // We close and delete the pipes that could have been left open
|
|
597 // from a previous execution.
|
|
598 cleanPipes();
|
|
599
|
|
600 // Set up the security attributes struct.
|
|
601 sa.nLength = SECURITY_ATTRIBUTES.sizeof;
|
|
602 sa.lpSecurityDescriptor = null;
|
|
603 sa.bInheritHandle = true;
|
|
604
|
|
605 // Set up members of the STARTUPINFO structure.
|
|
606 memset(&startup, '\0', STARTUPINFO.sizeof);
|
|
607 startup.cb = STARTUPINFO.sizeof;
|
|
608 startup.dwFlags |= STARTF_USESTDHANDLES;
|
|
609
|
|
610 // Create the pipes used to communicate with the child process.
|
|
611 Pipe pin = new Pipe(DefaultStdinBufferSize, &sa);
|
|
612 // Replace stdin with the "read" pipe
|
|
613 _stdin = pin.sink;
|
|
614 startup.hStdInput = cast(HANDLE) pin.source.fileHandle();
|
|
615 // Ensure the write handle to the pipe for STDIN is not inherited.
|
|
616 SetHandleInformation(cast(HANDLE) pin.sink.fileHandle(), HANDLE_FLAG_INHERIT, 0);
|
|
617 scope(exit)
|
|
618 pin.source.close();
|
|
619
|
|
620 Pipe pout = new Pipe(DefaultStdoutBufferSize, &sa);
|
|
621 // Replace stdout with the "write" pipe
|
|
622 _stdout = pout.source;
|
|
623 startup.hStdOutput = cast(HANDLE) pout.sink.fileHandle();
|
|
624 // Ensure the read handle to the pipe for STDOUT is not inherited.
|
|
625 SetHandleInformation(cast(HANDLE) pout.source.fileHandle(), HANDLE_FLAG_INHERIT, 0);
|
|
626 scope(exit)
|
|
627 pout.sink.close();
|
|
628
|
|
629 Pipe perr = new Pipe(DefaultStderrBufferSize, &sa);
|
|
630 // Replace stderr with the "write" pipe
|
|
631 _stderr = perr.source;
|
|
632 startup.hStdError = cast(HANDLE) perr.sink.fileHandle();
|
|
633 // Ensure the read handle to the pipe for STDOUT is not inherited.
|
|
634 SetHandleInformation(cast(HANDLE) perr.source.fileHandle(), HANDLE_FLAG_INHERIT, 0);
|
|
635 scope(exit)
|
|
636 perr.sink.close();
|
|
637
|
|
638 _info = new PROCESS_INFORMATION;
|
|
639 // Set up members of the PROCESS_INFORMATION structure.
|
|
640 memset(_info, '\0', PROCESS_INFORMATION.sizeof);
|
|
641
|
|
642 char[] command = toString();
|
|
643 command ~= '\0';
|
|
644
|
|
645 // Convert the working directory to a null-ended string if
|
|
646 // necessary.
|
|
647 if (CreateProcessA(null, command.ptr, null, null, true,
|
|
648 DETACHED_PROCESS,
|
|
649 (_env.length > 0 ? toNullEndedBuffer(_env).ptr : null),
|
|
650 toStringz(_workDir), &startup, _info))
|
|
651 {
|
|
652 CloseHandle(_info.hThread);
|
|
653 _running = true;
|
|
654 }
|
|
655 else
|
|
656 {
|
|
657 throw new ProcessCreateException(_args[0], __FILE__, __LINE__);
|
|
658 }
|
|
659 }
|
|
660 else version (Posix)
|
|
661 {
|
|
662 // We close and delete the pipes that could have been left open
|
|
663 // from a previous execution.
|
|
664 cleanPipes();
|
|
665
|
|
666 Pipe pin = new Pipe(DefaultStdinBufferSize);
|
|
667 Pipe pout = new Pipe(DefaultStdoutBufferSize);
|
|
668 Pipe perr = new Pipe(DefaultStderrBufferSize);
|
|
669 // This pipe is used to propagate the result of the call to
|
|
670 // execv*() from the child process to the parent process.
|
|
671 Pipe pexec = new Pipe(8);
|
|
672 int status = 0;
|
|
673
|
|
674 _pid = fork();
|
|
675 if (_pid >= 0)
|
|
676 {
|
|
677 if (_pid != 0)
|
|
678 {
|
|
679 // Parent process
|
|
680 _stdin = pin.sink;
|
|
681 pin.source.close();
|
|
682
|
|
683 _stdout = pout.source;
|
|
684 pout.sink.close();
|
|
685
|
|
686 _stderr = perr.source;
|
|
687 perr.sink.close();
|
|
688
|
|
689 pexec.sink.close();
|
|
690 scope(exit)
|
|
691 pexec.source.close();
|
|
692
|
|
693 try
|
|
694 {
|
|
695 pexec.source.input.read((cast(byte*) &status)[0 .. status.sizeof]);
|
|
696 }
|
|
697 catch (Exception e)
|
|
698 {
|
|
699 // Everything's OK, the pipe was closed after the call to execv*()
|
|
700 }
|
|
701
|
|
702 if (status == 0)
|
|
703 {
|
|
704 _running = true;
|
|
705 }
|
|
706 else
|
|
707 {
|
|
708 // We set errno to the value that was sent through
|
|
709 // the pipe from the child process
|
|
710 errno = status;
|
|
711 _running = false;
|
|
712
|
|
713 throw new ProcessCreateException(_args[0], __FILE__, __LINE__);
|
|
714 }
|
|
715 }
|
|
716 else
|
|
717 {
|
|
718 // Child process
|
|
719 int rc;
|
|
720 char*[] argptr;
|
|
721 char*[] envptr;
|
|
722
|
|
723 // Replace stdin with the "read" pipe
|
|
724 dup2(pin.source.fileHandle(), STDIN_FILENO);
|
|
725 pin.sink().close();
|
|
726 scope(exit)
|
|
727 pin.source.close();
|
|
728
|
|
729 // Replace stdout with the "write" pipe
|
|
730 dup2(pout.sink.fileHandle(), STDOUT_FILENO);
|
|
731 pout.source.close();
|
|
732 scope(exit)
|
|
733 pout.sink.close();
|
|
734
|
|
735 // Replace stderr with the "write" pipe
|
|
736 dup2(perr.sink.fileHandle(), STDERR_FILENO);
|
|
737 perr.source.close();
|
|
738 scope(exit)
|
|
739 perr.sink.close();
|
|
740
|
|
741 // We close the unneeded part of the execv*() notification pipe
|
|
742 pexec.source.close();
|
|
743 scope(exit)
|
|
744 pexec.sink.close();
|
|
745 // Set the "write" pipe so that it closes upon a successful
|
|
746 // call to execv*()
|
|
747 if (fcntl(cast(int) pexec.sink.fileHandle(), F_SETFD, FD_CLOEXEC) == 0)
|
|
748 {
|
|
749 // Convert the arguments and the environment variables to
|
|
750 // the format expected by the execv() family of functions.
|
|
751 argptr = toNullEndedArray(_args);
|
|
752 envptr = (_env.length > 0 ? toNullEndedArray(_env) : null);
|
|
753
|
|
754 // Switch to the working directory if it has been set.
|
|
755 if (_workDir.length > 0)
|
|
756 {
|
|
757 chdir(toStringz(_workDir));
|
|
758 }
|
|
759
|
|
760 // Replace the child fork with a new process. We always use the
|
|
761 // system PATH to look for executables that don't specify
|
|
762 // directories in their names.
|
|
763 rc = execvpe(_args[0], argptr, envptr);
|
|
764 if (rc == -1)
|
|
765 {
|
|
766 Cerr("Failed to exec ")(_args[0])(": ")(SysError.lastMsg).newline;
|
|
767
|
|
768 try
|
|
769 {
|
|
770 status = errno;
|
|
771
|
|
772 // Propagate the child process' errno value to
|
|
773 // the parent process.
|
|
774 pexec.sink.output.write((cast(byte*) &status)[0 .. status.sizeof]);
|
|
775 }
|
|
776 catch (Exception e)
|
|
777 {
|
|
778 }
|
|
779 exit(errno);
|
|
780 }
|
|
781 }
|
|
782 else
|
|
783 {
|
|
784 Cerr("Failed to set notification pipe to close-on-exec for ")
|
|
785 (_args[0])(": ")(SysError.lastMsg).newline;
|
|
786 exit(errno);
|
|
787 }
|
|
788 }
|
|
789 }
|
|
790 else
|
|
791 {
|
|
792 throw new ProcessForkException(_pid, __FILE__, __LINE__);
|
|
793 }
|
|
794 }
|
|
795 else
|
|
796 {
|
|
797 assert(false, "tango.sys.Process: Unsupported platform");
|
|
798 }
|
|
799 }
|
|
800
|
|
801
|
|
802 /**
|
|
803 * Unconditionally wait for a process to end and return the reason and
|
|
804 * status code why the process ended.
|
|
805 *
|
|
806 * Returns:
|
|
807 * The return value is a Result struct, which has two members:
|
|
808 * reason and status. The reason can take the
|
|
809 * following values:
|
|
810 *
|
|
811 * Process.Result.Exit: the child process exited normally;
|
|
812 * status has the process' return
|
|
813 * code.
|
|
814 *
|
|
815 * Process.Result.Signal: the child process was killed by a signal;
|
|
816 * status has the signal number
|
|
817 * that killed the process.
|
|
818 *
|
|
819 * Process.Result.Stop: the process was stopped; status
|
|
820 * has the signal number that was used to stop
|
|
821 * the process.
|
|
822 *
|
|
823 * Process.Result.Continue: the process had been previously stopped
|
|
824 * and has now been restarted;
|
|
825 * status has the signal number
|
|
826 * that was used to continue the process.
|
|
827 *
|
|
828 * Process.Result.Error: We could not properly wait on the child
|
|
829 * process; status has the
|
|
830 * errno value if the process was
|
|
831 * running and -1 if not.
|
|
832 *
|
|
833 * Remarks:
|
|
834 * You can only call wait() on a running process once. The Signal, Stop
|
|
835 * and Continue reasons will only be returned on POSIX-compatible
|
|
836 * platforms.
|
|
837 */
|
|
838 public Result wait()
|
|
839 {
|
|
840 version (Windows)
|
|
841 {
|
|
842 Result result;
|
|
843
|
|
844 if (_running)
|
|
845 {
|
|
846 DWORD rc;
|
|
847 DWORD exitCode;
|
|
848
|
|
849 assert(_info !is null);
|
|
850
|
|
851 // We clean up the process related data and set the _running
|
|
852 // flag to false once we're done waiting for the process to
|
|
853 // finish.
|
|
854 //
|
|
855 // IMPORTANT: we don't delete the open pipes so that the parent
|
|
856 // process can get whatever the child process left on
|
|
857 // these pipes before dying.
|
|
858 scope(exit)
|
|
859 {
|
|
860 CloseHandle(_info.hProcess);
|
|
861 _running = false;
|
|
862 }
|
|
863
|
|
864 rc = WaitForSingleObject(_info.hProcess, INFINITE);
|
|
865 if (rc == WAIT_OBJECT_0)
|
|
866 {
|
|
867 GetExitCodeProcess(_info.hProcess, &exitCode);
|
|
868
|
|
869 result.reason = Result.Exit;
|
|
870 result.status = cast(typeof(result.status)) exitCode;
|
|
871
|
|
872 debug (Process)
|
|
873 Stdout.formatln("Child process '{0}' ({1}) returned with code {2}\n",
|
|
874 _args[0], _pid, result.status);
|
|
875 }
|
|
876 else if (rc == WAIT_FAILED)
|
|
877 {
|
|
878 result.reason = Result.Error;
|
|
879 result.status = cast(short) GetLastError();
|
|
880
|
|
881 debug (Process)
|
|
882 Stdout.formatln("Child process '{0}' ({1}) failed "
|
|
883 "with unknown exit status {2}\n",
|
|
884 _args[0], _pid, result.status);
|
|
885 }
|
|
886 }
|
|
887 else
|
|
888 {
|
|
889 result.reason = Result.Error;
|
|
890 result.status = -1;
|
|
891
|
|
892 debug (Process)
|
|
893 Stdout.formatln("Child process '{0}' is not running", _args[0]);
|
|
894 }
|
|
895 return result;
|
|
896 }
|
|
897 else version (Posix)
|
|
898 {
|
|
899 Result result;
|
|
900
|
|
901 if (_running)
|
|
902 {
|
|
903 int rc;
|
|
904
|
|
905 // We clean up the process related data and set the _running
|
|
906 // flag to false once we're done waiting for the process to
|
|
907 // finish.
|
|
908 //
|
|
909 // IMPORTANT: we don't delete the open pipes so that the parent
|
|
910 // process can get whatever the child process left on
|
|
911 // these pipes before dying.
|
|
912 scope(exit)
|
|
913 {
|
|
914 _running = false;
|
|
915 }
|
|
916
|
|
917 // Wait for child process to end.
|
|
918 if (waitpid(_pid, &rc, 0) != -1)
|
|
919 {
|
|
920 if (WIFEXITED(rc))
|
|
921 {
|
|
922 result.reason = Result.Exit;
|
|
923 result.status = WEXITSTATUS(rc);
|
|
924 if (result.status != 0)
|
|
925 {
|
|
926 debug (Process)
|
|
927 Stdout.formatln("Child process '{0}' ({1}) returned with code {2}\n",
|
|
928 _args[0], _pid, result.status);
|
|
929 }
|
|
930 }
|
|
931 else
|
|
932 {
|
|
933 if (WIFSIGNALED(rc))
|
|
934 {
|
|
935 result.reason = Result.Signal;
|
|
936 result.status = WTERMSIG(rc);
|
|
937
|
|
938 debug (Process)
|
|
939 Stdout.formatln("Child process '{0}' ({1}) was killed prematurely "
|
|
940 "with signal {2}",
|
|
941 _args[0], _pid, result.status);
|
|
942 }
|
|
943 else if (WIFSTOPPED(rc))
|
|
944 {
|
|
945 result.reason = Result.Stop;
|
|
946 result.status = WSTOPSIG(rc);
|
|
947
|
|
948 debug (Process)
|
|
949 Stdout.formatln("Child process '{0}' ({1}) was stopped "
|
|
950 "with signal {2}",
|
|
951 _args[0], _pid, result.status);
|
|
952 }
|
|
953 else if (WIFCONTINUED(rc))
|
|
954 {
|
|
955 result.reason = Result.Stop;
|
|
956 result.status = WSTOPSIG(rc);
|
|
957
|
|
958 debug (Process)
|
|
959 Stdout.formatln("Child process '{0}' ({1}) was continued "
|
|
960 "with signal {2}",
|
|
961 _args[0], _pid, result.status);
|
|
962 }
|
|
963 else
|
|
964 {
|
|
965 result.reason = Result.Error;
|
|
966 result.status = rc;
|
|
967
|
|
968 debug (Process)
|
|
969 Stdout.formatln("Child process '{0}' ({1}) failed "
|
|
970 "with unknown exit status {2}\n",
|
|
971 _args[0], _pid, result.status);
|
|
972 }
|
|
973 }
|
|
974 }
|
|
975 else
|
|
976 {
|
|
977 result.reason = Result.Error;
|
|
978 result.status = errno;
|
|
979
|
|
980 debug (Process)
|
|
981 Stdout.formatln("Could not wait on child process '{0}' ({1}): ({2}) {3}",
|
|
982 _args[0], _pid, result.status, SysError.lastMsg);
|
|
983 }
|
|
984 }
|
|
985 else
|
|
986 {
|
|
987 result.reason = Result.Error;
|
|
988 result.status = -1;
|
|
989
|
|
990 debug (Process)
|
|
991 Stdout.formatln("Child process '{0}' is not running", _args[0]);
|
|
992 }
|
|
993 return result;
|
|
994 }
|
|
995 else
|
|
996 {
|
|
997 assert(false, "tango.sys.Process: Unsupported platform");
|
|
998 }
|
|
999 }
|
|
1000
|
|
1001 /**
|
|
1002 * Kill a running process. This method will not return until the process
|
|
1003 * has been killed.
|
|
1004 *
|
|
1005 * Throws:
|
|
1006 * ProcessKillException if the process could not be killed;
|
|
1007 * ProcessWaitException if we could not wait on the process after
|
|
1008 * killing it.
|
|
1009 *
|
|
1010 * Remarks:
|
|
1011 * After calling this method you will not be able to call wait() on the
|
|
1012 * process.
|
|
1013 */
|
|
1014 public void kill()
|
|
1015 {
|
|
1016 version (Windows)
|
|
1017 {
|
|
1018 if (_running)
|
|
1019 {
|
|
1020 assert(_info !is null);
|
|
1021
|
|
1022 if (TerminateProcess(_info.hProcess, cast(UINT) -1))
|
|
1023 {
|
|
1024 assert(_info !is null);
|
|
1025
|
|
1026 // We clean up the process related data and set the _running
|
|
1027 // flag to false once we're done waiting for the process to
|
|
1028 // finish.
|
|
1029 //
|
|
1030 // IMPORTANT: we don't delete the open pipes so that the parent
|
|
1031 // process can get whatever the child process left on
|
|
1032 // these pipes before dying.
|
|
1033 scope(exit)
|
|
1034 {
|
|
1035 CloseHandle(_info.hProcess);
|
|
1036 _running = false;
|
|
1037 }
|
|
1038
|
|
1039 // FIXME: We should probably use a timeout here
|
|
1040 if (WaitForSingleObject(_info.hProcess, INFINITE) == WAIT_FAILED)
|
|
1041 {
|
|
1042 throw new ProcessWaitException(cast(int) _info.dwProcessId,
|
|
1043 __FILE__, __LINE__);
|
|
1044 }
|
|
1045 }
|
|
1046 else
|
|
1047 {
|
|
1048 throw new ProcessKillException(cast(int) _info.dwProcessId,
|
|
1049 __FILE__, __LINE__);
|
|
1050 }
|
|
1051 }
|
|
1052 else
|
|
1053 {
|
|
1054 debug (Process)
|
|
1055 Stdout.print("Tried to kill an invalid process");
|
|
1056 }
|
|
1057 }
|
|
1058 else version (Posix)
|
|
1059 {
|
|
1060 if (_running)
|
|
1061 {
|
|
1062 int rc;
|
|
1063
|
|
1064 assert(_pid > 0);
|
|
1065
|
|
1066 if (.kill(_pid, SIGTERM) != -1)
|
|
1067 {
|
|
1068 // We clean up the process related data and set the _running
|
|
1069 // flag to false once we're done waiting for the process to
|
|
1070 // finish.
|
|
1071 //
|
|
1072 // IMPORTANT: we don't delete the open pipes so that the parent
|
|
1073 // process can get whatever the child process left on
|
|
1074 // these pipes before dying.
|
|
1075 scope(exit)
|
|
1076 {
|
|
1077 _running = false;
|
|
1078 }
|
|
1079
|
|
1080 // FIXME: is this loop really needed?
|
|
1081 for (uint i = 0; i < 100; i++)
|
|
1082 {
|
|
1083 rc = waitpid(pid, null, WNOHANG | WUNTRACED);
|
|
1084 if (rc == _pid)
|
|
1085 {
|
|
1086 break;
|
|
1087 }
|
|
1088 else if (rc == -1)
|
|
1089 {
|
|
1090 throw new ProcessWaitException(cast(int) _pid, __FILE__, __LINE__);
|
|
1091 }
|
|
1092 usleep(50000);
|
|
1093 }
|
|
1094 }
|
|
1095 else
|
|
1096 {
|
|
1097 throw new ProcessKillException(_pid, __FILE__, __LINE__);
|
|
1098 }
|
|
1099 }
|
|
1100 else
|
|
1101 {
|
|
1102 debug (Process)
|
|
1103 Stdout.print("Tried to kill an invalid process");
|
|
1104 }
|
|
1105 }
|
|
1106 else
|
|
1107 {
|
|
1108 assert(false, "tango.sys.Process: Unsupported platform");
|
|
1109 }
|
|
1110 }
|
|
1111
|
|
1112 /**
|
|
1113 * Split a string containing the command line used to invoke a program
|
|
1114 * and return and array with the parsed arguments. The double-quotes (")
|
|
1115 * character can be used to specify arguments with embedded spaces.
|
|
1116 * e.g. first "second param" third
|
|
1117 */
|
|
1118 protected static char[][] splitArgs(inout char[] command, char[] delims = " \t\r\n")
|
|
1119 in
|
|
1120 {
|
|
1121 assert(!contains(delims, '"'),
|
|
1122 "The argument delimiter string cannot contain a double quotes ('\"') character");
|
|
1123 }
|
|
1124 body
|
|
1125 {
|
|
1126 enum State
|
|
1127 {
|
|
1128 Start,
|
|
1129 FindDelimiter,
|
|
1130 InsideQuotes
|
|
1131 }
|
|
1132
|
|
1133 char[][] args = null;
|
|
1134 char[][] chunks = null;
|
|
1135 int start = -1;
|
|
1136 char c;
|
|
1137 int i;
|
|
1138 State state = State.Start;
|
|
1139
|
|
1140 // Append an argument to the 'args' array using the 'chunks' array
|
|
1141 // and the current position in the 'command' string as the source.
|
|
1142 void appendChunksAsArg()
|
|
1143 {
|
|
1144 uint argPos;
|
|
1145
|
|
1146 if (chunks.length > 0)
|
|
1147 {
|
|
1148 // Create the array element corresponding to the argument by
|
|
1149 // appending the first chunk.
|
|
1150 args ~= chunks[0];
|
|
1151 argPos = args.length - 1;
|
|
1152
|
|
1153 for (uint chunkPos = 1; chunkPos < chunks.length; ++chunkPos)
|
|
1154 {
|
|
1155 args[argPos] ~= chunks[chunkPos];
|
|
1156 }
|
|
1157
|
|
1158 if (start != -1)
|
|
1159 {
|
|
1160 args[argPos] ~= command[start .. i];
|
|
1161 }
|
|
1162 chunks.length = 0;
|
|
1163 }
|
|
1164 else
|
|
1165 {
|
|
1166 if (start != -1)
|
|
1167 {
|
|
1168 args ~= command[start .. i];
|
|
1169 }
|
|
1170 }
|
|
1171 start = -1;
|
|
1172 }
|
|
1173
|
|
1174 for (i = 0; i < command.length; i++)
|
|
1175 {
|
|
1176 c = command[i];
|
|
1177
|
|
1178 switch (state)
|
|
1179 {
|
|
1180 // Start looking for an argument.
|
|
1181 case State.Start:
|
|
1182 if (c == '"')
|
|
1183 {
|
|
1184 state = State.InsideQuotes;
|
|
1185 }
|
|
1186 else if (!contains(delims, c))
|
|
1187 {
|
|
1188 start = i;
|
|
1189 state = State.FindDelimiter;
|
|
1190 }
|
|
1191 else
|
|
1192 {
|
|
1193 appendChunksAsArg();
|
|
1194 }
|
|
1195 break;
|
|
1196
|
|
1197 // Find the ending delimiter for an argument.
|
|
1198 case State.FindDelimiter:
|
|
1199 if (c == '"')
|
|
1200 {
|
|
1201 // If we find a quotes character this means that we've
|
|
1202 // found a quoted section of an argument. (e.g.
|
|
1203 // abc"def"ghi). The quoted section will be appended
|
|
1204 // to the preceding part of the argument. This is also
|
|
1205 // what Unix shells do (i.e. a"b"c becomes abc).
|
|
1206 if (start != -1)
|
|
1207 {
|
|
1208 chunks ~= command[start .. i];
|
|
1209 start = -1;
|
|
1210 }
|
|
1211 state = State.InsideQuotes;
|
|
1212 }
|
|
1213 else if (contains(delims, c))
|
|
1214 {
|
|
1215 appendChunksAsArg();
|
|
1216 state = State.Start;
|
|
1217 }
|
|
1218 break;
|
|
1219
|
|
1220 // Inside a quoted argument or section of an argument.
|
|
1221 case State.InsideQuotes:
|
|
1222 if (start == -1)
|
|
1223 {
|
|
1224 start = i;
|
|
1225 }
|
|
1226
|
|
1227 if (c == '"')
|
|
1228 {
|
|
1229 chunks ~= command[start .. i];
|
|
1230 start = -1;
|
|
1231 state = State.Start;
|
|
1232 }
|
|
1233 break;
|
|
1234
|
|
1235 default:
|
|
1236 assert(false, "Invalid state in Process.splitArgs");
|
|
1237 }
|
|
1238 }
|
|
1239
|
|
1240 // Add the last argument (if there is one)
|
|
1241 appendChunksAsArg();
|
|
1242
|
|
1243 return args;
|
|
1244 }
|
|
1245
|
|
1246 /**
|
|
1247 * Close and delete any pipe that may have been left open in a previous
|
|
1248 * execution of a child process.
|
|
1249 */
|
|
1250 protected void cleanPipes()
|
|
1251 {
|
|
1252 delete _stdin;
|
|
1253 delete _stdout;
|
|
1254 delete _stderr;
|
|
1255 }
|
|
1256
|
|
1257 version (Windows)
|
|
1258 {
|
|
1259 /**
|
|
1260 * Convert an associative array of strings to a buffer containing a
|
|
1261 * concatenation of "<name>=<value>" strings separated by a null
|
|
1262 * character and with an additional null character at the end of it.
|
|
1263 * This is the format expected by the CreateProcess() Windows API for
|
|
1264 * the environment variables.
|
|
1265 */
|
|
1266 protected static char[] toNullEndedBuffer(char[][char[]] src)
|
|
1267 {
|
|
1268 char[] dest;
|
|
1269
|
|
1270 foreach (key, value; src)
|
|
1271 {
|
|
1272 dest ~= key ~ '=' ~ value ~ '\0';
|
|
1273 }
|
|
1274
|
|
1275 if (dest.length > 0)
|
|
1276 {
|
|
1277 dest ~= '\0';
|
|
1278 }
|
|
1279
|
|
1280 return dest;
|
|
1281 }
|
|
1282 }
|
|
1283 else version (Posix)
|
|
1284 {
|
|
1285 /**
|
|
1286 * Convert an array of strings to an array of pointers to char with
|
|
1287 * a terminating null character (C strings). The resulting array
|
|
1288 * has a null pointer at the end. This is the format expected by
|
|
1289 * the execv*() family of POSIX functions.
|
|
1290 */
|
|
1291 protected static char*[] toNullEndedArray(char[][] src)
|
|
1292 {
|
|
1293 if (src !is null)
|
|
1294 {
|
|
1295 char*[] dest = new char*[src.length + 1];
|
|
1296 int i = src.length;
|
|
1297
|
|
1298 // Add terminating null pointer to the array
|
|
1299 dest[i] = null;
|
|
1300
|
|
1301 while (--i >= 0)
|
|
1302 {
|
|
1303 // Add a terminating null character to each string
|
|
1304 dest[i] = toStringz(src[i]);
|
|
1305 }
|
|
1306 return dest;
|
|
1307 }
|
|
1308 else
|
|
1309 {
|
|
1310 return null;
|
|
1311 }
|
|
1312 }
|
|
1313
|
|
1314 /**
|
|
1315 * Convert an associative array of strings to an array of pointers to
|
|
1316 * char with a terminating null character (C strings). The resulting
|
|
1317 * array has a null pointer at the end. This is the format expected by
|
|
1318 * the execv*() family of POSIX functions for environment variables.
|
|
1319 */
|
|
1320 protected static char*[] toNullEndedArray(char[][char[]] src)
|
|
1321 {
|
|
1322 char*[] dest;
|
|
1323
|
|
1324 foreach (key, value; src)
|
|
1325 {
|
|
1326 dest ~= (key ~ '=' ~ value ~ '\0').ptr;
|
|
1327 }
|
|
1328
|
|
1329 if (dest.length > 0)
|
|
1330 {
|
|
1331 dest ~= null;
|
|
1332 }
|
|
1333 return dest;
|
|
1334 }
|
|
1335
|
|
1336 /**
|
|
1337 * Execute a process by looking up a file in the system path, passing
|
|
1338 * the array of arguments and the the environment variables. This
|
|
1339 * method is a combination of the execve() and execvp() POSIX system
|
|
1340 * calls.
|
|
1341 */
|
|
1342 protected static int execvpe(char[] filename, char*[] argv, char*[] envp)
|
|
1343 in
|
|
1344 {
|
|
1345 assert(filename.length > 0);
|
|
1346 }
|
|
1347 body
|
|
1348 {
|
|
1349 int rc = -1;
|
|
1350 char* str;
|
|
1351
|
|
1352 if (!contains(filename, FileConst.PathSeparatorChar) &&
|
|
1353 (str = getenv("PATH")) !is null)
|
|
1354 {
|
|
1355 char[][] pathList = delimit(str[0 .. strlen(str)], ":");
|
|
1356
|
|
1357 foreach (path; pathList)
|
|
1358 {
|
|
1359 if (path[path.length - 1] != FileConst.PathSeparatorChar)
|
|
1360 {
|
|
1361 path ~= FileConst.PathSeparatorChar;
|
|
1362 }
|
|
1363
|
|
1364 debug (Process)
|
|
1365 Stdout.formatln("Trying execution of '{0}' in directory '{1}'",
|
|
1366 filename, path);
|
|
1367
|
|
1368 path ~= filename;
|
|
1369 path ~= '\0';
|
|
1370
|
|
1371 rc = execve(path.ptr, argv.ptr, (envp.length > 0 ? envp.ptr : null));
|
|
1372 // If the process execution failed because of an error
|
|
1373 // other than ENOENT (No such file or directory) we
|
|
1374 // abort the loop.
|
|
1375 if (rc == -1 && errno != ENOENT)
|
|
1376 {
|
|
1377 break;
|
|
1378 }
|
|
1379 }
|
|
1380 }
|
|
1381 else
|
|
1382 {
|
|
1383 debug (Process)
|
|
1384 Stdout.formatln("Calling execve('{0}', argv[{1}], {2})",
|
|
1385 (argv[0])[0 .. strlen(argv[0])],
|
|
1386 argv.length, (envp.length > 0 ? "envp" : "null"));
|
|
1387
|
|
1388 rc = execve(argv[0], argv.ptr, (envp.length > 0 ? envp.ptr : null));
|
|
1389 }
|
|
1390 return rc;
|
|
1391 }
|
|
1392 }
|
|
1393 }
|
|
1394
|
|
1395
|
|
1396 /**
|
|
1397 * Exception thrown when the process cannot be created.
|
|
1398 */
|
|
1399 class ProcessCreateException: ProcessException
|
|
1400 {
|
|
1401 public this(char[] command, char[] file, uint line)
|
|
1402 {
|
|
1403 super("Could not create process for " ~ command ~ " : " ~ SysError.lastMsg);
|
|
1404 }
|
|
1405 }
|
|
1406
|
|
1407 /**
|
|
1408 * Exception thrown when the parent process cannot be forked.
|
|
1409 *
|
|
1410 * This exception will only be thrown on POSIX-compatible platforms.
|
|
1411 */
|
|
1412 class ProcessForkException: ProcessException
|
|
1413 {
|
|
1414 public this(int pid, char[] file, uint line)
|
|
1415 {
|
|
1416 super(format("Could not fork process ", pid) ~ " : " ~ SysError.lastMsg);
|
|
1417 }
|
|
1418 }
|
|
1419
|
|
1420 /**
|
|
1421 * Exception thrown when the process cannot be killed.
|
|
1422 */
|
|
1423 class ProcessKillException: ProcessException
|
|
1424 {
|
|
1425 public this(int pid, char[] file, uint line)
|
|
1426 {
|
|
1427 super(format("Could not kill process ", pid) ~ " : " ~ SysError.lastMsg);
|
|
1428 }
|
|
1429 }
|
|
1430
|
|
1431 /**
|
|
1432 * Exception thrown when the parent process tries to wait on the child
|
|
1433 * process and fails.
|
|
1434 */
|
|
1435 class ProcessWaitException: ProcessException
|
|
1436 {
|
|
1437 public this(int pid, char[] file, uint line)
|
|
1438 {
|
|
1439 super(format("Could not wait on process ", pid) ~ " : " ~ SysError.lastMsg);
|
|
1440 }
|
|
1441 }
|
|
1442
|
|
1443
|
|
1444
|
|
1445
|
|
1446 /**
|
|
1447 * append an int argument to a message
|
|
1448 */
|
|
1449 private char[] format (char[] msg, int value)
|
|
1450 {
|
|
1451 char[10] tmp;
|
|
1452
|
|
1453 return msg ~ Integer.format (tmp, value);
|
|
1454 }
|
|
1455
|
|
1456
|
|
1457 debug (UnitTest)
|
|
1458 {
|
|
1459 private import tango.text.stream.LineIterator;
|
|
1460
|
|
1461 unittest
|
|
1462 {
|
|
1463 char[][] params;
|
|
1464 char[] command = "echo ";
|
|
1465
|
|
1466 params ~= "one";
|
|
1467 params ~= "two";
|
|
1468 params ~= "three";
|
|
1469
|
|
1470 command ~= '"';
|
|
1471 foreach (i, param; params)
|
|
1472 {
|
|
1473 command ~= param;
|
|
1474 if (i != params.length - 1)
|
|
1475 {
|
|
1476 command ~= '\n';
|
|
1477 }
|
|
1478 }
|
|
1479 command ~= '"';
|
|
1480
|
|
1481 try
|
|
1482 {
|
|
1483 auto p = new Process(command, null);
|
|
1484
|
|
1485 p.execute();
|
|
1486
|
|
1487 foreach (i, line; new LineIterator!(char)(p.stdout))
|
|
1488 {
|
|
1489 if (i == params.length) // echo can add ending new line confusing this test
|
|
1490 break;
|
|
1491 assert(line == params[i]);
|
|
1492 }
|
|
1493
|
|
1494 auto result = p.wait();
|
|
1495
|
|
1496 assert(result.reason == Process.Result.Exit && result.status == 0);
|
|
1497 }
|
|
1498 catch (ProcessException e)
|
|
1499 {
|
|
1500 Cerr("Program execution failed: ")(e.toString()).newline();
|
|
1501 }
|
|
1502 }
|
|
1503 }
|
|
1504
|