core: split PATH on ; on Windows
[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_APPEND);
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 // Error?
218 if (!created) {
219 result->running = 0;
220 result->status = 127;
221
222 // Close subprocess pipes
223 if (pipeflag & 1) CloseHandle(in_fd[1]);
224 if (pipeflag & 2) CloseHandle(out_fd[0]);
225 if (pipeflag & 3) CloseHandle(err_fd[0]);
226 } else {
227 result->h_process = proc_info.hProcess;
228 result->h_thread = proc_info.hThread;
229 result->id = GetProcessId(proc_info.hProcess);
230 result->running = 1;
231 }
232
233 return result;
234 #else
235 se_exec_data_t* result = NULL;
236 int id;
237 int in_fd[2];
238 int out_fd[2];
239 int err_fd[2];
240 if (pipeflag & 1) {
241 int res = pipe(in_fd);
242 if ( res == -1 ) {
243 return NULL;
244 }
245 }
246 if (pipeflag & 2) {
247 int res = pipe(out_fd);
248 if ( res == -1 ) {
249 return NULL;
250 }
251 }
252 if (pipeflag & 4) {
253 int res = pipe(err_fd);
254 if ( res == -1 ) {
255 return NULL;
256 }
257 }
258
259 id = fork();
260 if (id == 0)
261 { /* child */
262 char **arg = malloc(sizeof(char*) * (argc+1));
263 char *c = args;
264 int i;
265
266 /* Prepare args */
267 for(i=0; i<argc; i++)
268 {
269 arg[i] = c;
270 c += strlen(c) + 1;
271 }
272 arg[argc] = NULL;
273
274 /* Connect pipe */
275 if (pipeflag & 1)
276 {
277 close(0);
278 dup2(in_fd[0], 0);
279 close(in_fd[0]);
280 close(in_fd[1]);
281 }
282 if (pipeflag & 2)
283 {
284 close(1);
285 dup2(out_fd[1], 1);
286 close(out_fd[0]);
287 close(out_fd[1]);
288 }
289 if (pipeflag & 4)
290 {
291 close(2);
292 dup2(err_fd[1], 2);
293 close(err_fd[0]);
294 close(err_fd[1]);
295 }
296
297 /* calls */
298 execvp(prog, arg);
299 _exit(127);
300 }
301 else if (id > 0)
302 { /* father */
303 result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t));
304 result->id = id;
305 result->running = 1;
306 if (pipeflag & 1)
307 {
308 result->in_fd = in_fd[1];
309 close(in_fd[0]);
310 } else
311 result->in_fd = -1;
312
313 if (pipeflag & 2)
314 {
315 result->out_fd = out_fd[0];
316 close(out_fd[1]);
317 } else
318 result->out_fd = -1;
319
320 if (pipeflag & 4)
321 {
322 result->err_fd = err_fd[0];
323 close(err_fd[1]);
324 } else
325 result->err_fd = -1;
326 }
327
328 return result;
329 #endif
330 `}
331 end
332
333 # `Process` on which the `stdout` is readable like a `Reader`
334 class ProcessReader
335 super Process
336 super Reader
337
338 # File Descriptor used for the input.
339 var stream_in: FileReader is noinit
340
341 redef fun close do stream_in.close
342
343 redef fun read_char do return stream_in.read_char
344
345 redef fun read_byte do return stream_in.read_byte
346
347 redef fun eof do return stream_in.eof
348
349 redef fun pipeflags do return 2
350
351 redef fun execute
352 do
353 super
354 stream_in = new FileReader.from_fd(data.out_fd)
355 end
356 end
357
358 # `Process` on which `stdin` is writable like a `Writer`
359 class ProcessWriter
360 super Process
361 super Writer
362
363 # File Descriptor used for the output.
364 var stream_out: Writer is noinit
365
366 redef fun close do stream_out.close
367
368 redef fun is_writable do return stream_out.is_writable
369
370 redef fun write(s) do stream_out.write(s)
371
372 redef fun pipeflags do return 1
373
374 redef fun execute
375 do
376 super
377 var out = new FileWriter.from_fd(data.in_fd)
378 out.set_buffering_mode(0, sys.buffer_mode_none)
379 stream_out = out
380 end
381 end
382
383 # `Process` on which stdout can be read and stdin can be written to like a `Duplex`
384 class ProcessDuplex
385 super ProcessReader
386 super ProcessWriter
387 super Duplex
388
389 redef fun close
390 do
391 stream_in.close
392 stream_out.close
393 end
394
395 redef fun pipeflags do return 3
396
397 redef fun execute do super
398
399 # Write `input` to process and return its output
400 #
401 # Writing and reading are processed line by line,
402 # reading only when something is available.
403 #
404 # ~~~
405 # var proc = new ProcessDuplex("tr", "[:lower:]", "[:upper:]")
406 # assert proc.write_and_read("""
407 # Alice
408 # Bob
409 # """) == """
410 # ALICE
411 # BOB
412 # """
413 # ~~~
414 fun write_and_read(input: Text): String
415 do
416 var read = new Buffer
417
418 # Main loop, read and write line by line
419 var prev = 0
420 for delimiter in input.search_all('\n') do
421 write input.substring(prev, delimiter.after-prev)
422 prev = delimiter.after
423
424 while stream_in.poll_in do
425 read.append stream_in.read_line
426 end
427 end
428
429 # Write the last line
430 write input.substring_from(prev)
431 stream_out.close
432
433 # Read the rest, may be everything for some programs
434 read.append stream_in.read_all
435 stream_in.close
436
437 # Clean up
438 wait
439 return read.to_s
440 end
441 end
442
443 redef class Sys
444 # Execute a shell command and return its error code
445 fun system(command: Text): Int
446 do
447 return command.to_cstring.system
448 end
449
450 # The pid of the program
451 fun pid: Int `{ return getpid(); `}
452 end
453
454 redef class CString
455 # Execute self as a shell command.
456 #
457 # See the posix function system(3).
458 fun system: Int `{
459 int status = system(self);
460 #ifndef _WIN32
461 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) {
462 // system exited on SIGINT: in my opinion the user wants the main to be discontinued
463 kill(getpid(), SIGINT);
464 }
465 #endif
466 return status;
467 `}
468 end
469
470 private extern class NativeProcess `{ se_exec_data_t* `}
471
472 fun id: Int `{ return (long)self->id; `}
473 fun status: Int `{ return self->status; `}
474 fun in_fd: Int `{ return self->in_fd; `}
475 fun out_fd: Int `{ return self->out_fd; `}
476 fun err_fd: Int `{ return self->err_fd; `}
477
478 fun is_finished: Bool `{
479 int result = (int)0;
480 if (self->running) {
481 #ifdef _WIN32
482 if (WaitForSingleObject(self->h_process, 0) == 0) {
483 /* child is finished */
484 result = 1;
485
486 long unsigned int status;
487 GetExitCodeProcess(self->h_process, &status);
488 self->running = 0;
489 self->status = (int)status;
490
491 CloseHandle(self->h_process);
492 CloseHandle(self->h_thread);
493 }
494 #else
495 int status;
496 int id = waitpid(self->id, &status, WNOHANG);
497 if (id != 0) {
498 /* child is finished */
499 result = (int)(id == self->id);
500 self->status = WEXITSTATUS(status);
501 self->running = 0;
502 }
503 #endif
504 }
505 else{
506 result = (int)1;
507 }
508 return result;
509 `}
510
511 fun wait `{
512 #ifdef _WIN32
513 long unsigned int status;
514 if (self->running) {
515 WaitForSingleObject(self->h_process, INFINITE);
516 GetExitCodeProcess(self->h_process, &status);
517
518 CloseHandle(self->h_process);
519 CloseHandle(self->h_thread);
520
521 self->status = (int)status;
522 self->running = 0;
523 }
524 #else
525 int status;
526 if (self->running) {
527 waitpid(self->id, &status, 0);
528 self->status = WEXITSTATUS(status);
529 self->running = 0;
530 }
531 #endif
532 `}
533 end