/* * Copyright (C)2005-2015 Haxe Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ // ported from NekoVM #include #include #include #include #include #ifdef _WIN32 # include #else # include # include # include # include # include # ifndef __APPLE__ # if defined(__FreeBSD__) || defined(__DragonFly__) # include # else # include # endif # endif #endif #ifdef _WIN32 # define POSIX_LABEL(name) # define HANDLE_EINTR(label) # define HANDLE_FINTR(f,label) #else # include # define POSIX_LABEL(name) name: # define HANDLE_EINTR(label) if( errno == EINTR ) goto label # define HANDLE_FINTR(f,label) if( ferror(f) && errno == EINTR ) goto label #endif // --- neko-to-caml api -- #define val_check(v,t) #define val_check_kind(v,k) #define val_data(v) v #define val_array_size(v) Wosize_val(v) #define val_array_ptr(v) (&Field(v,0)) #define val_string(v) String_val(v) #define val_strlen(v) caml_string_length(v) #define alloc_abstract(_,data) ((value)data) #define alloc_int(i) Val_int(i) #define val_gc(v,callb) #define val_null Val_int(0) #define val_some(v) Field(v,0) #define val_int(v) Int_val(v) #define DEFINE_KIND(_) #define neko_error() failwith(__FUNCTION__) static value alloc_private( int size ) { return alloc((size + sizeof(value) - 1) / sizeof(value), Abstract_tag); } // --- buffer api #define EXTERN typedef struct _stringitem { char *str; int size; int len; struct _stringitem *next; } * stringitem; struct _buffer { int totlen; int blen; stringitem data; }; typedef struct _buffer *buffer; static void buffer_append_new( buffer b, const char *s, int len ) { int size; stringitem it; while( b->totlen >= (b->blen << 2) ) b->blen <<= 1; size = (len < b->blen)?b->blen:len; it = (stringitem)malloc(sizeof(struct _stringitem)); it->str = (char*)malloc(size); memcpy(it->str,s,len); it->size = size; it->len = len; it->next = b->data; b->data = it; } EXTERN void buffer_append_sub( buffer b, const char *s, int len ) { stringitem it; if( s == NULL || len <= 0 ) return; b->totlen += len; it = b->data; if( it ) { int free = it->size - it->len; if( free >= len ) { memcpy(it->str + it->len,s,len); it->len += len; return; } else { memcpy(it->str + it->len,s,free); it->len += free; s += free; len -= free; } } buffer_append_new(b,s,len); } EXTERN void buffer_append_str( buffer b, const char *s ) { if( s == NULL ) return; buffer_append_sub(b,s,strlen(s)); } EXTERN buffer alloc_buffer( const char *init ) { buffer b = (buffer)malloc(sizeof(struct _buffer)); b->totlen = 0; b->blen = 16; b->data = NULL; if( init ) buffer_append_str(b,init); return b; } EXTERN void buffer_append_char( buffer b, char c ) { stringitem it; b->totlen++; it = b->data; if( it && it->len != it->size ) { it->str[it->len++] = c; return; } buffer_append_new(b,&c,1); } EXTERN char *buffer_to_string( buffer b ) { char *v = (char*)malloc(b->totlen + 1); stringitem it = b->data; char *s = v + b->totlen; *s = 0; while( it != NULL ) { stringitem tmp; s -= it->len; memcpy(s,it->str,it->len); tmp = it->next; free(it->str); free(it); it = tmp; } free(b); return v; } EXTERN int buffer_length( buffer b ) { return b->totlen; } // --------------- #include #include typedef struct { #ifdef _WIN32 HANDLE oread; HANDLE eread; HANDLE iwrite; PROCESS_INFORMATION pinf; #else int oread; int eread; int iwrite; int pid; #endif } vprocess; DEFINE_KIND(k_process); #define val_process(v) ((vprocess*)val_data(v)) /**

Process

An API for starting and communication with sub processes.

**/ #ifndef _WIN32 static int do_close( int fd ) { POSIX_LABEL(close_again); if( close(fd) != 0 ) { HANDLE_EINTR(close_again); return 1; } return 0; } #endif static void free_process( value vp ) { vprocess *p = val_process(vp); # ifdef _WIN32 CloseHandle(p->eread); CloseHandle(p->oread); CloseHandle(p->iwrite); CloseHandle(p->pinf.hProcess); CloseHandle(p->pinf.hThread); # else do_close(p->eread); do_close(p->oread); do_close(p->iwrite); # endif } /** process_run : cmd:string -> args:string array option -> 'process Start a process using a command and the specified arguments. When args is not null, cmd and args will be auto-quoted/escaped. If no auto-quoting/escaping is desired, you should append necessary arguments to cmd as if it is inputted to the shell directly, and pass null as args. **/ CAMLprim value process_run( value cmd, value vargs ) { CAMLparam2(cmd,vargs); int i, isRaw; vprocess *p; val_check(cmd,string); isRaw = vargs == val_null; if (!isRaw) { val_check(vargs,array); vargs = val_some(vargs); } # ifdef _WIN32 { SECURITY_ATTRIBUTES sattr; STARTUPINFO sinf; HANDLE proc = GetCurrentProcess(); HANDLE oread,eread,iwrite; // creates commandline buffer b = alloc_buffer(NULL); char *sargs; if (isRaw) { char* cmdexe; buffer_append_char(b,'"'); cmdexe = getenv("COMSPEC"); if (!cmdexe) cmdexe = "cmd.exe"; buffer_append_str(b,cmdexe); buffer_append_char(b,'"'); buffer_append_str(b,"/C \""); buffer_append_str(b,val_string(cmd)); buffer_append_char(b,'"'); } else { buffer_append_char(b,'"'); buffer_append_str(b,val_string(cmd)); buffer_append_char(b,'"'); for(i=0;ioread,0,FALSE,DUPLICATE_SAME_ACCESS); DuplicateHandle(proc,eread,proc,&p->eread,0,FALSE,DUPLICATE_SAME_ACCESS); DuplicateHandle(proc,iwrite,proc,&p->iwrite,0,FALSE,DUPLICATE_SAME_ACCESS); CloseHandle(oread); CloseHandle(eread); CloseHandle(iwrite); if( !CreateProcess(NULL,val_string(sargs),NULL,NULL,TRUE,0,NULL,NULL,&sinf,&p->pinf) ) { CloseHandle(p->eread); CloseHandle(p->oread); CloseHandle(p->iwrite); free(sargs); neko_error(); } free(sargs); // close unused pipes CloseHandle(sinf.hStdOutput); CloseHandle(sinf.hStdError); CloseHandle(sinf.hStdInput); } # else char **argv; if (isRaw) { argv = (char**)alloc_private(sizeof(char*)*4); argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = val_string(cmd); argv[3] = NULL; } else { argv = (char**)alloc_private(sizeof(char*)*(val_array_size(vargs)+2)); argv[0] = val_string(cmd); for(i=0;ipid = fork(); if( p->pid == -1 ) { do_close(input[0]); do_close(input[1]); do_close(output[0]); do_close(output[1]); do_close(error[0]); do_close(error[1]); neko_error(); } // child if( p->pid == 0 ) { close(input[1]); close(output[0]); close(error[0]); dup2(input[0],0); dup2(output[1],1); dup2(error[1],2); execvp(argv[0],argv); fprintf(stderr,"Command not found : %s\n",val_string(cmd)); exit(1); } // parent do_close(input[0]); do_close(output[1]); do_close(error[1]); p->iwrite = input[1]; p->oread = output[0]; p->eread = error[0]; # endif { CAMLlocal1(vp); vp = alloc_abstract(k_process,p); val_gc(vp,free_process); CAMLreturn(vp); } } #define CHECK_ARGS() \ vprocess *p; \ val_check_kind(vp,k_process); \ val_check(str,string); \ val_check(pos,int); \ val_check(len,int); \ if( val_int(pos) < 0 || val_int(len) < 0 || val_int(pos) + val_int(len) > val_strlen(str) ) \ neko_error(); \ p = val_process(vp); \ /** process_stdout_read : 'process -> buf:string -> pos:int -> len:int -> int Read up to [len] bytes in [buf] starting at [pos] from the process stdout. Returns the number of bytes read this way. Raise an exception if this process stdout is closed and no more data is available for reading. **/ CAMLprim value process_stdout_read( value vp, value str, value pos, value len ) { CHECK_ARGS(); # ifdef _WIN32 { DWORD nbytes; if( !ReadFile(p->oread,val_string(str)+val_int(pos),val_int(len),&nbytes,NULL) ) neko_error(); return alloc_int(nbytes); } # else int nbytes; POSIX_LABEL(stdout_read_again); nbytes = read(p->oread,val_string(str)+val_int(pos),val_int(len)); if( nbytes < 0 ) { HANDLE_EINTR(stdout_read_again); neko_error(); } if( nbytes == 0 ) neko_error(); return alloc_int(nbytes); # endif } /** process_stderr_read : 'process -> buf:string -> pos:int -> len:int -> int Read up to [len] bytes in [buf] starting at [pos] from the process stderr. Returns the number of bytes read this way. Raise an exception if this process stderr is closed and no more data is available for reading. **/ CAMLprim value process_stderr_read( value vp, value str, value pos, value len ) { CHECK_ARGS(); # ifdef _WIN32 { DWORD nbytes; if( !ReadFile(p->eread,val_string(str)+val_int(pos),val_int(len),&nbytes,NULL) ) neko_error(); return alloc_int(nbytes); } # else int nbytes; POSIX_LABEL(stderr_read_again); nbytes = read(p->eread,val_string(str)+val_int(pos),val_int(len)); if( nbytes < 0 ) { HANDLE_EINTR(stderr_read_again); neko_error(); } if( nbytes == 0 ) neko_error(); return alloc_int(nbytes); # endif } /** process_stdin_write : 'process -> buf:string -> pos:int -> len:int -> int Write up to [len] bytes from [buf] starting at [pos] to the process stdin. Returns the number of bytes writen this way. Raise an exception if this process stdin is closed. **/ CAMLprim value process_stdin_write( value vp, value str, value pos, value len ) { CHECK_ARGS(); # ifdef _WIN32 { DWORD nbytes; if( !WriteFile(p->iwrite,val_string(str)+val_int(pos),val_int(len),&nbytes,NULL) ) neko_error(); return alloc_int(nbytes); } # else int nbytes; POSIX_LABEL(stdin_write_again); nbytes = write(p->iwrite,val_string(str)+val_int(pos),val_int(len)); if( nbytes == -1 ) { HANDLE_EINTR(stdin_write_again); neko_error(); } return alloc_int(nbytes); # endif } /** process_stdin_close : 'process -> void Close the process standard input. **/ CAMLprim value process_stdin_close( value vp ) { vprocess *p; val_check_kind(vp,k_process); p = val_process(vp); # ifdef _WIN32 if( !CloseHandle(p->iwrite) ) neko_error(); # else if( do_close(p->iwrite) ) neko_error(); p->iwrite = -1; # endif return val_null; } /** process_exit : 'process -> int Wait until the process terminate, then returns its exit code. **/ CAMLprim value process_exit( value vp ) { vprocess *p; val_check_kind(vp,k_process); p = val_process(vp); # ifdef _WIN32 { DWORD rval; WaitForSingleObject(p->pinf.hProcess,INFINITE); if( !GetExitCodeProcess(p->pinf.hProcess,&rval) ) neko_error(); return alloc_int(rval); } # else int rval; while( waitpid(p->pid,&rval,0) != p->pid ) { if( errno == EINTR ) continue; neko_error(); } if( !WIFEXITED(rval) ) neko_error(); return alloc_int(WEXITSTATUS(rval)); # endif } /** process_pid : 'process -> int Returns the process id. **/ CAMLprim value process_pid( value vp ) { vprocess *p; val_check_kind(vp,k_process); p = val_process(vp); # ifdef _WIN32 return alloc_int(p->pinf.dwProcessId); # else return alloc_int(p->pid); # endif } /** process_close : 'process -> void Close the process I/O. **/ CAMLprim value process_close( value vp ) { val_check_kind(vp,k_process); free_process(vp); //val_kind(vp) = NULL; //val_gc(vp,NULL); return val_null; } /** process_kill : 'process -> void Terminates a running process. **/ CAMLprim value process_kill( value vp ) { val_check_kind(vp,k_process); # ifdef _WIN32 TerminateProcess(val_process(vp)->pinf.hProcess,-1); # else kill(val_process(vp)->pid,9); # endif return val_null; } /* ************************************************************************ */