/* Part of SWI-Prolog Author: Jan Wielemaker E-mail: J.Wielemaker@vu.nl WWW: http://www.swi-prolog.org Copyright (c) 2000-2023, University of Amsterdam VU University Amsterdam CWI, Amsterdam SWI-Prolog Solutions b.v. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #define _CRT_SECURE_NO_WARNINGS 1 /*#define O_DEBUG 1*/ #undef O_DEBUG #define _GNU_SOURCE /* get pipe2() */ #include #include #include "error.h" #include #include #include #include #include #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_FCNTL_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_PRCTL #include #endif #ifdef _REENTRANT #include #endif #ifdef HAVE_CRT_EXTERNS_H #include /* _NSGetEnviron() on MacOS */ #endif static atom_t ATOM_stdin; static atom_t ATOM_stdout; static atom_t ATOM_stderr; static atom_t ATOM_std; static atom_t ATOM_null; static atom_t ATOM_process; static atom_t ATOM_detached; static atom_t ATOM_cwd; static atom_t ATOM_env; static atom_t ATOM_environment; static atom_t ATOM_priority; static atom_t ATOM_window; static atom_t ATOM_timeout; static atom_t ATOM_release; static atom_t ATOM_infinite; static atom_t ATOM_text; static atom_t ATOM_binary; static atom_t ATOM_octet; static atom_t ATOM_utf8; static atom_t ATOM_ascii; static atom_t ATOM_iso_latin_1; static atom_t ATOM_unicode_be; static atom_t ATOM_unicode_le; static functor_t FUNCTOR_error2; static functor_t FUNCTOR_process_error2; static functor_t FUNCTOR_system_error2; static functor_t FUNCTOR_pipe1; static functor_t FUNCTOR_pipe2; static functor_t FUNCTOR_stream1; static functor_t FUNCTOR_exit1; static functor_t FUNCTOR_killed1; static functor_t FUNCTOR_eq2; /* =/2 */ static functor_t FUNCTOR_type1; static functor_t FUNCTOR_encoding1; #define MAYBE 2 #if O_DEBUG #define DEBUG(g) g #else #define DEBUG(g) (void)0 #endif #ifdef __WINDOWS__ #include #include #include #include typedef wchar_t echar; /* environment character */ #define ecslen(s) wcslen(s) #ifndef CREATE_BREAKAWAY_FROM_JOB #define CREATE_BREAKAWAY_FROM_JOB 0x1000000 #endif #else typedef char echar; #define ecslen(s) strlen(s) #endif /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ISSUES: - Deal with child errors (no cwd, cannot execute, etc.) - Complete test suite - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /******************************* * ADMIN * *******************************/ typedef enum std_type { std_std, std_null, std_pipe, std_stream } std_type; typedef struct p_stream { term_t term; /* P in pipe(P) */ std_type type; /* type of stream */ IOENC encoding; /* Encoding for the stream */ #ifdef __WINDOWS__ HANDLE fd[2]; /* pipe handles */ #else int fd[2]; /* pipe handles */ #endif int cloexec; /* close on exec activated */ } p_stream; typedef struct ecbuf { echar *buffer; size_t size; size_t allocated; } ecbuf; typedef struct p_options { atom_t exe_name; /* exe as atom */ #ifdef __WINDOWS__ wchar_t *exe; /* Executable */ wchar_t *cmdline; /* Command line */ wchar_t *cwd; /* CWD of new process */ #else char *exe; /* Executable */ char **argv; /* argument vector */ char *cwd; /* CWD of new process */ char **envp; /* New environment */ #endif ecbuf envbuf; /* environment buffer */ term_t pid; /* process(PID) */ int pipes; /* #pipes found */ p_stream streams[3]; int detached; /* create as detached */ int window; /* Show a window? */ int priority; /* Process priority */ } p_options; typedef struct wait_options { double timeout; int has_timeout; int release; } wait_options; typedef enum create_method { PCREATE_SPAWN, PCREATE_VFORK, PCREATE_FORK } create_method; static create_method create_process_method = PCREATE_SPAWN; #ifdef __WINDOWS__ static int win_command_line(term_t t, size_t arity, const wchar_t *exepath, wchar_t **cmdline); #endif /******************************* * STRING BUFFER * *******************************/ static void free_ecbuf(ecbuf *b) { if ( b->buffer ) { PL_free(b->buffer); b->buffer = NULL; } } static int add_ecbuf(ecbuf *b, echar *data, size_t len) { if ( b->size + len > b->allocated ) { size_t newsize = (b->allocated ? b->allocated * 2 : 2048); while( b->size + len > newsize ) newsize *= 2; if ( b->buffer ) { b->buffer = PL_realloc(b->buffer, newsize*sizeof(echar)); } else { b->buffer = PL_malloc(newsize*sizeof(echar)); } b->allocated = newsize; } memcpy(b->buffer+b->size, data, len*sizeof(echar)); b->size += len; return TRUE; } /******************************* * ENVIRONMENT PARSING * *******************************/ static int get_echars_arg_ex(int i, term_t from, term_t arg, echar **sp, size_t *lenp) { const echar *s, *e; if ( !PL_get_arg(i, from, arg) ) return FALSE; #ifdef __WINDOWS__ if ( !PL_get_wchars(arg, lenp, sp, CVT_ATOMIC|CVT_EXCEPTION) ) #else if ( !PL_get_nchars(arg, lenp, sp, CVT_ATOMIC|CVT_EXCEPTION|REP_FN) ) #endif return FALSE; for(s = *sp, e = s+*lenp; s 0; env += ecslen(env)+1) { const echar *s, *q; for(s=env, q=e; *s && *q && *s == *q && *s != '=' && *q != '='; s++,q++) ; if (*s == *q && *s == '=' ) return TRUE; } return FALSE; } static int parse_environment(term_t t, p_options *info, int pass) { term_t tail = PL_copy_term_ref(t); term_t head = PL_new_term_ref(); term_t tmp = PL_new_term_ref(); ecbuf *eb = &info->envbuf; int count = 0; #ifndef __WINDOWS__ echar *q; char **ep; int c = 0; #endif if ( eb->buffer ) return PL_permission_error("redefine", "environment", t); while( PL_get_list(tail, head, tail) ) { echar *s; size_t len; if ( !PL_is_functor(head, FUNCTOR_eq2) ) return PL_type_error("environment_variable", head); if ( !get_echars_arg_ex(1, head, tmp, &s, &len) ) return FALSE; add_ecbuf(eb, s, len); add_ecbuf(eb, ECHARS("="), 1); if ( !get_echars_arg_ex(2, head, tmp, &s, &len) ) return FALSE; add_ecbuf(eb, s, len); add_ecbuf(eb, ECHARS("\0"), 1); count++; } if ( !PL_get_nil_ex(tail) ) return FALSE; if ( pass && count == 0 ) return TRUE; /* environment([]) is a no-op */ if ( pass ) { #ifndef __WINDOWS__ #ifdef HAVE__NSGETENVIRON char **environ = *_NSGetEnviron(); #else extern char **environ; #endif char **e; int count0 = count; for(e=environ; e && *e; e++) { if ( !already_in_env(eb->buffer, count0, *e) ) { add_ecbuf(eb, *e, strlen(*e)); add_ecbuf(eb, ECHARS("\0"), 1); count++; } } #else /*__WINDOWS__*/ echar *env = GetEnvironmentStringsW(); if ( env ) { int count0 = count; while(*env) { size_t len = wcslen(env); if ( !already_in_env(eb->buffer, count0, env) ) { add_ecbuf(eb, env, len+1); count++; } env += len+1; } } #endif /*__WINDOWS__*/ } #ifdef __WINDOWS__ add_ecbuf(eb, ECHARS("\0"), 1); if ( count == 0 ) add_ecbuf(eb, ECHARS("\0"), 1); #else info->envp = PL_malloc((count+1)*sizeof(char*)); for(ep=info->envp, c=0, q=eb->buffer; cbuffer) == eb->size); *ep = NULL; #endif return TRUE; } static int get_type(term_t head, IOENC *enc) { atom_t a; if ( PL_get_atom_ex(head, &a) ) { if ( a == ATOM_text ) *enc = ENC_ANSI; else if ( a == ATOM_binary ) *enc = ENC_OCTET; else return PL_domain_error("stream_type", head); return TRUE; } return FALSE; } /* TBD: provide a public API for translating encoding names to * the IOENC enum */ static int get_encoding(term_t head, IOENC *enc) { atom_t a; if ( PL_get_atom_ex(head, &a) ) { IOENC e; if ( (e=PL_atom_to_encoding(a)) != ENC_UNKNOWN ) { *enc = e; return TRUE; } return PL_domain_error("encoding", head); } return FALSE; } static int get_stream(term_t t, p_options *info, p_stream *stream, atom_t name) { atom_t a; int i; if ( PL_get_atom(t, &a) ) { if ( a == ATOM_null ) { stream->type = std_null; return TRUE; } else if ( a == ATOM_std ) { stream->type = std_std; return TRUE; } else { return PL_domain_error("process_stream", t); } } else if ( PL_is_functor(t, FUNCTOR_pipe1) || PL_is_functor(t, FUNCTOR_pipe2) ) { stream->term = PL_new_term_ref(); stream->encoding = ENC_ANSI; _PL_get_arg(1, t, stream->term); if ( !PL_is_variable(stream->term) ) { for (i = 0; i < info->pipes; i++) { if (PL_compare(info->streams[i].term, t) == 0) break; } if (i == info->pipes) return PL_uninstantiation_error(stream->term); } if ( PL_is_functor(t, FUNCTOR_pipe2) ) { term_t tail = PL_new_term_ref(); term_t head = PL_new_term_ref(); _PL_get_arg(2, t, tail); while(PL_get_list_ex(tail, head, tail)) { if ( PL_is_functor(head, FUNCTOR_type1) ) { _PL_get_arg(1, head, head); if ( !get_type(head, &stream->encoding) ) return FALSE; } else if ( PL_is_functor(head, FUNCTOR_encoding1) ) { _PL_get_arg(1, head, head); if ( !get_encoding(head, &stream->encoding) ) return FALSE; } else return PL_domain_error("pipe_option", head); } if ( !PL_get_nil_ex(tail) ) return FALSE; } stream->type = std_pipe; info->pipes++; return TRUE; } else if ( PL_is_functor(t, FUNCTOR_stream1) ) { IOSTREAM *s; int fd; stream->term = PL_new_term_ref(); _PL_get_arg(1, t, stream->term); if ( !PL_get_stream(stream->term, &s, name == ATOM_stdin ? SIO_INPUT : SIO_OUTPUT) ) return FALSE; stream->type = std_stream; if ( (fd = Sfileno(s)) > 0 ) { #ifdef __WINDOWS__ stream->fd[0] = stream->fd[1] = (HANDLE)_get_osfhandle(fd); #else stream->fd[0] = stream->fd[1] = fd; #endif } else { return PL_domain_error("file_stream", stream->term); } return TRUE; } else return PL_type_error("process_stream", t); } static int parse_options(term_t options, p_options *info) { term_t tail = PL_copy_term_ref(options); term_t head = PL_new_term_ref(); term_t arg = PL_new_term_ref(); info->window = MAYBE; while(PL_get_list(tail, head, tail)) { atom_t name; size_t arity; if ( !PL_get_name_arity(head, &name, &arity) || arity != 1 ) return PL_type_error("option", head); _PL_get_arg(1, head, arg); if ( name == ATOM_stdin ) { if ( !get_stream(arg, info, &info->streams[0], name) ) return FALSE; } else if ( name == ATOM_stdout ) { if ( !get_stream(arg, info, &info->streams[1], name) ) return FALSE; } else if ( name == ATOM_stderr ) { if ( !get_stream(arg, info, &info->streams[2], name) ) return FALSE; } else if ( name == ATOM_process ) { info->pid = PL_copy_term_ref(arg); } else if ( name == ATOM_detached ) { if ( !PL_get_bool_ex(arg, &info->detached) ) return FALSE; } else if ( name == ATOM_cwd ) { #ifdef __WINDOWS__ if ( !PL_get_wchars(arg, NULL, &info->cwd, CVT_ATOM|CVT_STRING|CVT_EXCEPTION|BUF_MALLOC) ) return FALSE; #else if ( !PL_get_chars(arg, &info->cwd, CVT_ATOM|CVT_STRING|CVT_EXCEPTION|BUF_MALLOC|REP_FN) ) return FALSE; #endif } else if ( name == ATOM_window ) { if ( !PL_get_bool_ex(arg, &info->window) ) return FALSE; } else if ( name == ATOM_env ) { if ( !parse_environment(arg, info, FALSE) ) return FALSE; } else if ( name == ATOM_environment ) { if ( !parse_environment(arg, info, TRUE) ) return FALSE; } else if ( name == ATOM_priority ) { int tmp; if ( !PL_get_integer_ex(arg, &tmp) ) return FALSE; if ( tmp < -20 || tmp > 19 ) return PL_domain_error("priority_option", arg); info->priority = tmp; } else return PL_domain_error("process_option", head); } if ( !PL_get_nil_ex(tail) ) return FALSE; return TRUE; } static int get_exe(term_t exe, p_options *info) { size_t arity; term_t arg = PL_new_term_ref(); if ( !PL_get_name_arity(exe, &info->exe_name, &arity) ) return PL_type_error("callable", exe); PL_put_atom(arg, info->exe_name); #ifdef __WINDOWS__ if ( !PL_get_wchars(arg, NULL, &info->exe, CVT_ATOM|CVT_EXCEPTION|BUF_MALLOC) ) return FALSE; if ( !win_command_line(exe, arity, info->exe, &info->cmdline) ) return FALSE; #else /*__WINDOWS__*/ if ( !PL_get_chars(arg, &info->exe, CVT_ATOM|CVT_EXCEPTION|BUF_MALLOC|REP_FN) ) return FALSE; if ( !(info->argv = PL_malloc((arity+2)*sizeof(char*))) ) return PL_resource_error("memory"); memset(info->argv, 0, (arity+2)*sizeof(char*)); if ( !(info->argv[0] = PL_malloc(strlen(info->exe)+1)) ) return PL_resource_error("memory"); strcpy(info->argv[0], info->exe); { size_t i; for(i=1; i<=arity; i++) { _PL_get_arg(i, exe, arg); if ( !PL_get_chars(arg, &info->argv[i], CVT_ATOMIC|CVT_EXCEPTION|BUF_MALLOC|REP_FN) ) return FALSE; } info->argv[i] = NULL; } #endif /*__WINDOWS__*/ return TRUE; } static void free_options(p_options *info) /* TBD: close streams */ { if ( info->exe ) { PL_free(info->exe); info->exe = NULL; } if ( info->cwd ) { PL_free(info->cwd); info->cwd = NULL; } #ifndef __WINDOWS__ if ( info->envp ) { PL_free(info->envp); info->envp = NULL; } #endif free_ecbuf(&info->envbuf); #ifdef __WINDOWS__ if ( info->cmdline ) { PL_free(info->cmdline); info->cmdline = NULL; } #else /*__WINDOWS__*/ if ( info->argv ) { char **a; for(a=info->argv; *a; a++) { if ( *a ) PL_free(*a); } PL_free(info->argv); info->argv = NULL; } #endif /*__WINDOWS__*/ } /******************************* * PROCESS READS * *******************************/ #define PROCESS_MAGIC 0x29498001 typedef struct process_context { int magic; /* PROCESS_MAGIC */ #ifdef __WINDOWS__ HANDLE handle; /* process handle */ #else pid_t pid; /* the process id */ #endif int open_mask; /* Open streams */ int pipes[3]; /* stdin/stdout/stderr */ atom_t exe_name; /* exe as atom */ } process_context; static int wait_for_process(process_context *pc); static int process_fd(void *handle, process_context **PC) { process_context *pc = (process_context*) ((uintptr_t)handle&~(uintptr_t)0x3); int pipe = (int)(uintptr_t)handle & 0x3; if ( pc->magic == PROCESS_MAGIC ) { if ( PC ) *PC = pc; return pc->pipes[pipe]; } return -1; } static ssize_t Sread_process(void *handle, char *buf, size_t size) { int fd = process_fd(handle, NULL); return (*Sfilefunctions.read)((void*)(uintptr_t)fd, buf, size); } static ssize_t Swrite_process(void *handle, char *buf, size_t size) { int fd = process_fd(handle, NULL); return (*Sfilefunctions.write)((void*)(uintptr_t)fd, buf, size); } static int Sclose_process(void *handle) { process_context *pc; int fd = process_fd(handle, &pc); if ( fd >= 0 ) { int which = (int)(uintptr_t)handle & 0x3; int rc; rc = (*Sfilefunctions.close)((void*)(uintptr_t)fd); pc->open_mask &= ~(1<open_mask)); if ( !pc->open_mask ) { int rcw = wait_for_process(pc); return rcw ? 0 : -1; } return rc; } return -1; } static int Scontrol_process(void *handle, int action, void *arg) { process_context *pc; int fd = process_fd(handle, &pc); switch(action) { case SIO_GETFILENO: { int *fdp = arg; *fdp = fd; return 0; } default: return (*Sfilefunctions.control)((void*)(uintptr_t)fd, action, arg); } } static IOFUNCTIONS Sprocessfunctions = { Sread_process, Swrite_process, NULL, /* seek */ Sclose_process, Scontrol_process, NULL /* seek64 */ }; static IOSTREAM * open_process_pipe(process_context *pc, p_options *info, int which, int fdn) { void *handle; #ifdef __WINDOWS__ HANDLE fd = info->streams[which].fd[fdn]; #else int fd = info->streams[which].fd[fdn]; #endif int flags = SIO_RECORDPOS|SIO_FBUF; IOSTREAM *s; pc->open_mask |= (1<pipes[which] = _open_osfhandle((intptr_t)fd, _O_BINARY); #else pc->pipes[which] = fd; #endif if ( info->streams[which].encoding != ENC_OCTET ) flags |= SIO_TEXT; if ( which == 0 ) flags |= SIO_OUTPUT; else flags |= SIO_INPUT; handle = (void *)((uintptr_t)pc | (uintptr_t)which); if ( (s=Snew(handle, flags, &Sprocessfunctions)) ) s->encoding = info->streams[which].encoding; return s; } /******************************* * OS STUFF * *******************************/ #ifdef __WINDOWS__ CRITICAL_SECTION process_lock; #define LOCK() EnterCriticalSection(&process_lock); #define UNLOCK() LeaveCriticalSection(&process_lock); /* We need a root job to put non-detached processes into */ static HANDLE rootJob = (HANDLE)0; static IOFUNCTIONS Sprocessfilefunctions = { 0, }; static void win_init(void) { InitializeCriticalSection(&process_lock); JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli; rootJob = CreateJobObject(NULL, NULL); if (rootJob == NULL) /* Already in a job */ return; memset(&jeli, 0, sizeof(jeli)); jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; /* This will fail (silently) if we dont have permission to manage processes */ SetInformationJobObject(rootJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)); memcpy(&Sprocessfilefunctions, &Sfilefunctions, sizeof(IOFUNCTIONS)); Sprocessfilefunctions.seek = NULL; Sprocessfilefunctions.seek64 = NULL; } #include "win_error.c" typedef struct arg_string { size_t len; wchar_t *text; wchar_t quote; } arg_string; #define QMISC 0x1 #define QDBLQ 0x2 #define QSBLQ 0x4 static int set_quote(arg_string *as) { int needq = 0; const wchar_t *s = as->text; for(; *s; s++) { if ( !iswalnum(*s) ) { if ( *s == '"' ) needq |= QDBLQ; else if ( *s == '\'' ) needq |= QSBLQ; else needq |= QMISC; } } if ( !needq ) { as->quote = 0; return TRUE; } needq &= ~QMISC; switch( needq ) { case QDBLQ: as->quote = '\''; return TRUE; case 0: case QSBLQ: as->quote = '"'; return TRUE; default: return FALSE; } } static int win_command_line(term_t t, size_t arity, const wchar_t *exe, wchar_t **cline) { if ( arity > 0 ) { arg_string *av = PL_malloc((arity+1)*sizeof(*av)); term_t arg = PL_new_term_ref(); size_t cmdlen; wchar_t *cmdline, *o; const wchar_t *b; size_t i; if ( (b=wcsrchr(exe, '\\')) ) b++; else b = exe; av[0].text = (wchar_t*)b; av[0].len = wcslen(av[0].text); set_quote(&av[0]); cmdlen = av[0].len+(av[0].quote?2:0)+1; for( i=1; i<=arity; i++) { _PL_get_arg(i, t, arg); if ( !PL_get_wchars(arg, &av[i].len, &av[i].text, CVT_ATOMIC|CVT_EXCEPTION|BUF_MALLOC) ) return FALSE; if ( wcslen(av[i].text) != av[i].len ) return PL_domain_error("no_zero_code_atom", arg); if ( !set_quote(&av[i]) ) return PL_domain_error("dos_quotable_atom", arg); cmdlen += av[i].len+(av[i].quote?2:0)+1; } cmdline = PL_malloc(cmdlen*sizeof(wchar_t)); for( o=cmdline,i=0; i<=arity; ) { wchar_t *s = av[i].text; if ( av[i].quote ) *o++ = av[i].quote; wcsncpy(o, s, av[i].len); o += av[i].len; if ( i > 0 ) PL_free(s); /* do not free shared exename */ if ( av[i].quote ) *o++ = av[i].quote; if (++i <= arity) *o++ = ' '; } *o = 0; PL_free(av); *cline = cmdline; } else { *cline = NULL; } return TRUE; } typedef struct win_process { DWORD pid; HANDLE handle; HANDLE job; struct win_process *next; } win_process; static win_process *processes; static void register_process(DWORD pid, HANDLE h, HANDLE job) { win_process *wp = PL_malloc(sizeof(*wp)); wp->pid = pid; wp->handle = h; wp->job = job; LOCK(); wp->next = processes; processes = wp; UNLOCK(); } static int unregister_process(DWORD pid) { win_process **wpp, *wp; LOCK(); for(wpp=&processes, wp=*wpp; wp; wpp=&wp->next, wp=*wpp) { if ( wp->pid == pid ) { *wpp = wp->next; PL_free(wp); UNLOCK(); return TRUE; } } UNLOCK(); return FALSE; } static HANDLE find_process_from_pid(DWORD pid, const char *pred) { win_process *wp; LOCK(); for(wp=processes; wp; wp=wp->next) { if ( wp->pid == pid ) { HANDLE h = wp->handle; UNLOCK(); return h; } } UNLOCK(); if ( pred ) { term_t ex = PL_new_term_ref(); if ( PL_put_integer(ex, pid) ) PL_existence_error("process", ex); } return (HANDLE)0; } static HANDLE find_job_from_pid(DWORD pid, const char *pred) { win_process *wp; LOCK(); for(wp=processes; wp; wp=wp->next) { if ( wp->pid == pid ) { HANDLE h = wp->job; UNLOCK(); return h; } } UNLOCK(); if ( pred ) { term_t ex = PL_new_term_ref(); if ( PL_put_integer(ex, pid) ) PL_existence_error("process", ex); } return (HANDLE)0; } #define WP_TIMEOUT 2 static int wait_process_handle(HANDLE process, ULONG *rc, DWORD timeout) { DWORD wc; retry: wc = MsgWaitForMultipleObjects(1, &process, FALSE, /* return on any event */ timeout, QS_ALLINPUT); switch(wc) { case WAIT_OBJECT_0: if ( !GetExitCodeProcess(process, rc) ) { win_error("GetExitCodeProcess"); CloseHandle(process); return FALSE; } CloseHandle(process); return TRUE; case WAIT_OBJECT_0+1: { MSG msg; while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) { TranslateMessage(&msg); DispatchMessage(&msg); if ( PL_handle_signals() < 0 ) return FALSE; } goto retry; } case WAIT_TIMEOUT: return WP_TIMEOUT; default: win_error("WaitForSingleObject"); CloseHandle(process); return FALSE; } } static int wait_for_pid(pid_t pid, term_t code, wait_options *opts) { HANDLE *h; if ( (h=find_process_from_pid(pid, "process_wait")) ) { ULONG rc; DWORD timeout; int wc; if ( opts->has_timeout ) timeout = (DWORD)(opts->timeout * 1000.0); else timeout = INFINITE; if ( !(wc=wait_process_handle(h, &rc, timeout)) ) return FALSE; if ( wc == WP_TIMEOUT ) return PL_unify_atom(code, ATOM_timeout); unregister_process(pid); return PL_unify_term(code, PL_FUNCTOR, FUNCTOR_exit1, PL_LONG, rc); } else { return FALSE; } } static int win_wait_success(atom_t exe, HANDLE process) { ULONG rc; if ( !wait_process_handle(process, &rc, INFINITE) ) return FALSE; if ( rc != 0 ) { term_t ex = PL_new_term_ref(); if ( PL_unify_term(ex, PL_FUNCTOR, FUNCTOR_error2, PL_FUNCTOR, FUNCTOR_process_error2, PL_ATOM, exe, PL_FUNCTOR, FUNCTOR_exit1, PL_LONG, rc, PL_VARIABLE) ) return PL_raise_exception(ex); return FALSE; } return TRUE; } static int wait_for_process(process_context *pc) { int rc = win_wait_success(pc->exe_name, pc->handle); PL_unregister_atom(pc->exe_name); PL_free(pc); return rc; } static int create_pipes(p_options *info) { int i; SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); /* Length in bytes */ sa.bInheritHandle = 1; /* the child must inherit these handles */ sa.lpSecurityDescriptor = NULL; for(i=0; i<3; i++) { p_stream *s = &info->streams[i]; if ( s->term && s->type == std_pipe ) { if ( i == 2 && info->streams[1].term && PL_compare(info->streams[1].term, info->streams[2].term) == 0 ) { s->fd[0] = info->streams[1].fd[0]; s->fd[1] = info->streams[1].fd[1]; } else { if ( !CreatePipe(&s->fd[0], &s->fd[1], &sa, 1<<13) ) { return win_error("CreatePipe"); } } } } return TRUE; } static IOSTREAM * Sopen_handle(HANDLE h, const char *mode) { IOSTREAM *s = Sfdopen(_open_osfhandle((intptr_t)h, _O_BINARY), mode); if ( s->functions == &Sfilefunctions ) { s->functions = &Sprocessfilefunctions; } return s; } static HANDLE open_null_stream(DWORD access) { SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); /* Length in bytes */ sa.bInheritHandle = 1; /* the child must inherit these handles */ sa.lpSecurityDescriptor = NULL; return CreateFile("nul", access, FILE_SHARE_READ|FILE_SHARE_WRITE, &sa, /* security */ OPEN_EXISTING, 0, NULL); } static int console_app(void) { HANDLE h; if ( (h = GetStdHandle(STD_OUTPUT_HANDLE)) != INVALID_HANDLE_VALUE ) { DWORD mode; if ( GetConsoleMode(h, &mode) ) return TRUE; } return FALSE; } static int do_create_process(p_options *info) { int flags = 0; PROCESS_INFORMATION pi; STARTUPINFOW si; switch(info->window) { case MAYBE: if ( !console_app() ) flags |= CREATE_NO_WINDOW; break; case TRUE: break; case FALSE: flags |= CREATE_NO_WINDOW; break; } if ( info->detached ) flags |= CREATE_BREAKAWAY_FROM_JOB; if ( info->envbuf.buffer ) flags |= CREATE_UNICODE_ENVIRONMENT; memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; /* stdin */ switch( info->streams[0].type ) { case std_pipe: case std_stream: si.hStdInput = info->streams[0].fd[0]; SetHandleInformation(info->streams[0].fd[1], HANDLE_FLAG_INHERIT, FALSE); break; case std_null: si.hStdInput = open_null_stream(GENERIC_READ); break; case std_std: si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); break; } /* stdout */ switch( info->streams[1].type ) { case std_pipe: case std_stream: si.hStdOutput = info->streams[1].fd[1]; SetHandleInformation(info->streams[1].fd[0], HANDLE_FLAG_INHERIT, FALSE); break; case std_null: si.hStdOutput = open_null_stream(GENERIC_WRITE); break; case std_std: si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); break; } /* stderr */ switch( info->streams[2].type ) { case std_pipe: case std_stream: si.hStdError = info->streams[2].fd[1]; SetHandleInformation(info->streams[2].fd[0], HANDLE_FLAG_INHERIT, FALSE); break; case std_null: si.hStdError = open_null_stream(GENERIC_WRITE); break; case std_std: si.hStdError = GetStdHandle(STD_ERROR_HANDLE); break; } if ( CreateProcessW(info->exe, info->cmdline, NULL, /* Process security */ NULL, /* Thread security */ TRUE, /* Inherit handles */ flags, /* Creation flags */ info->envbuf.buffer, /* Environment */ info->cwd, /* Directory */ &si, /* Startup info */ &pi) ) /* Process information */ { int rc = TRUE; HANDLE hJob = (HANDLE)0; if ( !info->detached ) { hJob = rootJob; } CloseHandle(pi.hThread); if ( info->pipes > 0 && info->pid == 0 ) { IOSTREAM *s; process_context *pc = PL_malloc(sizeof(*pc)); DEBUG(Sdprintf("Wait on pipes\n")); memset(pc, 0, sizeof(*pc)); pc->magic = PROCESS_MAGIC; pc->handle = pi.hProcess; pc->exe_name = info->exe_name; PL_register_atom(pc->exe_name); if ( info->streams[0].type == std_pipe ) { CloseHandle(info->streams[0].fd[0]); if ( (s = open_process_pipe(pc, info, 0, 1)) ) rc = PL_unify_stream(info->streams[0].term, s); else CloseHandle(info->streams[0].fd[1]); } if ( info->streams[1].type == std_pipe ) { CloseHandle(info->streams[1].fd[1]); if ( rc && (s = open_process_pipe(pc, info, 1, 0)) ) PL_unify_stream(info->streams[1].term, s); else CloseHandle(info->streams[1].fd[0]); } if ( info->streams[2].type == std_pipe && ( !info->streams[1].term || PL_compare(info->streams[1].term, info->streams[2].term) != 0 ) ) { CloseHandle(info->streams[2].fd[1]); if ( rc && (s = open_process_pipe(pc, info, 2, 0)) ) rc = PL_unify_stream(info->streams[2].term, s); else CloseHandle(info->streams[2].fd[0]); } return rc; } else if ( info->pipes > 0 ) { IOSTREAM *s; if ( info->streams[0].type == std_pipe ) { CloseHandle(info->streams[0].fd[0]); if ( (s = Sopen_handle(info->streams[0].fd[1], "w")) ) rc = PL_unify_stream(info->streams[0].term, s); else CloseHandle(info->streams[0].fd[1]); } if ( info->streams[1].type == std_pipe ) { CloseHandle(info->streams[1].fd[1]); if ( rc && (s = Sopen_handle(info->streams[1].fd[0], "r")) ) rc = PL_unify_stream(info->streams[1].term, s); else CloseHandle(info->streams[1].fd[0]); } if ( info->streams[2].type == std_pipe && ( !info->streams[1].term || PL_compare(info->streams[1].term, info->streams[2].term) != 0 ) ) { CloseHandle(info->streams[2].fd[1]); if ( rc && (s = Sopen_handle(info->streams[2].fd[0], "r")) ) rc = PL_unify_stream(info->streams[2].term, s); else CloseHandle(info->streams[2].fd[0]); } } if ( !rc ) { Sdprintf("FATAL ERROR: create_process/3\n"); PL_halt(1); } if ( info->detached ) { hJob = CreateJobObject(NULL, NULL); AssignProcessToJobObject(hJob, pi.hProcess); } else { AssignProcessToJobObject(rootJob, pi.hProcess); } if ( info->pid ) { register_process(pi.dwProcessId, pi.hProcess, hJob); return PL_unify_integer(info->pid, pi.dwProcessId); } return win_wait_success(info->exe_name, pi.hProcess); } else { return win_error("CreateProcess"); } } #else /*__WINDOWS__*/ #ifndef HAVE_PRCTL #ifdef _REENTRANT static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; #define LOCK() pthread_mutex_lock(&mutex) #define UNLOCK() pthread_mutex_unlock(&mutex) #else #define LOCK() {} #define UNLOCK() {} #endif typedef struct posix_process { pid_t pid; struct posix_process *next; } posix_process; static posix_process *processes = NULL; static void register_process(pid_t pid) { posix_process *pp = PL_malloc(sizeof(*pp)); pp->pid = pid; LOCK(); pp->next = processes; processes = pp; UNLOCK(); } static int unregister_process(pid_t pid) { posix_process **ppp, *pp; LOCK(); for(ppp=&processes, pp=*ppp; pp; ppp=&pp->next, pp=*ppp) { if ( pp->pid == pid ) { *ppp = pp->next; PL_free(pp); UNLOCK(); return TRUE; } } UNLOCK(); return FALSE; } static int kill_all_processes(int rc, void* arg) { posix_process* pp; for(pp=processes; pp; pp=pp->next) kill(pp->pid, SIGTERM); return 0; } static void posix_init() { PL_exit_hook(kill_all_processes, NULL); } #endif /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Note the descriptors created using pipe() are inherited by the child. This implies that if two threads call process_create/3 using pipes, the pipe created for one thread may also end up in the child created by another thread. We can avoid this using FD_CLOEXEC on the pipe's descriptor. Note that we can do that on both because the flag will be cleared on the duplicated descriptor after dup2 (which is executed before the exec, so the descriptors are still valid). This can be implemented safely on systems that have pipe2(). On other systems, safety can be enhanced by reducing the window using fcntl(), but this needs to be synced with fork() in other threads. (*) It seems that some systems implement the function, but the function returns ENOSYS to indicate it is not implemented. Hence, we need to go for a runtime solution. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static int create_pipes(p_options *info) { int i; for(i=0; i<3; i++) { p_stream *s = &info->streams[i]; if ( s->term && s->type == std_pipe ) { if ( i == 2 && info->streams[1].term && PL_compare(info->streams[1].term, info->streams[2].term) == 0 ) { s->fd[0] = info->streams[1].fd[0]; s->fd[1] = info->streams[1].fd[1]; } else { int my_side; #ifdef HAVE_PIPE2 if ( pipe2(s->fd, O_CLOEXEC) == 0 ) { s->cloexec = TRUE; continue; } else if ( errno != ENOSYS ) /* See (*) */ { if ( errno != EMFILE ) Sdprintf("pipe2(): unexpected error: %s\n", strerror(errno)); return PL_resource_error("open_files"); } #endif if ( pipe(s->fd) ) { if ( errno != EMFILE ) Sdprintf("pipe(): unexpected error: %s\n", strerror(errno)); return PL_resource_error("open_files"); } my_side = (i == 0 ? s->fd[1] : s->fd[0]); #ifdef F_SETFD if ( fcntl(my_side, F_SETFD, FD_CLOEXEC) == 0 ) s->cloexec = TRUE; #endif } } else if ( s->term && s->type == std_stream ) { if ( fcntl(s->fd[0], F_SETFD, FD_CLOEXEC) == 0 ) s->cloexec = TRUE; } } return TRUE; } static int unify_exit_status(term_t code, int status) { if ( WIFEXITED(status) ) { return PL_unify_term(code, PL_FUNCTOR, FUNCTOR_exit1, PL_INT, (int)WEXITSTATUS(status)); } else if ( WIFSIGNALED(status) ) { return PL_unify_term(code, PL_FUNCTOR, FUNCTOR_killed1, PL_INT, (int)WTERMSIG(status)); } else { assert(0); return FALSE; } } static int wait_for_pid(pid_t pid, term_t code, wait_options *opts) { pid_t p2; int status; if ( opts->has_timeout && opts->timeout == 0.0 ) { if ( (p2=waitpid(pid, &status, WNOHANG)) == pid ) { #ifndef HAVE_PRCTL unregister_process(pid); #endif return unify_exit_status(code, status); } else if ( p2 == 0 ) return PL_unify_atom(code, ATOM_timeout); else { term_t PID; error: return ((PID = PL_new_term_ref()) && PL_put_integer(PID, pid) && pl_error(NULL, 0, "waitpid", ERR_ERRNO, errno, "wait", "process", PID)); } } for(;;) { if ( (p2=waitpid(pid, &status, 0)) == pid ) return unify_exit_status(code, status); if ( p2 == -1 && errno == EINTR ) { if ( PL_handle_signals() < 0 ) return FALSE; } else { goto error; } } } static int wait_success(atom_t name, pid_t pid) { pid_t p2; for(;;) { int status; if ( (p2=waitpid(pid, &status, 0)) == pid ) { if ( WIFEXITED(status) && WEXITSTATUS(status) == 0 ) { #ifndef HAVE_PRCTL unregister_process(pid); #endif return TRUE; } else { term_t code, ex; if ( (code = PL_new_term_ref()) && (ex = PL_new_term_ref()) && unify_exit_status(code, status) && PL_unify_term(ex, PL_FUNCTOR, FUNCTOR_error2, PL_FUNCTOR, FUNCTOR_process_error2, PL_ATOM, name, PL_TERM, code, PL_VARIABLE) ) return PL_raise_exception(ex); return FALSE; } } if ( p2 == -1 && errno == EINTR ) { if ( PL_handle_signals() < 0 ) return FALSE; } } } static int wait_for_process(process_context *pc) { int rc = wait_success(pc->exe_name, pc->pid); PL_unregister_atom(pc->exe_name); PL_free(pc); return rc; } static int close_ok(int fd) { int rc; do { rc = close(fd); } while ( rc == -1 && errno == EINTR ); DEBUG(if ( rc == -1 ) perror("close")); return rc; } static IOSTREAM * p_fdopen(p_options *info, int which, int fdn, char *mode) { IOSTREAM *s; char m[10]; char *mp = m; *mp++ = mode[0]; if ( info->streams[which].encoding == ENC_OCTET ) *mp++ = 'b'; *mp = 0; if ( (s=Sfdopen(info->streams[which].fd[fdn], m)) ) s->encoding = info->streams[which].encoding; return s; } static int process_parent_side(p_options *info, int pid) { int rc = TRUE; if ( info->pipes > 0 && info->pid == 0 ) { IOSTREAM *s; /* no pid(Pid): wait */ process_context *pc = PL_malloc(sizeof(*pc)); DEBUG(Sdprintf("Wait on pipes\n")); memset(pc, 0, sizeof(*pc)); pc->magic = PROCESS_MAGIC; pc->pid = pid; pc->exe_name = info->exe_name; PL_register_atom(pc->exe_name); if ( info->streams[0].type == std_pipe ) { close_ok(info->streams[0].fd[0]); if ( (s = open_process_pipe(pc, info, 0, 1)) ) rc = PL_unify_stream(info->streams[0].term, s); else close_ok(info->streams[0].fd[1]); } if ( info->streams[1].type == std_pipe ) { close_ok(info->streams[1].fd[1]); if ( rc && (s = open_process_pipe(pc, info, 1, 0)) ) rc = PL_unify_stream(info->streams[1].term, s); else close_ok(info->streams[1].fd[0]); } if ( info->streams[2].type == std_pipe && ( !info->streams[1].term || PL_compare(info->streams[1].term, info->streams[2].term) != 0 ) ) { close_ok(info->streams[2].fd[1]); if ( rc && (s = open_process_pipe(pc, info, 2, 0)) ) rc = PL_unify_stream(info->streams[2].term, s); else close_ok(info->streams[2].fd[0]); } return rc; } else if ( info->pipes > 0 ) { IOSTREAM *s; if ( info->streams[0].type == std_pipe ) { close_ok(info->streams[0].fd[0]); if ( (s = p_fdopen(info, 0, 1, "w")) ) rc = PL_unify_stream(info->streams[0].term, s); else close_ok(info->streams[0].fd[1]); } if ( info->streams[1].type == std_pipe ) { close_ok(info->streams[1].fd[1]); if ( rc && (s = p_fdopen(info, 1, 0, "r")) ) rc = PL_unify_stream(info->streams[1].term, s); else close_ok(info->streams[1].fd[0]); } if ( info->streams[2].type == std_pipe && ( !info->streams[1].term || PL_compare(info->streams[1].term, info->streams[2].term) != 0 ) ) { close_ok(info->streams[2].fd[1]); if ( rc && (s = p_fdopen(info, 2, 0, "r")) ) PL_unify_stream(info->streams[2].term, s); else close_ok(info->streams[2].fd[0]); } } assert(rc); /* What else? */ #ifndef HAVE_PRCTL /* Then we must do this manually */ if ( !info->detached ) register_process(pid); #endif if ( info->pid ) return PL_unify_integer(info->pid, pid); return wait_success(info->exe_name, pid); } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - If profiling is used, fork() may not work succeed as it is interrupted before completion. This problem was diagnosed by Edward Schwartz. It seems to affect at least Ubuntu 18.04, but not later versions. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #if defined(SIGPROF) && defined(HAVE_SIGPROCMASK) static void blockSignal(int sig, sigset_t *old) { sigset_t set; sigemptyset(&set); sigaddset(&set, sig); sigprocmask(SIG_BLOCK, &set, old); } static void restoreSignals(sigset_t *old) { sigprocmask(SIG_SETMASK, old, NULL); } #else #define blockSignal(sig, old) (void)set #define restoreSignals(set) (void)0 #endif #ifndef HAVE_VFORK #define vfork fork #endif static int do_create_process_fork(p_options *info, create_method method) { int pid; sigset_t set; blockSignal(SIGPROF, &set); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" if ( method == PCREATE_VFORK ) pid = vfork(); /* As long as it is there we use it */ else pid = fork(); if ( pid != 0 ) /* parent */ restoreSignals(&set); #pragma GCC diagnostic pop if ( pid == 0 ) /* child */ { int fd; PL_cleanup_fork(); #if defined(HAVE_SYS_RESOURCE_H) && defined(PRIO_PROCESS) if ( info->priority != 255 ) setpriority(PRIO_PROCESS, pid, info->priority); #endif if ( info->detached ) { setsid(); #ifdef HAVE_SETPGID setpgid(pid, pid); #elif defined(HAVE_SETPGRP) #ifdef SETPGRP_VOID setpgrp(); #else setpgrp(pid, pid); #endif #endif } else { #ifdef HAVE_PRCTL prctl(PR_SET_PDEATHSIG, SIGTERM); #endif } if ( info->cwd ) { if ( chdir(info->cwd) ) { perror(info->cwd); exit(1); } } /* stdin */ switch( info->streams[0].type ) { case std_pipe: case std_stream: dup2(info->streams[0].fd[0], 0); if ( !info->streams[0].cloexec ) close(info->streams[0].fd[1]); break; case std_null: if ( (fd = open("/dev/null", O_RDONLY)) >= 0 ) dup2(fd, 0); break; case std_std: { int fd = Sfileno(Suser_input); if ( fd > 0 ) dup2(fd, 0); break; } } /* stdout */ switch( info->streams[1].type ) { case std_pipe: case std_stream: dup2(info->streams[1].fd[1], 1); if ( !info->streams[1].cloexec ) close(info->streams[1].fd[0]); break; case std_null: if ( (fd = open("/dev/null", O_WRONLY)) >= 0 ) dup2(fd, 1); break; case std_std: { int fd = Sfileno(Suser_output); if ( fd >= 0 && fd != 1 ) dup2(fd, 1); break; } } /* stderr */ switch( info->streams[2].type ) { case std_pipe: case std_stream: dup2(info->streams[2].fd[1], 2); if ( !info->streams[2].cloexec ) close(info->streams[2].fd[0]); break; case std_null: if ( (fd = open("/dev/null", O_WRONLY)) >= 0 ) dup2(fd, 2); break; case std_std: { int fd = Sfileno(Suser_error); if ( fd >= 0 && fd != 2 ) dup2(fd, 2); break; } } if ( info->envp ) { execve(info->exe, info->argv, info->envp); } else { #ifdef HAVE__NSGETENVIRON char **environ = *_NSGetEnviron(); #else extern char **environ; #endif execve(info->exe, info->argv, environ); } perror(info->exe); exit(1); } else if ( pid < 0 ) /* parent */ { term_t exe = PL_new_term_ref(); PL_put_atom_chars(exe, info->exe); return pl_error(NULL, 0, "fork", ERR_ERRNO, errno, "fork", "process", exe); } else { return process_parent_side(info, pid); } } #ifdef HAVE_POSIX_SPAWN #include static int do_create_process(p_options *info) { pid_t pid; posix_spawn_file_actions_t file_actions; posix_spawnattr_t attr; int rc; if ( info->cwd || create_process_method != PCREATE_SPAWN ) return do_create_process_fork(info, create_process_method); posix_spawn_file_actions_init(&file_actions); posix_spawnattr_init(&attr); if ( info->detached ) posix_spawnattr_setpgroup(&attr, 0); switch( info->streams[0].type ) /* stdin */ { case std_pipe: case std_stream: posix_spawn_file_actions_adddup2(&file_actions, info->streams[0].fd[0], 0); if ( !info->streams[0].cloexec ) posix_spawn_file_actions_addclose(&file_actions, info->streams[0].fd[1]); break; case std_null: posix_spawn_file_actions_addopen(&file_actions, 0, "/dev/null", O_RDONLY, 0); break; case std_std: break; } /* stdout */ switch( info->streams[1].type ) { case std_pipe: case std_stream: posix_spawn_file_actions_adddup2(&file_actions, info->streams[1].fd[1], 1); if ( !info->streams[1].cloexec ) posix_spawn_file_actions_addclose(&file_actions, info->streams[1].fd[0]); break; case std_null: posix_spawn_file_actions_addopen(&file_actions, 1, "/dev/null", O_WRONLY, 0); break; case std_std: break; } /* stderr */ switch( info->streams[2].type ) { case std_pipe: case std_stream: posix_spawn_file_actions_adddup2(&file_actions, info->streams[2].fd[1], 2); if ( !info->streams[2].cloexec ) posix_spawn_file_actions_addclose(&file_actions, info->streams[2].fd[0]); break; case std_null: posix_spawn_file_actions_addopen(&file_actions, 2, "/dev/null", O_WRONLY, 0); break; case std_std: break; } rc = posix_spawn(&pid, info->exe, &file_actions, &attr, info->argv, info->envp); posix_spawn_file_actions_destroy(&file_actions); posix_spawnattr_destroy(&attr); if ( rc == 0 ) { return process_parent_side(info, pid); } else { term_t exe = PL_new_term_ref(); PL_put_atom_chars(exe, info->exe); return pl_error(NULL, 0, "spawn", ERR_ERRNO, errno, "fork", "process", exe); } } #else /*HAVE_POSIX_SPAWN*/ static int do_create_process(p_options *info) { return do_create_process_fork(info, create_process_method); } #endif /*HAVE_POSIX_SPAWN*/ #endif /*__WINDOWS__*/ /******************************* * BINDING * *******************************/ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Running out of resources after we created the process is really hard to handle gracefully, so we first make a list to ensure we have enough resources to bind the return value. Ideally, we need a call to ask for sufficient resources. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static int ensure_stack_resources(int count) { fid_t fid = PL_open_foreign_frame(); term_t list = PL_new_term_ref(); term_t tail = PL_copy_term_ref(list); while ( count-- > 0 ) { term_t head; if ( !(head = PL_new_term_ref()) || !PL_unify_list(tail, head, tail) ) { PL_close_foreign_frame(fid); return FALSE; } } PL_discard_foreign_frame(fid); return TRUE; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Basic process creation interface takes * Exe file * List of arguments * standard streams % std, null, pipe(S) * Working directory * detached * window % Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static foreign_t process_create(term_t exe, term_t options) { p_options info; int rc = FALSE; if ( !ensure_stack_resources(10) ) /* max 3 stream structures */ return FALSE; memset(&info, 0, sizeof(info)); info.priority = 255; /* zero is a valid priority */ if ( !get_exe(exe, &info) ) goto out; if ( !parse_options(options, &info) ) goto out; if ( !create_pipes(&info) ) goto out; rc = do_create_process(&info); out: free_options(&info); return rc; } static int get_pid(term_t pid, pid_t *p) { int n; if ( !PL_get_integer_ex(pid, &n) ) return FALSE; if ( n < 0 ) return PL_domain_error("not_less_than_zero", pid),FALSE; *p = n; return TRUE; } static foreign_t process_wait(term_t pid, term_t code, term_t options) { pid_t p; wait_options opts; term_t tail = PL_copy_term_ref(options); term_t head = PL_new_term_ref(); term_t arg = PL_new_term_ref(); if ( !get_pid(pid, &p) ) return FALSE; memset(&opts, 0, sizeof(opts)); while(PL_get_list(tail, head, tail)) { atom_t name; size_t arity; if ( !PL_get_name_arity(head, &name, &arity) || arity != 1 ) return PL_type_error("option", head); _PL_get_arg(1, head, arg); if ( name == ATOM_timeout ) { atom_t a; if ( !(PL_get_atom(arg, &a) && a == ATOM_infinite) ) { if ( !PL_get_float(arg, &opts.timeout) ) return PL_type_error("timeout", arg); opts.has_timeout = TRUE; } } else if ( name == ATOM_release ) { if ( !PL_get_bool_ex(arg, &opts.release) ) return FALSE; if ( opts.release == FALSE ) return PL_domain_error("true", arg); } else return PL_domain_error("process_wait_option", head); } if ( !PL_get_nil_ex(tail) ) return FALSE; return wait_for_pid(p, code, &opts); } #ifndef __WINDOWS__ static foreign_t process_kill_posix(term_t pid_term, pid_t pid, int sig) { if ( kill(pid, sig) == 0 ) return TRUE; switch(errno) { case EPERM: return pl_error("process_kill", 2, NULL, ERR_PERMISSION, pid_term, "kill", "process"); case ESRCH: return pl_error("process_kill", 2, NULL, ERR_EXISTENCE, "process", pid_term); default: return pl_error("process_kill", 2, "kill", ERR_ERRNO, errno, "kill", "process", pid_term); } } #endif static foreign_t process_kill(term_t pid, term_t signal) { pid_t p; if ( !get_pid(pid, &p) ) return FALSE; { #ifdef __WINDOWS__ HANDLE h; if ( !(h=find_process_from_pid(p, "process_kill")) ) return FALSE; if ( TerminateProcess(h, 255) ) return TRUE; return win_error("TerminateProcess"); #else /*__WINDOWS__*/ int sig; if ( !PL_get_signum_ex(signal, &sig) ) return FALSE; return process_kill_posix(pid, p, sig); #endif /*__WINDOWS__*/ } } static foreign_t process_group_kill(term_t pid, term_t signal) { pid_t p; if ( !get_pid(pid, &p) ) return FALSE; { #ifdef __WINDOWS__ HANDLE hJob; hJob = find_job_from_pid(p, "process_group_kill"); TerminateJobObject(hJob, 255); /* For consistency, if you try to kill the process group of the root process then exit. Windows does not do this for us. */ if (hJob == rootJob) exit(255); return TRUE; #else /*__WINDOWS__*/ int sig; if ( !PL_get_signum_ex(signal, &sig) ) return FALSE; return process_kill_posix(pid, -p, sig); #endif /*__WINDOWS__*/ } } static foreign_t process_set_method(term_t how) { char *s; if ( PL_get_chars(how, &s, CVT_ATOM|CVT_EXCEPTION) ) { if ( strcmp(s, "fork") == 0 ) create_process_method = PCREATE_FORK; else if ( strcmp(s, "vfork") == 0 ) create_process_method = PCREATE_VFORK; else if ( strcmp(s, "spawn") == 0 ) create_process_method = PCREATE_SPAWN; else return PL_domain_error("process_create_method", how); return TRUE; } return FALSE; } #define MKATOM(n) ATOM_ ## n = PL_new_atom(#n) #define MKFUNCTOR(n,a) FUNCTOR_ ## n ## a = PL_new_functor(PL_new_atom(#n), a) install_t install_process() { #ifdef __WINDOWS__ win_init(); #elif !defined(HAVE_PRCTL) posix_init(); #endif MKATOM(stdin); MKATOM(stdout); MKATOM(stderr); MKATOM(std); MKATOM(null); MKATOM(process); MKATOM(detached); MKATOM(cwd); MKATOM(env); MKATOM(environment); MKATOM(priority); MKATOM(window); MKATOM(timeout); MKATOM(release); MKATOM(infinite); MKATOM(text); MKATOM(binary); MKATOM(octet); MKATOM(utf8); MKATOM(ascii); MKATOM(iso_latin_1); MKATOM(unicode_be); MKATOM(unicode_le); MKFUNCTOR(pipe, 1); MKFUNCTOR(pipe, 2); MKFUNCTOR(type, 1); MKFUNCTOR(encoding, 1); MKFUNCTOR(stream, 1); MKFUNCTOR(error, 2); MKFUNCTOR(process_error, 2); MKFUNCTOR(system_error, 2); MKFUNCTOR(exit, 1); MKFUNCTOR(killed, 1); FUNCTOR_eq2 = PL_new_functor(PL_new_atom("="), 2); PL_register_foreign("process_create", 2, process_create, 0); PL_register_foreign("process_wait", 3, process_wait, 0); PL_register_foreign("process_kill", 2, process_kill, 0); PL_register_foreign("process_group_kill", 2, process_group_kill, 0); PL_register_foreign("process_set_method", 1, process_set_method, 0); }