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