X-Git-Url: http://nitlanguage.org diff --git a/lib/core/exec.nit b/lib/core/exec.nit index 6c03c7f..fedc5de 100644 --- a/lib/core/exec.nit +++ b/lib/core/exec.nit @@ -23,17 +23,22 @@ in "C" `{ #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. +#ifdef _WIN32 + #include + #include +#else + #include +#endif + typedef struct se_exec_data se_exec_data_t; struct se_exec_data { +#ifdef _WIN32 + HANDLE h_process; + HANDLE h_thread; +#endif pid_t id; int running; int status; @@ -59,59 +64,178 @@ class Process end # The status once finished + # + # Require: `is_finished` fun status: Int do assert is_finished return data.status end - # The executable run - # Is a filepath, or a executable found in PATH - var command: String + # The target executable + # Either a file path or the name of an executable available in PATH. + var command: Text # 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] + var arguments: nullable Array[Text] # Launch a command with some arguments - init(command: String, arguments: String...) is old_style_init do + init(command: Text, arguments: Text...) is old_style_init do self.command = command self.arguments = arguments execute end # Launch a simple command with arguments passed as an array - init from_a(command: String, arguments: nullable Array[String]) + init from_a(command: Text, arguments: nullable Array[Text]) do self.command = command self.arguments = arguments execute end - # flags used internally to know whith pipe to open + # Flags used internally to know which pipe to open private fun pipeflags: Int do return 0 # Internal code to handle execution protected fun execute do - # 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) var arguments = self.arguments - if arguments != null then - for a in arguments do - args.add('\0') - #a.output_class_name - args.append(a) + + var args = new FlatBuffer + var argc = 1 + + if not is_windows then + # Pass the arguments as a big C string where elements are separated with '\0' + args.append command + if arguments != null then + for a in arguments do + args.add '\0' + args.append a + end + argc += arguments.length + end + else + # Combine the program and args in a single string + assert not command.chars.has('"') + args = new FlatBuffer + + args.add '"' + args.append command + args.add '"' + + if arguments != null then + for a in arguments do + args.append " \"" + args.append a.replace('"', "\\\"") + args.add '"' + end 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, argc, pipeflags) + assert not data.address_is_null else print_error "Internal error executing: {command}" end private var data: NativeProcess - private fun basic_exec_execute(prog, args: NativeString, argc: Int, pipeflag: Int): NativeProcess `{ + + private fun basic_exec_execute(prog, args: CString, argc: Int, pipeflag: Int): NativeProcess `{ +#ifdef _WIN32 + SECURITY_ATTRIBUTES sec_attr; + sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attr.bInheritHandle = TRUE; + sec_attr.lpSecurityDescriptor = NULL; + + STARTUPINFO start_info; + ZeroMemory(&start_info, sizeof(STARTUPINFO)); + start_info.cb = sizeof(STARTUPINFO); + start_info.dwFlags = STARTF_USESTDHANDLES; + + HANDLE in_fd[2]; + HANDLE out_fd[2]; + HANDLE err_fd[2]; + + se_exec_data_t *result = (se_exec_data_t*)malloc(sizeof(se_exec_data_t)); + + // Redirect stdin? + if (pipeflag & 1) { + if (!CreatePipe(&in_fd[0], &in_fd[1], &sec_attr, 0)) { + return NULL; + } + start_info.hStdInput = in_fd[0]; + result->in_fd = _open_osfhandle((intptr_t)in_fd[1], _O_WRONLY); + if ( !SetHandleInformation(in_fd[1], HANDLE_FLAG_INHERIT, 0) ) + return NULL; + } else { + start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + result->in_fd = -1; + } + + // Redirect stdout? + if (pipeflag & 2) { + if (!CreatePipe(&out_fd[0], &out_fd[1], &sec_attr, 0)) { + return NULL; + } + start_info.hStdOutput = out_fd[1]; + result->out_fd = _open_osfhandle((intptr_t)out_fd[0], _O_RDONLY); + if ( !SetHandleInformation(out_fd[0], HANDLE_FLAG_INHERIT, 0) ) + return NULL; + } else { + start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + result->out_fd = -1; + } + + // Redirect stderr? + if (pipeflag & 4) { + if (!CreatePipe(&err_fd[0], &err_fd[1], &sec_attr, 0)) { + return NULL; + } + start_info.hStdError = err_fd[1]; + result->err_fd = _open_osfhandle((intptr_t)err_fd[0], _O_RDONLY); + if ( !SetHandleInformation(err_fd[0], HANDLE_FLAG_INHERIT, 0) ) + return NULL; + } else { + start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + result->err_fd = -1; + } + + PROCESS_INFORMATION proc_info; + ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION)); + + BOOL created = CreateProcess(NULL, + args, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // inherit handles + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &start_info, + &proc_info); + + if (pipeflag & 1) CloseHandle(in_fd[0]); + if (pipeflag & 2) CloseHandle(out_fd[1]); + if (pipeflag & 3) CloseHandle(err_fd[1]); + + // Error? + if (!created) { + result->running = 0; + result->status = 127; + + // Close subprocess pipes + if (pipeflag & 1) CloseHandle(in_fd[1]); + if (pipeflag & 2) CloseHandle(out_fd[0]); + if (pipeflag & 3) CloseHandle(err_fd[0]); + } else { + result->h_process = proc_info.hProcess; + result->h_thread = proc_info.hThread; + result->id = GetProcessId(proc_info.hProcess); + result->running = 1; + } + + return result; +#else se_exec_data_t* result = NULL; int id; int in_fd[2]; @@ -206,6 +330,7 @@ class Process } return result; +#endif `} end @@ -292,7 +417,7 @@ class ProcessDuplex # ~~~ fun write_and_read(input: Text): String do - var read = new Buffer #new Array[String] + var read = new Buffer # Main loop, read and write line by line var prev = 0 @@ -321,7 +446,7 @@ end redef class Sys # Execute a shell command and return its error code - fun system(command: String): Int + fun system(command: Text): Int do return command.to_cstring.system end @@ -330,23 +455,25 @@ redef class Sys fun pid: Int `{ return getpid(); `} end -redef class NativeString +redef class CString # Execute self as a shell command. # # See the posix function system(3). fun system: Int `{ int status = system(self); +#ifndef _WIN32 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) { // system exited on SIGINT: in my opinion the user wants the main to be discontinued kill(getpid(), SIGINT); } +#endif return status; `} end private extern class NativeProcess `{ se_exec_data_t* `} - fun id: Int `{ return self->id; `} + fun id: Int `{ return (long)self->id; `} fun status: Int `{ return self->status; `} fun in_fd: Int `{ return self->in_fd; `} fun out_fd: Int `{ return self->out_fd; `} @@ -354,8 +481,22 @@ private extern class NativeProcess `{ se_exec_data_t* `} fun is_finished: Bool `{ int result = (int)0; - int status; if (self->running) { +#ifdef _WIN32 + if (WaitForSingleObject(self->h_process, 0) == 0) { + /* child is finished */ + result = 1; + + long unsigned int status; + GetExitCodeProcess(self->h_process, &status); + self->running = 0; + self->status = (int)status; + + CloseHandle(self->h_process); + CloseHandle(self->h_thread); + } +#else + int status; int id = waitpid(self->id, &status, WNOHANG); if (id != 0) { /* child is finished */ @@ -363,6 +504,7 @@ private extern class NativeProcess `{ se_exec_data_t* `} self->status = WEXITSTATUS(status); self->running = 0; } +#endif } else{ result = (int)1; @@ -371,11 +513,25 @@ private extern class NativeProcess `{ se_exec_data_t* `} `} fun wait `{ +#ifdef _WIN32 + long unsigned int status; + if (self->running) { + WaitForSingleObject(self->h_process, INFINITE); + GetExitCodeProcess(self->h_process, &status); + + CloseHandle(self->h_process); + CloseHandle(self->h_thread); + + self->status = (int)status; + self->running = 0; + } +#else int status; if (self->running) { waitpid(self->id, &status, 0); self->status = WEXITSTATUS(status); self->running = 0; } +#endif `} end