Merge: doc: fixed some typos and other misc. corrections
[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 } else {
331 perror("Process:");
332 return NULL;
333 }
334
335 return result;
336 #endif
337 `}
338 end
339
340 # `Process` on which the `stdout` is readable like a `Reader`
341 class ProcessReader
342 super Process
343 super Reader
344
345 # File Descriptor used for the input.
346 var stream_in: FileReader is noinit
347
348 redef fun close do stream_in.close
349
350 redef fun read_char do return stream_in.read_char
351
352 redef fun raw_read_byte do return stream_in.read_byte
353
354 redef fun eof do return stream_in.eof
355
356 redef fun pipeflags do return 2
357
358 redef fun execute
359 do
360 super
361 stream_in = new FileReader.from_fd(data.out_fd)
362 end
363 end
364
365 # `Process` on which `stdin` is writable like a `Writer`
366 class ProcessWriter
367 super Process
368 super Writer
369
370 # File Descriptor used for the output.
371 var stream_out: Writer is noinit
372
373 redef fun close do stream_out.close
374
375 redef fun is_writable do return stream_out.is_writable
376
377 redef fun write(s) do stream_out.write(s)
378
379 redef fun pipeflags do return 1
380
381 redef fun execute
382 do
383 super
384 var out = new FileWriter.from_fd(data.in_fd)
385 out.set_buffering_mode(0, sys.buffer_mode_none)
386 stream_out = out
387 end
388 end
389
390 # `Process` on which stdout can be read and stdin can be written to like a `Duplex`
391 class ProcessDuplex
392 super ProcessReader
393 super ProcessWriter
394 super Duplex
395
396 redef fun close
397 do
398 stream_in.close
399 stream_out.close
400 end
401
402 redef fun pipeflags do return 3
403
404 redef fun execute do super
405
406 # Write `input` to process and return its output
407 #
408 # Writing and reading are processed line by line,
409 # reading only when something is available.
410 #
411 # ~~~
412 # var proc = new ProcessDuplex("tr", "[:lower:]", "[:upper:]")
413 # assert proc.write_and_read("""
414 # Alice
415 # Bob
416 # """) == """
417 # ALICE
418 # BOB
419 # """
420 # ~~~
421 fun write_and_read(input: Text): String
422 do
423 var read = new Buffer
424
425 # Main loop, read and write line by line
426 var prev = 0
427 for delimiter in input.search_all('\n') do
428 write input.substring(prev, delimiter.after-prev)
429 prev = delimiter.after
430
431 while stream_in.poll_in do
432 read.append stream_in.read_line
433 end
434 end
435
436 # Write the last line
437 write input.substring_from(prev)
438 stream_out.close
439
440 # Read the rest, may be everything for some programs
441 read.append stream_in.read_all
442 stream_in.close
443
444 # Clean up
445 wait
446 return read.to_s
447 end
448 end
449
450 redef class Sys
451 # Execute a shell command and return its error code
452 fun system(command: Text): Int
453 do
454 return command.to_cstring.system
455 end
456
457 # The pid of the program
458 fun pid: Int `{ return getpid(); `}
459 end
460
461 redef class CString
462 # Execute self as a shell command.
463 #
464 # See the posix function system(3).
465 fun system: Int `{
466 int status = system(self);
467 #ifndef _WIN32
468 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) {
469 // system exited on SIGINT: in my opinion the user wants the main to be discontinued
470 kill(getpid(), SIGINT);
471 }
472 #endif
473 return status;
474 `}
475 end
476
477 private extern class NativeProcess `{ se_exec_data_t* `}
478
479 fun id: Int `{ return (long)self->id; `}
480 fun status: Int `{ return self->status; `}
481 fun in_fd: Int `{ return self->in_fd; `}
482 fun out_fd: Int `{ return self->out_fd; `}
483 fun err_fd: Int `{ return self->err_fd; `}
484
485 fun is_finished: Bool `{
486 int result = (int)0;
487 if (self->running) {
488 #ifdef _WIN32
489 if (WaitForSingleObject(self->h_process, 0) == 0) {
490 /* child is finished */
491 result = 1;
492
493 long unsigned int status;
494 GetExitCodeProcess(self->h_process, &status);
495 self->running = 0;
496 self->status = (int)status;
497
498 CloseHandle(self->h_process);
499 CloseHandle(self->h_thread);
500 }
501 #else
502 int status;
503 int id = waitpid(self->id, &status, WNOHANG);
504 if (id != 0) {
505 /* child is finished */
506 result = (int)(id == self->id);
507 self->status = WEXITSTATUS(status);
508 self->running = 0;
509 }
510 #endif
511 }
512 else{
513 result = (int)1;
514 }
515 return result;
516 `}
517
518 fun wait `{
519 #ifdef _WIN32
520 long unsigned int status;
521 if (self->running) {
522 WaitForSingleObject(self->h_process, INFINITE);
523 GetExitCodeProcess(self->h_process, &status);
524
525 CloseHandle(self->h_process);
526 CloseHandle(self->h_thread);
527
528 self->status = (int)status;
529 self->running = 0;
530 }
531 #else
532 int status;
533 if (self->running) {
534 waitpid(self->id, &status, 0);
535 self->status = WEXITSTATUS(status);
536 self->running = 0;
537 }
538 #endif
539 `}
540 end