Mercurial > projects > ldc
comparison tango/tango/sys/Process.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
131:5825d48b27d1 | 132:1700239cab2e |
---|---|
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 |