nit: Added link to `CONTRIBUTING.md` from the README
[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 <sys/wait.h>
27 #include <signal.h>
28 `}
29
30 in "C Header" `{
31 #include <sys/types.h>
32
33 // FIXME this should be in the "C" block when bug on module blocks is fixed
34 // or, even better, replace the C structure by a Nit object.
35 typedef struct se_exec_data se_exec_data_t;
36 struct se_exec_data {
37 pid_t id;
38 int running;
39 int status;
40 int in_fd;
41 int out_fd;
42 int err_fd;
43 };
44 `}
45
46 # Simple sub-process
47 class Process
48 # The pid of the process
49 fun id: Int do return data.id
50
51 # Is the process finished?
52 fun is_finished: Bool do return data.is_finished
53
54 # Wait the termination of the process
55 fun wait
56 do
57 data.wait
58 assert is_finished
59 end
60
61 # The status once finished
62 fun status: Int
63 do
64 assert is_finished
65 return data.status
66 end
67
68 # The executable run
69 # Is a filepath, or a executable found in PATH
70 var command: String
71
72 # The arguments of the command
73 # Starts with the first real arguments---ie. does not include the progname (`argv[0]`, in C)
74 var arguments: nullable Array[String]
75
76 # Launch a command with some arguments
77 init(command: String, arguments: String...) is old_style_init do
78 self.command = command
79 self.arguments = arguments
80 execute
81 end
82
83 # Launch a simple command with arguments passed as an array
84 init from_a(command: String, arguments: nullable Array[String])
85 do
86 self.command = command
87 self.arguments = arguments
88 execute
89 end
90
91 # flags used internally to know whith pipe to open
92 private fun pipeflags: Int do return 0
93
94 # Internal code to handle execution
95 protected fun execute
96 do
97 # The pass the arguments as a big C string where elements are separated with '\0'
98 var args = new FlatBuffer
99 var l = 1 # Number of elements in args
100 args.append(command)
101 if arguments != null then
102 for a in arguments do
103 args.add('\0')
104 #a.output_class_name
105 args.append(a)
106 end
107 l += arguments.length
108 end
109 data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, l, pipeflags)
110 end
111
112 private var data: NativeProcess
113 private fun basic_exec_execute(prog, args: NativeString, argc: Int, pipeflag: Int): NativeProcess `{
114 se_exec_data_t* result = NULL;
115 int id;
116 int in_fd[2];
117 int out_fd[2];
118 int err_fd[2];
119 if (pipeflag & 1) {
120 int res = pipe(in_fd);
121 if ( res == -1 ) {
122 return NULL;
123 }
124 }
125 if (pipeflag & 2) {
126 int res = pipe(out_fd);
127 if ( res == -1 ) {
128 return NULL;
129 }
130 }
131 if (pipeflag & 4) {
132 int res = pipe(err_fd);
133 if ( res == -1 ) {
134 return NULL;
135 }
136 }
137
138 id = fork();
139 if (id == 0)
140 { /* child */
141 char **arg = malloc(sizeof(char*) * (argc+1));
142 char *c = args;
143 int i;
144
145 /* Prepare args */
146 for(i=0; i<argc; i++)
147 {
148 arg[i] = c;
149 c += strlen(c) + 1;
150 }
151 arg[argc] = NULL;
152
153 /* Connect pipe */
154 if (pipeflag & 1)
155 {
156 close(0);
157 dup2(in_fd[0], 0);
158 close(in_fd[0]);
159 close(in_fd[1]);
160 }
161 if (pipeflag & 2)
162 {
163 close(1);
164 dup2(out_fd[1], 1);
165 close(out_fd[0]);
166 close(out_fd[1]);
167 }
168 if (pipeflag & 4)
169 {
170 close(2);
171 dup2(err_fd[1], 2);
172 close(err_fd[0]);
173 close(err_fd[1]);
174 }
175
176 /* calls */
177 execvp(prog, arg);
178 _exit(127);
179 }
180 else if (id > 0)
181 { /* father */
182 result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t));
183 result->id = id;
184 result->running = 1;
185 if (pipeflag & 1)
186 {
187 result->in_fd = in_fd[1];
188 close(in_fd[0]);
189 } else
190 result->in_fd = -1;
191
192 if (pipeflag & 2)
193 {
194 result->out_fd = out_fd[0];
195 close(out_fd[1]);
196 } else
197 result->out_fd = -1;
198
199 if (pipeflag & 4)
200 {
201 result->err_fd = err_fd[0];
202 close(err_fd[1]);
203 } else
204 result->err_fd = -1;
205 }
206
207 return result;
208 `}
209 end
210
211 # `Process` on which the `stdout` is readable like a `Reader`
212 class ProcessReader
213 super Process
214 super Reader
215
216 # File Descriptor used for the input.
217 var stream_in: FileReader is noinit
218
219 redef fun close do stream_in.close
220
221 redef fun read_char do return stream_in.read_char
222
223 redef fun read_byte do return stream_in.read_byte
224
225 redef fun eof do return stream_in.eof
226
227 redef fun pipeflags do return 2
228
229 redef fun execute
230 do
231 super
232 stream_in = new FileReader.from_fd(data.out_fd)
233 end
234 end
235
236 # `Process` on which `stdin` is writable like a `Writer`
237 class ProcessWriter
238 super Process
239 super Writer
240
241 # File Descriptor used for the output.
242 var stream_out: Writer is noinit
243
244 redef fun close do stream_out.close
245
246 redef fun is_writable do return stream_out.is_writable
247
248 redef fun write(s) do stream_out.write(s)
249
250 redef fun pipeflags do return 1
251
252 redef fun execute
253 do
254 super
255 var out = new FileWriter.from_fd(data.in_fd)
256 out.set_buffering_mode(0, sys.buffer_mode_none)
257 stream_out = out
258 end
259 end
260
261 # `Process` on which stdout can be read and stdin can be written to like a `Duplex`
262 class ProcessDuplex
263 super ProcessReader
264 super ProcessWriter
265 super Duplex
266
267 redef fun close
268 do
269 stream_in.close
270 stream_out.close
271 end
272
273 redef fun pipeflags do return 3
274
275 redef fun execute do super
276
277 # Write `input` to process and return its output
278 #
279 # Writing and reading are processed line by line,
280 # reading only when something is available.
281 #
282 # ~~~
283 # var proc = new ProcessDuplex("tr", "[:lower:]", "[:upper:]")
284 # assert proc.write_and_read("""
285 # Alice
286 # Bob
287 # """) == """
288 # ALICE
289 # BOB
290 # """
291 # ~~~
292 fun write_and_read(input: Text): String
293 do
294 var read = new Buffer #new Array[String]
295
296 # Main loop, read and write line by line
297 var prev = 0
298 for delimiter in input.search_all('\n') do
299 write input.substring(prev, delimiter.after-prev)
300 prev = delimiter.after
301
302 while stream_in.poll_in do
303 read.append stream_in.read_line
304 end
305 end
306
307 # Write the last line
308 write input.substring_from(prev)
309 stream_out.close
310
311 # Read the rest, may be everything for some programs
312 read.append stream_in.read_all
313 stream_in.close
314
315 # Clean up
316 wait
317 return read.to_s
318 end
319 end
320
321 redef class Sys
322 # Execute a shell command and return its error code
323 fun system(command: String): Int
324 do
325 return command.to_cstring.system
326 end
327
328 # The pid of the program
329 fun pid: Int `{ return getpid(); `}
330 end
331
332 redef class NativeString
333 # Execute self as a shell command.
334 #
335 # See the posix function system(3).
336 fun system: Int `{
337 int status = system(self);
338 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) {
339 // system exited on SIGINT: in my opinion the user wants the main to be discontinued
340 kill(getpid(), SIGINT);
341 }
342 return status;
343 `}
344 end
345
346 private extern class NativeProcess `{ se_exec_data_t* `}
347
348 fun id: Int `{ return self->id; `}
349 fun status: Int `{ return self->status; `}
350 fun in_fd: Int `{ return self->in_fd; `}
351 fun out_fd: Int `{ return self->out_fd; `}
352 fun err_fd: Int `{ return self->err_fd; `}
353
354 fun is_finished: Bool `{
355 int result = (int)0;
356 int status;
357 if (self->running) {
358 int id = waitpid(self->id, &status, WNOHANG);
359 if (id != 0) {
360 /* child is finished */
361 result = (int)(id == self->id);
362 self->status = WEXITSTATUS(status);
363 self->running = 0;
364 }
365 }
366 else{
367 result = (int)1;
368 }
369 return result;
370 `}
371
372 fun wait `{
373 int status;
374 if (self->running) {
375 waitpid(self->id, &status, 0);
376 self->status = WEXITSTATUS(status);
377 self->running = 0;
378 }
379 `}
380 end