Merge: Added contributing guidelines and link from 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 var arguments = self.arguments
102 if arguments != null then
103 for a in arguments do
104 args.add('\0')
105 #a.output_class_name
106 args.append(a)
107 end
108 l += arguments.length
109 end
110 data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, l, pipeflags)
111 end
112
113 private var data: NativeProcess
114 private fun basic_exec_execute(prog, args: NativeString, argc: Int, pipeflag: Int): NativeProcess `{
115 se_exec_data_t* result = NULL;
116 int id;
117 int in_fd[2];
118 int out_fd[2];
119 int err_fd[2];
120 if (pipeflag & 1) {
121 int res = pipe(in_fd);
122 if ( res == -1 ) {
123 return NULL;
124 }
125 }
126 if (pipeflag & 2) {
127 int res = pipe(out_fd);
128 if ( res == -1 ) {
129 return NULL;
130 }
131 }
132 if (pipeflag & 4) {
133 int res = pipe(err_fd);
134 if ( res == -1 ) {
135 return NULL;
136 }
137 }
138
139 id = fork();
140 if (id == 0)
141 { /* child */
142 char **arg = malloc(sizeof(char*) * (argc+1));
143 char *c = args;
144 int i;
145
146 /* Prepare args */
147 for(i=0; i<argc; i++)
148 {
149 arg[i] = c;
150 c += strlen(c) + 1;
151 }
152 arg[argc] = NULL;
153
154 /* Connect pipe */
155 if (pipeflag & 1)
156 {
157 close(0);
158 dup2(in_fd[0], 0);
159 close(in_fd[0]);
160 close(in_fd[1]);
161 }
162 if (pipeflag & 2)
163 {
164 close(1);
165 dup2(out_fd[1], 1);
166 close(out_fd[0]);
167 close(out_fd[1]);
168 }
169 if (pipeflag & 4)
170 {
171 close(2);
172 dup2(err_fd[1], 2);
173 close(err_fd[0]);
174 close(err_fd[1]);
175 }
176
177 /* calls */
178 execvp(prog, arg);
179 _exit(127);
180 }
181 else if (id > 0)
182 { /* father */
183 result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t));
184 result->id = id;
185 result->running = 1;
186 if (pipeflag & 1)
187 {
188 result->in_fd = in_fd[1];
189 close(in_fd[0]);
190 } else
191 result->in_fd = -1;
192
193 if (pipeflag & 2)
194 {
195 result->out_fd = out_fd[0];
196 close(out_fd[1]);
197 } else
198 result->out_fd = -1;
199
200 if (pipeflag & 4)
201 {
202 result->err_fd = err_fd[0];
203 close(err_fd[1]);
204 } else
205 result->err_fd = -1;
206 }
207
208 return result;
209 `}
210 end
211
212 # `Process` on which the `stdout` is readable like a `Reader`
213 class ProcessReader
214 super Process
215 super Reader
216
217 # File Descriptor used for the input.
218 var stream_in: FileReader is noinit
219
220 redef fun close do stream_in.close
221
222 redef fun read_char do return stream_in.read_char
223
224 redef fun read_byte do return stream_in.read_byte
225
226 redef fun eof do return stream_in.eof
227
228 redef fun pipeflags do return 2
229
230 redef fun execute
231 do
232 super
233 stream_in = new FileReader.from_fd(data.out_fd)
234 end
235 end
236
237 # `Process` on which `stdin` is writable like a `Writer`
238 class ProcessWriter
239 super Process
240 super Writer
241
242 # File Descriptor used for the output.
243 var stream_out: Writer is noinit
244
245 redef fun close do stream_out.close
246
247 redef fun is_writable do return stream_out.is_writable
248
249 redef fun write(s) do stream_out.write(s)
250
251 redef fun pipeflags do return 1
252
253 redef fun execute
254 do
255 super
256 var out = new FileWriter.from_fd(data.in_fd)
257 out.set_buffering_mode(0, sys.buffer_mode_none)
258 stream_out = out
259 end
260 end
261
262 # `Process` on which stdout can be read and stdin can be written to like a `Duplex`
263 class ProcessDuplex
264 super ProcessReader
265 super ProcessWriter
266 super Duplex
267
268 redef fun close
269 do
270 stream_in.close
271 stream_out.close
272 end
273
274 redef fun pipeflags do return 3
275
276 redef fun execute do super
277
278 # Write `input` to process and return its output
279 #
280 # Writing and reading are processed line by line,
281 # reading only when something is available.
282 #
283 # ~~~
284 # var proc = new ProcessDuplex("tr", "[:lower:]", "[:upper:]")
285 # assert proc.write_and_read("""
286 # Alice
287 # Bob
288 # """) == """
289 # ALICE
290 # BOB
291 # """
292 # ~~~
293 fun write_and_read(input: Text): String
294 do
295 var read = new Buffer #new Array[String]
296
297 # Main loop, read and write line by line
298 var prev = 0
299 for delimiter in input.search_all('\n') do
300 write input.substring(prev, delimiter.after-prev)
301 prev = delimiter.after
302
303 while stream_in.poll_in do
304 read.append stream_in.read_line
305 end
306 end
307
308 # Write the last line
309 write input.substring_from(prev)
310 stream_out.close
311
312 # Read the rest, may be everything for some programs
313 read.append stream_in.read_all
314 stream_in.close
315
316 # Clean up
317 wait
318 return read.to_s
319 end
320 end
321
322 redef class Sys
323 # Execute a shell command and return its error code
324 fun system(command: String): Int
325 do
326 return command.to_cstring.system
327 end
328
329 # The pid of the program
330 fun pid: Int `{ return getpid(); `}
331 end
332
333 redef class NativeString
334 # Execute self as a shell command.
335 #
336 # See the posix function system(3).
337 fun system: Int `{
338 int status = system(self);
339 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) {
340 // system exited on SIGINT: in my opinion the user wants the main to be discontinued
341 kill(getpid(), SIGINT);
342 }
343 return status;
344 `}
345 end
346
347 private extern class NativeProcess `{ se_exec_data_t* `}
348
349 fun id: Int `{ return self->id; `}
350 fun status: Int `{ return self->status; `}
351 fun in_fd: Int `{ return self->in_fd; `}
352 fun out_fd: Int `{ return self->out_fd; `}
353 fun err_fd: Int `{ return self->err_fd; `}
354
355 fun is_finished: Bool `{
356 int result = (int)0;
357 int status;
358 if (self->running) {
359 int id = waitpid(self->id, &status, WNOHANG);
360 if (id != 0) {
361 /* child is finished */
362 result = (int)(id == self->id);
363 self->status = WEXITSTATUS(status);
364 self->running = 0;
365 }
366 }
367 else{
368 result = (int)1;
369 }
370 return result;
371 `}
372
373 fun wait `{
374 int status;
375 if (self->running) {
376 waitpid(self->id, &status, 0);
377 self->status = WEXITSTATUS(status);
378 self->running = 0;
379 }
380 `}
381 end