exec: close child pipes from the parent process
[nit.git] / lib / core / exec.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2004-2008 Jean Privat <jean@pryen.org>
4 # Copyright 2008 Floréal Morandat <morandat@lirmm.fr>
5 #
6 # This file is free software, which comes along with NIT. This software is
7 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
8 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
9 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
10 # is kept unaltered, and a notification of the changes is added.
11 # You are allowed to redistribute it and sell it, alone or is a part of
12 # another product.
13
14 # Invocation and management of operating system sub-processes.
15 # Standard input and output can be handled through streams.
16 module exec
17
18 import file
19
20 in "C" `{
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <unistd.h>
26 #include <signal.h>
27 #include <sys/types.h>
28
29 #ifdef _WIN32
30 #include <windows.h>
31 #include <fcntl.h>
32 #else
33 #include <sys/wait.h>
34 #endif
35
36 typedef struct se_exec_data se_exec_data_t;
37 struct se_exec_data {
38 #ifdef _WIN32
39 HANDLE h_process;
40 HANDLE h_thread;
41 #endif
42 pid_t id;
43 int running;
44 int status;
45 int in_fd;
46 int out_fd;
47 int err_fd;
48 };
49 `}
50
51 # Simple sub-process
52 class Process
53 # The pid of the process
54 fun id: Int do return data.id
55
56 # Is the process finished?
57 fun is_finished: Bool do return data.is_finished
58
59 # Wait the termination of the process
60 fun wait
61 do
62 data.wait
63 assert is_finished
64 end
65
66 # The status once finished
67 #
68 # Require: `is_finished`
69 fun status: Int
70 do
71 assert is_finished
72 return data.status
73 end
74
75 # The target executable
76 # Either a file path or the name of an executable available in PATH.
77 var command: Text
78
79 # The arguments of the command
80 # Starts with the first real arguments---ie. does not include the progname (`argv[0]`, in C)
81 var arguments: nullable Array[Text]
82
83 # Launch a command with some arguments
84 init(command: Text, arguments: Text...) is old_style_init do
85 self.command = command
86 self.arguments = arguments
87 execute
88 end
89
90 # Launch a simple command with arguments passed as an array
91 init from_a(command: Text, arguments: nullable Array[Text])
92 do
93 self.command = command
94 self.arguments = arguments
95 execute
96 end
97
98 # Flags used internally to know which pipe to open
99 private fun pipeflags: Int do return 0
100
101 # Internal code to handle execution
102 protected fun execute
103 do
104 var arguments = self.arguments
105
106 var args = new FlatBuffer
107 var argc = 1
108
109 if not is_windows then
110 # Pass the arguments as a big C string where elements are separated with '\0'
111 args.append command
112 if arguments != null then
113 for a in arguments do
114 args.add '\0'
115 args.append a
116 end
117 argc += arguments.length
118 end
119 else
120 # Combine the program and args in a single string
121 assert not command.chars.has('"')
122 args = new FlatBuffer
123
124 args.add '"'
125 args.append command
126 args.add '"'
127
128 if arguments != null then
129 for a in arguments do
130 args.append " \""
131 args.append a.replace('"', "\\\"")
132 args.add '"'
133 end
134 end
135 end
136
137 data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, argc, pipeflags)
138 assert not data.address_is_null else print_error "Internal error executing: {command}"
139 end
140
141 private var data: NativeProcess
142
143 private fun basic_exec_execute(prog, args: CString, argc: Int, pipeflag: Int): NativeProcess `{
144 #ifdef _WIN32
145 SECURITY_ATTRIBUTES sec_attr;
146 sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
147 sec_attr.bInheritHandle = TRUE;
148 sec_attr.lpSecurityDescriptor = NULL;
149
150 STARTUPINFO start_info;
151 ZeroMemory(&start_info, sizeof(STARTUPINFO));
152 start_info.cb = sizeof(STARTUPINFO);
153 start_info.dwFlags = STARTF_USESTDHANDLES;
154
155 HANDLE in_fd[2];
156 HANDLE out_fd[2];
157 HANDLE err_fd[2];
158
159 se_exec_data_t *result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t));
160
161 // Redirect stdin?
162 if (pipeflag & 1) {
163 if (!CreatePipe(&in_fd[0], &in_fd[1], &sec_attr, 0)) {
164 return NULL;
165 }
166 start_info.hStdInput = in_fd[0];
167 result->in_fd = _open_osfhandle((intptr_t)in_fd[1], _O_WRONLY);
168 if ( !SetHandleInformation(in_fd[1], HANDLE_FLAG_INHERIT, 0) )
169 return NULL;
170 } else {
171 start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
172 result->in_fd = -1;
173 }
174
175 // Redirect stdout?
176 if (pipeflag & 2) {
177 if (!CreatePipe(&out_fd[0], &out_fd[1], &sec_attr, 0)) {
178 return NULL;
179 }
180 start_info.hStdOutput = out_fd[1];
181 result->out_fd = _open_osfhandle((intptr_t)out_fd[0], _O_RDONLY);
182 if ( !SetHandleInformation(out_fd[0], HANDLE_FLAG_INHERIT, 0) )
183 return NULL;
184 } else {
185 start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
186 result->out_fd = -1;
187 }
188
189 // Redirect stderr?
190 if (pipeflag & 4) {
191 if (!CreatePipe(&err_fd[0], &err_fd[1], &sec_attr, 0)) {
192 return NULL;
193 }
194 start_info.hStdError = err_fd[1];
195 result->err_fd = _open_osfhandle((intptr_t)err_fd[0], _O_RDONLY);
196 if ( !SetHandleInformation(err_fd[0], HANDLE_FLAG_INHERIT, 0) )
197 return NULL;
198 } else {
199 start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
200 result->err_fd = -1;
201 }
202
203 PROCESS_INFORMATION proc_info;
204 ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION));
205
206 BOOL created = CreateProcess(NULL,
207 args, // command line
208 NULL, // process security attributes
209 NULL, // primary thread security attributes
210 TRUE, // inherit handles
211 0, // creation flags
212 NULL, // use parent's environment
213 NULL, // use parent's current directory
214 &start_info,
215 &proc_info);
216
217 if (pipeflag & 1) CloseHandle(in_fd[0]);
218 if (pipeflag & 2) CloseHandle(out_fd[1]);
219 if (pipeflag & 3) CloseHandle(err_fd[1]);
220
221 // Error?
222 if (!created) {
223 result->running = 0;
224 result->status = 127;
225
226 // Close subprocess pipes
227 if (pipeflag & 1) CloseHandle(in_fd[1]);
228 if (pipeflag & 2) CloseHandle(out_fd[0]);
229 if (pipeflag & 3) CloseHandle(err_fd[0]);
230 } else {
231 result->h_process = proc_info.hProcess;
232 result->h_thread = proc_info.hThread;
233 result->id = GetProcessId(proc_info.hProcess);
234 result->running = 1;
235 }
236
237 return result;
238 #else
239 se_exec_data_t* result = NULL;
240 int id;
241 int in_fd[2];
242 int out_fd[2];
243 int err_fd[2];
244 if (pipeflag & 1) {
245 int res = pipe(in_fd);
246 if ( res == -1 ) {
247 return NULL;
248 }
249 }
250 if (pipeflag & 2) {
251 int res = pipe(out_fd);
252 if ( res == -1 ) {
253 return NULL;
254 }
255 }
256 if (pipeflag & 4) {
257 int res = pipe(err_fd);
258 if ( res == -1 ) {
259 return NULL;
260 }
261 }
262
263 id = fork();
264 if (id == 0)
265 { /* child */
266 char **arg = malloc(sizeof(char*) * (argc+1));
267 char *c = args;
268 int i;
269
270 /* Prepare args */
271 for(i=0; i<argc; i++)
272 {
273 arg[i] = c;
274 c += strlen(c) + 1;
275 }
276 arg[argc] = NULL;
277
278 /* Connect pipe */
279 if (pipeflag & 1)
280 {
281 close(0);
282 dup2(in_fd[0], 0);
283 close(in_fd[0]);
284 close(in_fd[1]);
285 }
286 if (pipeflag & 2)
287 {
288 close(1);
289 dup2(out_fd[1], 1);
290 close(out_fd[0]);
291 close(out_fd[1]);
292 }
293 if (pipeflag & 4)
294 {
295 close(2);
296 dup2(err_fd[1], 2);
297 close(err_fd[0]);
298 close(err_fd[1]);
299 }
300
301 /* calls */
302 execvp(prog, arg);
303 _exit(127);
304 }
305 else if (id > 0)
306 { /* father */
307 result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t));
308 result->id = id;
309 result->running = 1;
310 if (pipeflag & 1)
311 {
312 result->in_fd = in_fd[1];
313 close(in_fd[0]);
314 } else
315 result->in_fd = -1;
316
317 if (pipeflag & 2)
318 {
319 result->out_fd = out_fd[0];
320 close(out_fd[1]);
321 } else
322 result->out_fd = -1;
323
324 if (pipeflag & 4)
325 {
326 result->err_fd = err_fd[0];
327 close(err_fd[1]);
328 } else
329 result->err_fd = -1;
330 }
331
332 return result;
333 #endif
334 `}
335 end
336
337 # `Process` on which the `stdout` is readable like a `Reader`
338 class ProcessReader
339 super Process
340 super Reader
341
342 # File Descriptor used for the input.
343 var stream_in: FileReader is noinit
344
345 redef fun close do stream_in.close
346
347 redef fun read_char do return stream_in.read_char
348
349 redef fun read_byte do return stream_in.read_byte
350
351 redef fun eof do return stream_in.eof
352
353 redef fun pipeflags do return 2
354
355 redef fun execute
356 do
357 super
358 stream_in = new FileReader.from_fd(data.out_fd)
359 end
360 end
361
362 # `Process` on which `stdin` is writable like a `Writer`
363 class ProcessWriter
364 super Process
365 super Writer
366
367 # File Descriptor used for the output.
368 var stream_out: Writer is noinit
369
370 redef fun close do stream_out.close
371
372 redef fun is_writable do return stream_out.is_writable
373
374 redef fun write(s) do stream_out.write(s)
375
376 redef fun pipeflags do return 1
377
378 redef fun execute
379 do
380 super
381 var out = new FileWriter.from_fd(data.in_fd)
382 out.set_buffering_mode(0, sys.buffer_mode_none)
383 stream_out = out
384 end
385 end
386
387 # `Process` on which stdout can be read and stdin can be written to like a `Duplex`
388 class ProcessDuplex
389 super ProcessReader
390 super ProcessWriter
391 super Duplex
392
393 redef fun close
394 do
395 stream_in.close
396 stream_out.close
397 end
398
399 redef fun pipeflags do return 3
400
401 redef fun execute do super
402
403 # Write `input` to process and return its output
404 #
405 # Writing and reading are processed line by line,
406 # reading only when something is available.
407 #
408 # ~~~
409 # var proc = new ProcessDuplex("tr", "[:lower:]", "[:upper:]")
410 # assert proc.write_and_read("""
411 # Alice
412 # Bob
413 # """) == """
414 # ALICE
415 # BOB
416 # """
417 # ~~~
418 fun write_and_read(input: Text): String
419 do
420 var read = new Buffer
421
422 # Main loop, read and write line by line
423 var prev = 0
424 for delimiter in input.search_all('\n') do
425 write input.substring(prev, delimiter.after-prev)
426 prev = delimiter.after
427
428 while stream_in.poll_in do
429 read.append stream_in.read_line
430 end
431 end
432
433 # Write the last line
434 write input.substring_from(prev)
435 stream_out.close
436
437 # Read the rest, may be everything for some programs
438 read.append stream_in.read_all
439 stream_in.close
440
441 # Clean up
442 wait
443 return read.to_s
444 end
445 end
446
447 redef class Sys
448 # Execute a shell command and return its error code
449 fun system(command: Text): Int
450 do
451 return command.to_cstring.system
452 end
453
454 # The pid of the program
455 fun pid: Int `{ return getpid(); `}
456 end
457
458 redef class CString
459 # Execute self as a shell command.
460 #
461 # See the posix function system(3).
462 fun system: Int `{
463 int status = system(self);
464 #ifndef _WIN32
465 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) {
466 // system exited on SIGINT: in my opinion the user wants the main to be discontinued
467 kill(getpid(), SIGINT);
468 }
469 #endif
470 return status;
471 `}
472 end
473
474 private extern class NativeProcess `{ se_exec_data_t* `}
475
476 fun id: Int `{ return (long)self->id; `}
477 fun status: Int `{ return self->status; `}
478 fun in_fd: Int `{ return self->in_fd; `}
479 fun out_fd: Int `{ return self->out_fd; `}
480 fun err_fd: Int `{ return self->err_fd; `}
481
482 fun is_finished: Bool `{
483 int result = (int)0;
484 if (self->running) {
485 #ifdef _WIN32
486 if (WaitForSingleObject(self->h_process, 0) == 0) {
487 /* child is finished */
488 result = 1;
489
490 long unsigned int status;
491 GetExitCodeProcess(self->h_process, &status);
492 self->running = 0;
493 self->status = (int)status;
494
495 CloseHandle(self->h_process);
496 CloseHandle(self->h_thread);
497 }
498 #else
499 int status;
500 int id = waitpid(self->id, &status, WNOHANG);
501 if (id != 0) {
502 /* child is finished */
503 result = (int)(id == self->id);
504 self->status = WEXITSTATUS(status);
505 self->running = 0;
506 }
507 #endif
508 }
509 else{
510 result = (int)1;
511 }
512 return result;
513 `}
514
515 fun wait `{
516 #ifdef _WIN32
517 long unsigned int status;
518 if (self->running) {
519 WaitForSingleObject(self->h_process, INFINITE);
520 GetExitCodeProcess(self->h_process, &status);
521
522 CloseHandle(self->h_process);
523 CloseHandle(self->h_thread);
524
525 self->status = (int)status;
526 self->running = 0;
527 }
528 #else
529 int status;
530 if (self->running) {
531 waitpid(self->id, &status, 0);
532 self->status = WEXITSTATUS(status);
533 self->running = 0;
534 }
535 #endif
536 `}
537 end