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