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