X-Git-Url: http://nitlanguage.org diff --git a/lib/standard/exec.nit b/lib/standard/exec.nit index 97c847e..3e0d97a 100644 --- a/lib/standard/exec.nit +++ b/lib/standard/exec.nit @@ -5,171 +5,374 @@ # # This file is free software, which comes along with NIT. This software is # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; -# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. You can modify it is you want, provided this header # is kept unaltered, and a notification of the changes is added. # You are allowed to redistribute it and sell it, alone or is a part of # another product. -# This module handle simple system calls -# Standard input and output can be handleb trougth streams. -package exec +# Invocation and management of operating system sub-processes. +# Standard input and output can be handled through streams. +module exec -import stream +import file -# Simple sub-processus -class Process +in "C" `{ + #include + #include + #include + #include + #include + #include + #include +`} + +in "C Header" `{ + #include + + // FIXME this should be in the "C" block when bug on module blocks is fixed + // or, even better, replace the C structure by a Nit object. + typedef struct se_exec_data se_exec_data_t; + struct se_exec_data { + pid_t id; + int running; + int status; + int in_fd; + int out_fd; + int err_fd; + }; +`} - # The pid of the processus - fun id: Int do return _data.id +# Simple sub-process +class Process + # The pid of the process + fun id: Int do return data.id - # Is the processus finished? - fun is_finished: Bool do return _data.is_finished + # Is the process finished? + fun is_finished: Bool do return data.is_finished - # wait the terminaison of the process + # Wait the termination of the process fun wait do - _data.wait + data.wait assert is_finished end - + # The status once finished fun status: Int do assert is_finished - return _data.status + return data.status end - # launch a command with some arguments - init(command: String, arguments: String...) - do - execute(command, arguments, 0) + # The executable run + # Is a filepath, or a executable found in PATH + var command: String + + # The arguments of the command + # Starts with the first real arguments---ie. does not include the progname (`argv[0]`, in C) + var arguments: nullable Array[String] + + # Launch a command with some arguments + init(command: String, arguments: String...) is old_style_init do + self.command = command + self.arguments = arguments + execute end - # launch a simple command without arguments - init init_(command: String) + # Launch a simple command with arguments passed as an array + init from_a(command: String, arguments: nullable Array[String]) do - execute(command, null, 0) + self.command = command + self.arguments = arguments + execute end - # Internal code to handle execusion - protected init execute(command: String, arguments: nullable Array[String], pipeflags: Int) + # flags used internally to know whith pipe to open + private fun pipeflags: Int do return 0 + + # Internal code to handle execution + protected fun execute do - var args = new Buffer + # The pass the arguments as a big C string where elements are separated with '\0' + var args = new FlatBuffer var l = 1 # Number of elements in args args.append(command) if arguments != null then for a in arguments do args.add('\0') + #a.output_class_name args.append(a) end l += arguments.length end - _data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, l, pipeflags) + data = basic_exec_execute(command.to_cstring, args.to_s.to_cstring, l, pipeflags) end - - var _data: NativeProcess - private fun basic_exec_execute(p: NativeString, av: NativeString, ac: Int, pf: Int): NativeProcess is extern "exec_Process_Process_basic_exec_execute_4" + + private var data: NativeProcess + private fun basic_exec_execute(prog, args: NativeString, argc: Int, pipeflag: Int): NativeProcess `{ + se_exec_data_t* result = NULL; + int id; + int in_fd[2]; + int out_fd[2]; + int err_fd[2]; + if (pipeflag & 1) { + int res = pipe(in_fd); + if ( res == -1 ) { + return NULL; + } + } + if (pipeflag & 2) { + int res = pipe(out_fd); + if ( res == -1 ) { + return NULL; + } + } + if (pipeflag & 4) { + int res = pipe(err_fd); + if ( res == -1 ) { + return NULL; + } + } + + id = fork(); + if (id == 0) + { /* child */ + char **arg = malloc(sizeof(char*) * (argc+1)); + char *c = args; + int i; + + /* Prepare args */ + for(i=0; i 0) + { /* father */ + result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t)); + result->id = id; + result->running = 1; + if (pipeflag & 1) + { + result->in_fd = in_fd[1]; + close(in_fd[0]); + } else + result->in_fd = -1; + + if (pipeflag & 2) + { + result->out_fd = out_fd[0]; + close(out_fd[1]); + } else + result->out_fd = -1; + + if (pipeflag & 4) + { + result->err_fd = err_fd[0]; + close(err_fd[1]); + } else + result->err_fd = -1; + } + + return result; + `} end -# stdout of the processus is readable -class IProcess +# `Process` on which the `stdout` is readable like a `Reader` +class ProcessReader super Process - super IStream - var _in: FDIStream - - redef fun close do _in.close - - redef fun read_char do return _in.read_char + super Reader - redef fun eof do return _in.eof + # File Descriptor used for the input. + var stream_in: FileReader is noinit - init(command: String, arguments: String...) - do - execute(command, arguments, 2) - _in = new FDIStream(_data.out_fd) - end - - init init_(command: String) + redef fun close do stream_in.close + + redef fun read_char do return stream_in.read_char + + redef fun read_byte do return stream_in.read_byte + + redef fun eof do return stream_in.eof + + redef fun pipeflags do return 2 + + redef fun execute do - execute(command, null, 2) - _in = new FDIStream(_data.out_fd) + super + stream_in = new FileReader.from_fd(data.out_fd) end end -# stdin of the processus is writable -class OProcess +# `Process` on which `stdin` is writable like a `Writer` +class ProcessWriter super Process - super OStream - var _out: OStream + super Writer - redef fun close do _out.close + # File Descriptor used for the output. + var stream_out: Writer is noinit - redef fun is_writable do return _out.is_writable + redef fun close do stream_out.close - redef fun write(s) do _out.write(s) - - init(command: String, arguments: String...) - do - execute(command, arguments, 1) - _out = new FDOStream(_data.in_fd) - end - - init init_(command: String) + redef fun is_writable do return stream_out.is_writable + + redef fun write(s) do stream_out.write(s) + + redef fun pipeflags do return 1 + + redef fun execute do - execute(command, null, 1) - _out = new FDOStream(_data.in_fd) + super + var out = new FileWriter.from_fd(data.in_fd) + out.set_buffering_mode(0, sys.buffer_mode_none) + stream_out = out end end -# stdin and stdout are both accessible -class IOProcess - super IProcess - super OProcess - super IOStream +# `Process` on which stdout can be read and stdin can be written to like a `Duplex` +class ProcessDuplex + super ProcessReader + super ProcessWriter + super Duplex redef fun close do - _in.close - _out.close + stream_in.close + stream_out.close end - init(command: String, arguments: String...) - do - execute(command, arguments, 3) - _in = new FDIStream(_data.out_fd) - _out = new FDOStream(_data.in_fd) - end - - init init_(command: String) + redef fun pipeflags do return 3 + + redef fun execute do super + + # Write `input` to process and return its output + # + # Writing and reading are processed line by line, + # reading only when something is available. + # + # ~~~ + # var proc = new ProcessDuplex("tr", "[:lower:]", "[:upper:]") + # assert proc.write_and_read(""" + # Alice + # Bob + # """) == """ + # ALICE + # BOB + # """ + # ~~~ + fun write_and_read(input: Text): String do - execute(command, null, 3) - _in = new FDIStream(_data.out_fd) - _out = new FDOStream(_data.in_fd) + var read = new Buffer #new Array[String] + + # Main loop, read and write line by line + var prev = 0 + for delimiter in input.search_all('\n') do + write input.substring(prev, delimiter.after-prev) + prev = delimiter.after + + while stream_in.poll_in do + read.append stream_in.read_line + end + end + + # Write the last line + write input.substring_from(prev) + stream_out.close + + # Read the rest, may be everything for some programs + read.append stream_in.read_all + stream_in.close + + # Clean up + wait + return read.to_s end end redef class Sys - # Execute a shell command and return it's error code + # Execute a shell command and return its error code fun system(command: String): Int do - return command.to_cstring.system + return command.to_cstring.system end end redef class NativeString - fun system: Int is extern "string_NativeString_NativeString_system_0" + # Execute self as a shell command. + # + # See the posix function system(3). + fun system: Int `{ + int status = system(self); + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) { + // system exited on SIGINT: in my opinion the user wants the main to be discontinued + kill(getpid(), SIGINT); + } + return status; + `} end -private extern NativeProcess - super Pointer - fun id: Int is extern "exec_NativeProcess_NativeProcess_id_0" - fun is_finished: Bool is extern "exec_NativeProcess_NativeProcess_is_finished_0" - fun status: Int is extern "exec_NativeProcess_NativeProcess_status_0" - fun wait is extern "exec_NativeProcess_NativeProcess_wait_0" +private extern class NativeProcess `{ se_exec_data_t* `} + + fun id: Int `{ return self->id; `} + fun status: Int `{ return self->status; `} + fun in_fd: Int `{ return self->in_fd; `} + fun out_fd: Int `{ return self->out_fd; `} + fun err_fd: Int `{ return self->err_fd; `} + + fun is_finished: Bool `{ + int result = (int)0; + int status; + if (self->running) { + int id = waitpid(self->id, &status, WNOHANG); + if (id != 0) { + /* child is finished */ + result = (int)(id == self->id); + self->status = WEXITSTATUS(status); + self->running = 0; + } + } + else{ + result = (int)1; + } + return result; + `} - fun in_fd: Int is extern "exec_NativeProcess_NativeProcess_in_fd_0" - fun out_fd: Int is extern "exec_NativeProcess_NativeProcess_out_fd_0" - fun err_fd: Int is extern "exec_NativeProcess_NativeProcess_err_fd_0" + fun wait `{ + int status; + if (self->running) { + waitpid(self->id, &status, 0); + self->status = WEXITSTATUS(status); + self->running = 0; + } + `} end