From 1fd4fb6a645fa107d644f1ca0a0a8d7aa702e1d5 Mon Sep 17 00:00:00 2001 From: Steve Bennett Date: Wed, 14 Sep 2016 14:15:47 +1000 Subject: signal, exec, wait, pid: improvements, especially to exec - fix popen [open "|pipeline ..."] to return meaningful status in close (but note that stderr is not captured) - popen pipelines can now be used as the target of exec redirection - overally improvements to exec on windows. Now crt file descriptors are used throughout - add support for [pid], [wait] and popen on windows - os.wait is now wait, and integrates with [exec ... &] to be able to wait for running background tasks - [socket pipe] is now also [pipe] and is supported on windows - [file tempfile] is supported on windows - move duplicated code between jim-aio.c and jim-exec.c to jimiocompat.c - Fix [exec] on windows to match unix semantics wrt sharing the parent stream unless redirected rather than using /dev/null - On windows redirect to or from /dev/null is automatically converted to NUL: - If signal support is disabled, implement a minimal Jim_SignalId() for exec and wait - aio now supports getfd, to return the underlying file descriptor. This is used by exec to support redirection, and allows popen channels to support exec redirection. Signed-off-by: Steve Bennett --- Makefile.in | 2 +- auto.def | 7 +- jim-aio.c | 170 ++++++----- jim-exec.c | 839 ++++++++++++++++++++--------------------------------- jim-file.c | 2 +- jim-nosignal.c | 28 ++ jim-posix.c | 96 ------ jim-signal.c | 10 - jim-signal.h | 8 - jim-win32compat.h | 5 - jim.h | 2 +- jim_tcl.txt | 61 ++-- jimiocompat.c | 214 ++++++++++++++ jimiocompat.h | 80 +++++ make-bootstrap-jim | 4 +- tclcompat.tcl | 21 +- tests/exec.test | 32 +- tests/exec2.test | 26 ++ tests/pid.test | 6 +- 19 files changed, 845 insertions(+), 768 deletions(-) create mode 100644 jim-nosignal.c create mode 100644 jimiocompat.c create mode 100644 jimiocompat.h diff --git a/Makefile.in b/Makefile.in index dfbdc4d..be663ab 100644 --- a/Makefile.in +++ b/Makefile.in @@ -51,7 +51,7 @@ JIMSH_CC := $(CXX) $(CXXFLAGS) JIMSH_CC := $(CC) $(CFLAGS) @endif -OBJS := _load-static-exts.o jim-subcmd.o jim-interactive.o jim-format.o jim.o utf8.o jimregexp.o \ +OBJS := _load-static-exts.o jim-subcmd.o jim-interactive.o jim-format.o jim.o utf8.o jimregexp.o jimiocompat.o \ @EXTRA_OBJS@ @C_EXT_OBJS@ @TCL_EXT_OBJS@ JIMSH := jimsh@EXEEXT@ diff --git a/auto.def b/auto.def index 4b454be..d1b26f9 100644 --- a/auto.def +++ b/auto.def @@ -354,7 +354,7 @@ dict set extdb info { sdl { pkg-config SDL_gfx check {[cc-check-function-in-lib SDL_SetVideoMode SDL] && [cc-check-function-in-lib rectangleRGBA SDL_gfx]} libdep {lib_SDL_SetVideoMode lib_rectangleRGBA} } - signal { check {[have-feature sigaction] && [have-feature vfork]} } + signal { check {[have-feature sigaction]} } sqlite3 { pkg-config sqlite3 check {[cc-check-function-in-lib sqlite3_prepare_v2 sqlite3]} libdep lib_sqlite3_prepare_v2 } zlib { pkg-config zlib check {[cc-check-function-in-lib deflate z]} libdep lib_deflate } syslog { check {[have-feature syslog]} } @@ -434,6 +434,11 @@ if {$jimregexp || [opt-bool jim-regexp]} { } } +# poor-man's signals +if {"signal" ni $extinfo(static-c)} { + lappend extra_objs jim-nosignal.o +} + if {[ext-get-status load] eq "n"} { # If we don't have load, no need to support shared objects define SH_LINKFLAGS "" diff --git a/jim-aio.c b/jim-aio.c index d0124c6..e72cce5 100644 --- a/jim-aio.c +++ b/jim-aio.c @@ -52,6 +52,7 @@ #endif #include "jim.h" +#include "jimiocompat.h" #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H) #include @@ -62,6 +63,9 @@ #ifdef HAVE_SYS_UN_H #include #endif +#define HAVE_SOCKETS +#elif defined (__MINGW32__) +/* currently mingw32 doesn't support sockets, but has pipe, fdopen */ #else #define JIM_ANSIC #endif @@ -99,9 +103,15 @@ #endif #endif +#ifdef JIM_ANSIC +/* no fdopen() with ANSIC, so can't support these */ +#undef HAVE_PIPE +#undef HAVE_SOCKETPAIR +#endif + #define JimCheckStreamError(interp, af) af->fops->error(af) -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) +#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP) union sockaddr_any { struct sockaddr sa; struct sockaddr_in sin; @@ -284,7 +294,7 @@ static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv); static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename, const char *hdlfmt, int family, const char *mode); -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) +#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP) static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen) { #if IPV6 @@ -591,6 +601,16 @@ FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command) return af->fp; } +static int aio_cmd_getfd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + fflush(af->fp); + Jim_SetResultInt(interp, fileno(af->fp)); + + return JIM_OK; +} + static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { AioFile *af = Jim_CmdPrivData(interp); @@ -726,7 +746,7 @@ static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv) return JIM_OK; } -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) +#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP) static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { AioFile *af = Jim_CmdPrivData(interp); @@ -853,7 +873,7 @@ static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv) static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { if (argc == 3) { -#if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN) +#if defined(HAVE_SOCKETS) && defined(HAVE_SHUTDOWN) static const char * const options[] = { "r", "w", NULL }; enum { OPT_R, OPT_W, }; int option; @@ -944,7 +964,7 @@ static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv) } #endif -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) +#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP) #define SOCKOPT_BOOL 0 #define SOCKOPT_INT 1 #define SOCKOPT_TIMEVAL 2 /* not currently supported */ @@ -1043,7 +1063,7 @@ static int aio_cmd_sockopt(Jim_Interp *interp, int argc, Jim_Obj *const *argv) Jim_SetResultFormatted(interp, "Unknown sockopt %#s", argv[0]); return JIM_ERR; } -#endif +#endif /* JIM_BOOTSTRAP */ #ifdef HAVE_FSYNC static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv) @@ -1374,6 +1394,13 @@ static const jim_subcmd_type aio_command_table[] = { 2, /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */ }, + { "getfd", + NULL, + aio_cmd_getfd, + 0, + 0, + /* Description: Internal command to return the underlying file descriptor. */ + }, { "gets", "?var?", aio_cmd_gets, @@ -1395,7 +1422,7 @@ static const jim_subcmd_type aio_command_table[] = { 0, /* Description: Is the file descriptor a tty? */ }, -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) +#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP) { "recvfrom", "len ?addrvar?", aio_cmd_recvfrom, @@ -1424,6 +1451,13 @@ static const jim_subcmd_type aio_command_table[] = { 1, /* Description: Set the listen backlog for server socket */ }, + { "sockopt", + "?opt 0|1?", + aio_cmd_sockopt, + 0, + 2, + /* Description: Return a dictionary of sockopts, or set the value of a sockopt */ + }, #endif /* JIM_BOOTSTRAP */ { "flush", NULL, @@ -1477,15 +1511,6 @@ static const jim_subcmd_type aio_command_table[] = { /* Description: Set O_NDELAY (if arg). Returns current/new setting. */ }, #endif -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) - { "sockopt", - "?opt 0|1?", - aio_cmd_sockopt, - 0, - 2, - /* Description: Return a dictionary of sockopts, or set the value of a sockopt */ - }, -#endif #ifdef HAVE_FSYNC { "sync", NULL, @@ -1525,7 +1550,8 @@ static const jim_subcmd_type aio_command_table[] = { /* Description: Returns script, or invoke exception-script when oob data, {} to remove */ }, #endif -#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP) +#if !defined(JIM_BOOTSTRAP) +#if defined(JIM_SSL) { "ssl", "?-server cert priv?", aio_cmd_ssl, @@ -1541,8 +1567,8 @@ static const jim_subcmd_type aio_command_table[] = { 0, /* Description: Verifies the certificate of a SSL/TLS channel */ }, -#endif /* JIM_BOOTSTRAP */ -#if defined(HAVE_STRUCT_FLOCK) && !defined(JIM_BOOTSTRAP) +#endif +#if defined(HAVE_STRUCT_FLOCK) { "lock", NULL, aio_cmd_lock, @@ -1557,8 +1583,8 @@ static const jim_subcmd_type aio_command_table[] = { 0, /* Description: Relase a lock. */ }, -#endif /* JIM_BOOTSTRAP */ -#if defined(HAVE_TERMIOS_H) && !defined(JIM_BOOTSTRAP) +#endif +#if defined(HAVE_TERMIOS_H) { "tty", "?baud rate? ?data bits? ?stop bits? ?parity even|odd|none? ?handshake xonxoff|rtscts|none? ?input raw|cooked? ?output raw|cooked? ?vmin n? ?vtime n?", aio_cmd_tty, @@ -1566,6 +1592,7 @@ static const jim_subcmd_type aio_command_table[] = { -1, /* Description: Get or set tty settings - valid only on a tty */ }, +#endif #endif /* JIM_BOOTSTRAP */ { NULL } }; @@ -1665,17 +1692,17 @@ static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *fi Jim_IncrRefCount(filename); if (fh == NULL) { -#if !defined(JIM_ANSIC) if (fd >= 0) { +#ifndef JIM_ANSIC fh = fdopen(fd, mode); +#endif } else -#endif fh = fopen(Jim_String(filename), mode); if (fh == NULL) { JimAioSetError(interp, filename); -#if !defined(JIM_ANSIC) +#ifndef JIM_ANSIC if (fd >= 0) { close(fd); } @@ -1689,7 +1716,9 @@ static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *fi af = Jim_Alloc(sizeof(*af)); memset(af, 0, sizeof(*af)); af->fp = fh; +#ifndef JIM_ANSIC af->fd = fileno(fh); +#endif af->filename = filename; #ifdef FD_CLOEXEC if ((openFlags & AIO_KEEPOPEN) == 0) { @@ -1721,7 +1750,6 @@ static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename, if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0])) { Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0); Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp)); - if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1])) { Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp)); Jim_SetResult(interp, objPtr); @@ -1737,7 +1765,27 @@ static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename, } #endif -#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP) +#ifdef HAVE_PIPE +static int JimAioPipeCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int p[2]; + static const char *mode[2] = { "r", "w" }; + + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, ""); + return JIM_ERR; + } + + if (pipe(p) != 0) { + JimAioSetError(interp, NULL); + return JIM_ERR; + } + + return JimMakeChannelPair(interp, p, argv[0], "aio.pipe%ld", 0, mode); +} +#endif + +#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP) static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { @@ -1979,22 +2027,10 @@ static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) #if defined(HAVE_PIPE) case SOCK_STREAM_PIPE: - { - int p[2]; - static const char *mode[2] = { "r", "w" }; - - if (argc != 2 || ipv6) { - goto wrongargs; - } - - if (pipe(p) < 0) { - JimAioSetError(interp, NULL); - return JIM_ERR; - } - - return JimMakeChannelPair(interp, p, argv[1], "aio.pipe%ld", 0, mode); + if (argc != 2 || ipv6) { + goto wrongargs; } - break; + return JimAioPipeCommand(interp, 1, &argv[1]); #endif default: @@ -2006,53 +2042,6 @@ static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) } #endif /* JIM_BOOTSTRAP */ -/** - * Returns the file descriptor of a writable, newly created temp file - * or -1 on error. - * - * On success, leaves the filename in the interpreter result, otherwise - * leaves an error message. - */ -int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template) -{ -#ifdef HAVE_MKSTEMP - int fd; - mode_t mask; - Jim_Obj *filenameObj; - - if (filename_template == NULL) { - const char *tmpdir = getenv("TMPDIR"); - if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) { - tmpdir = "/tmp/"; - } - filenameObj = Jim_NewStringObj(interp, tmpdir, -1); - if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') { - Jim_AppendString(interp, filenameObj, "/", 1); - } - Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1); - } - else { - filenameObj = Jim_NewStringObj(interp, filename_template, -1); - } - - /* Update the template name directly with the filename */ - mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); - fd = mkstemp(filenameObj->bytes); - umask(mask); - if (fd < 0) { - Jim_IncrRefCount(filenameObj); - Jim_SetResultFormatted(interp, "%#s: %s", filenameObj, strerror(errno)); - return -1; - } - - Jim_SetResult(interp, filenameObj); - return fd; -#else - Jim_SetResultString(interp, "platform has no tempfile support", -1); - return -1; -#endif -} - #if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP) static int JimAioLoadSSLCertsCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { @@ -2085,9 +2074,12 @@ int Jim_aioInit(Jim_Interp *interp) #endif Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL); -#ifndef JIM_ANSIC +#ifdef HAVE_SOCKETS Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL); #endif +#ifdef HAVE_PIPE + Jim_CreateCommand(interp, "pipe", JimAioPipeCommand, NULL, NULL); +#endif /* Create filehandles for stdin, stdout and stderr */ JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r"); diff --git a/jim-exec.c b/jim-exec.c index 125f41a..0d248f4 100644 --- a/jim-exec.c +++ b/jim-exec.c @@ -93,84 +93,24 @@ int Jim_execInit(Jim_Interp *interp) #include #include +#include "jim-signal.h" +#include "jimiocompat.h" +#include -#if defined(__MINGW32__) - /* XXX: Should we use this implementation for cygwin too? msvc? */ - #ifndef STRICT - #define STRICT - #endif - #define WIN32_LEAN_AND_MEAN - #include - #include - - typedef HANDLE fdtype; - typedef HANDLE pidtype; - #define JIM_BAD_FD INVALID_HANDLE_VALUE - #define JIM_BAD_PID INVALID_HANDLE_VALUE - #define JimCloseFd CloseHandle - - #define WIFEXITED(STATUS) 1 - #define WEXITSTATUS(STATUS) (STATUS) - #define WIFSIGNALED(STATUS) 0 - #define WTERMSIG(STATUS) 0 - #define WNOHANG 1 - - static fdtype JimFileno(FILE *fh); - static pidtype JimWaitPid(pidtype pid, int *status, int nohang); - static fdtype JimDupFd(fdtype infd); - static fdtype JimOpenForRead(const char *filename); - static FILE *JimFdOpenForRead(fdtype fd); - static int JimPipe(fdtype pipefd[2]); - static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, - fdtype inputId, fdtype outputId, fdtype errorId); - static int JimErrno(void); -#else - #include "jim-signal.h" - #include - #include - #include - #include - - typedef int fdtype; - typedef int pidtype; - #define JimPipe pipe - #define JimErrno() errno - #define JIM_BAD_FD -1 - #define JIM_BAD_PID -1 - #define JimFileno fileno - #define JimReadFd read - #define JimCloseFd close - #define JimWaitPid waitpid - #define JimDupFd dup - #define JimFdOpenForRead(FD) fdopen((FD), "r") - #define JimOpenForRead(NAME) open((NAME), O_RDONLY, 0) - - #ifndef HAVE_EXECVPE - #define execvpe(ARG0, ARGV, ENV) execvp(ARG0, ARGV) - #endif -#endif +struct WaitInfoTable; -static const char *JimStrError(void); static char **JimOriginalEnviron(void); static char **JimSaveEnv(char **env); static void JimRestoreEnv(char **env); static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, - pidtype **pidArrayPtr, fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr); -static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr); + pidtype **pidArrayPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr); +static void JimDetachPids(struct WaitInfoTable *table, int numPids, const pidtype *pidPtr); static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj); -static fdtype JimCreateTemp(Jim_Interp *interp, const char *contents, int len); -static fdtype JimOpenForWrite(const char *filename, int append); -static int JimRewindFd(fdtype fd); +static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv); -static void Jim_SetResultErrno(Jim_Interp *interp, const char *msg) -{ - Jim_SetResultFormatted(interp, "%s: %s", msg, JimStrError()); -} - -static const char *JimStrError(void) -{ - return strerror(JimErrno()); -} +#if defined(__MINGW32__) +static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId); +#endif /* * If the last character of 'objPtr' is a newline, then remove @@ -191,10 +131,10 @@ static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr) * Read from 'fd', append the data to strObj and close 'fd'. * Returns 1 if data was added, 0 if not, or -1 on error. */ -static int JimAppendStreamToString(Jim_Interp *interp, fdtype fd, Jim_Obj *strObj) +static int JimAppendStreamToString(Jim_Interp *interp, int fd, Jim_Obj *strObj) { char buf[256]; - FILE *fh = JimFdOpenForRead(fd); + FILE *fh = fdopen(fd, "r"); int ret = 0; if (fh == NULL) { @@ -292,40 +232,16 @@ static void JimFreeEnv(char **env, char **original_environ) } } -#ifndef jim_ext_signal -/* Implement trivial Jim_SignalId() and Jim_SignalName(), just good enough for JimCheckWaitStatus() */ -const char *Jim_SignalId(int sig) -{ - static char buf[10]; - snprintf(buf, sizeof(buf), "%d", sig); - return buf; -} - -const char *Jim_SignalName(int sig) -{ - return Jim_SignalId(sig); -} -#endif - -/* - * Create and store an appropriate value for the global variable $::errorCode - * Based on pid and waitStatus. - * - * Returns JIM_OK for a normal exit with code 0, otherwise returns JIM_ERR. - * - * Note that $::errorCode is left unchanged for a normal exit. - * Details of any abnormal exit is appended to the errStrObj, unless it is NULL. - */ -static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj) +static Jim_Obj *JimMakeErrorCode(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj) { - Jim_Obj *errorCode; + Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); - if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) { - return JIM_OK; + if (pid == JIM_BAD_PID || pid == JIM_NO_PID) { + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "NONE", -1)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, -1)); } - errorCode = Jim_NewListObj(interp, NULL, 0); - - if (WIFEXITED(waitStatus)) { + else if (WIFEXITED(waitStatus)) { Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1)); Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus))); @@ -333,14 +249,17 @@ static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, J else { const char *type; const char *action; + const char *signame; if (WIFSIGNALED(waitStatus)) { type = "CHILDKILLED"; action = "killed"; + signame = Jim_SignalId(WTERMSIG(waitStatus)); } else { type = "CHILDSUSP"; action = "suspended"; + signame = "none"; } Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1)); @@ -353,17 +272,33 @@ static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, J } Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); - Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalId(WTERMSIG(waitStatus)), -1)); - Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, Jim_SignalName(WTERMSIG(waitStatus)), -1)); + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, signame, -1)); + } + return errorCode; +} + +/* + * Create and store an appropriate value for the global variable $::errorCode + * Based on pid and waitStatus. + * + * Returns JIM_OK for a normal exit with code 0, otherwise returns JIM_ERR. + * + * Note that $::errorCode is left unchanged for a normal exit. + * Details of any abnormal exit is appended to the errStrObj, unless it is NULL. + */ +static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj) +{ + if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) { + return JIM_OK; } - Jim_SetGlobalVariableStr(interp, "errorCode", errorCode); + Jim_SetGlobalVariableStr(interp, "errorCode", JimMakeErrorCode(interp, pid, waitStatus, errStrObj)); return JIM_ERR; } /* - * Data structures of the following type are used by JimFork and - * JimWaitPids to keep track of child processes. + * Data structures of the following type are used by exec and + * wait to keep track of child processes. */ struct WaitInfo @@ -373,10 +308,12 @@ struct WaitInfo int flags; /* Various flag bits; see below for definitions. */ }; +/* This table is shared by exec and os.wait */ struct WaitInfoTable { struct WaitInfo *info; /* Table of outstanding processes */ int size; /* Size of the allocated table */ int used; /* Number of entries in use */ + int refcount; /* Free the table once the refcount drops to 0 */ }; /* @@ -395,8 +332,10 @@ static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData) { struct WaitInfoTable *table = privData; - Jim_Free(table->info); - Jim_Free(table); + if (--table->refcount == 0) { + Jim_Free(table->info); + Jim_Free(table); + } } static struct WaitInfoTable *JimAllocWaitInfoTable(void) @@ -404,22 +343,46 @@ static struct WaitInfoTable *JimAllocWaitInfoTable(void) struct WaitInfoTable *table = Jim_Alloc(sizeof(*table)); table->info = NULL; table->size = table->used = 0; + table->refcount = 1; return table; } +/** + * Removes the given pid from the wait table. + * + * Returns 0 if OK or -1 if not found. + */ +static int JimWaitRemove(struct WaitInfoTable *table, pidtype pid) +{ + int i; + + /* Find it in the table */ + for (i = 0; i < table->used; i++) { + if (pid == table->info[i].pid) { + if (i != table->used - 1) { + table->info[i] = table->info[table->used - 1]; + } + table->used--; + return 0; + } + } + return -1; +} + /* * The main [exec] command */ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - fdtype outputId; /* File id for output pipe. -1 means command overrode. */ - fdtype errorId; /* File id for temporary file containing error output. */ + int outputId; /* File id for output pipe. -1 means command overrode. */ + int errorId; /* File id for temporary file containing error output. */ pidtype *pidPtr; int numPids, result; int child_siginfo = 1; Jim_Obj *childErrObj; Jim_Obj *errStrObj; + struct WaitInfoTable *table = Jim_CmdPrivData(interp); /* * See if the command is to be run in the background; if so, create @@ -440,7 +403,7 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i])); } Jim_SetResult(interp, listObj); - JimDetachPids(interp, numPids, pidPtr); + JimDetachPids(table, numPids, pidPtr); Jim_Free(pidPtr); return JIM_OK; } @@ -460,7 +423,7 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) errStrObj = Jim_NewStringObj(interp, "", 0); /* Read from the output pipe until EOF */ - if (outputId != JIM_BAD_FD) { + if (outputId != -1) { if (JimAppendStreamToString(interp, outputId, errStrObj) < 0) { result = JIM_ERR; Jim_SetResultErrno(interp, "error reading from output pipe"); @@ -481,9 +444,9 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) * Note that unlike Tcl, the presence of stderr output does not cause * exec to return an error. */ - if (errorId != JIM_BAD_FD) { + if (errorId != -1) { int ret; - JimRewindFd(errorId); + lseek(errorId, 0, SEEK_SET); ret = JimAppendStreamToString(interp, errorId, errStrObj); if (ret < 0) { Jim_SetResultErrno(interp, "error reading from error pipe"); @@ -510,6 +473,65 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) return result; } +/** + * Does waitpid() on the given pid, and then removes the + * entry from the wait table. + * + * Returns the pid if OK and updates *statusPtr with the status, + * or JIM_BAD_PID if the pid was not in the table. + */ +static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr) +{ + if (JimWaitRemove(table, pid) == 0) { + /* wait for it */ + waitpid(pid, statusPtr, 0); + return pid; + } + + /* Not found */ + return JIM_BAD_PID; +} + +/** + * Indicates that one or more child processes have been placed in + * background and are no longer cared about. + * These children can be cleaned up with JimReapDetachedPids(). + */ +static void JimDetachPids(struct WaitInfoTable *table, int numPids, const pidtype *pidPtr) +{ + int j; + + for (j = 0; j < numPids; j++) { + /* Find it in the table */ + int i; + for (i = 0; i < table->used; i++) { + if (pidPtr[j] == table->info[i].pid) { + table->info[i].flags |= WI_DETACHED; + break; + } + } + } +} + +/* Use 'name getfd' to get the file descriptor associated with channel 'name' + * Returns the file descriptor or -1 on error + */ +static int JimGetChannelFd(Jim_Interp *interp, const char *name) +{ + Jim_Obj *objv[2]; + + objv[0] = Jim_NewStringObj(interp, name, -1); + objv[1] = Jim_NewStringObj(interp, "getfd", -1); + + if (Jim_EvalObjVector(interp, 2, objv) == JIM_OK) { + jim_wide fd; + if (Jim_GetWide(interp, Jim_GetResult(interp), &fd) == JIM_OK) { + return fd; + } + } + return -1; +} + static void JimReapDetachedPids(struct WaitInfoTable *table) { struct WaitInfo *waitPtr; @@ -525,7 +547,7 @@ static void JimReapDetachedPids(struct WaitInfoTable *table) for (count = table->used; count > 0; waitPtr++, count--) { if (waitPtr->flags & WI_DETACHED) { int status; - pidtype pid = JimWaitPid(waitPtr->pid, &status, WNOHANG); + pidtype pid = waitpid(waitPtr->pid, &status, WNOHANG); if (pid == waitPtr->pid) { /* Process has exited, so remove it from the table */ table->used--; @@ -539,69 +561,78 @@ static void JimReapDetachedPids(struct WaitInfoTable *table) } } -/** - * Does waitpid() on the given pid, and then removes the - * entry from the wait table. +/* + * wait ?-nohang? ?pid? * - * Returns the pid if OK and updates *statusPtr with the status, - * or JIM_BAD_PID if the pid was not in the table. + * An interface to waitpid(2) + * + * Returns a 3 element list. + * + * If the process has not exited or doesn't exist, returns: + * + * {NONE x x} + * + * If the process exited normally, returns: + * + * {CHILDSTATUS } + * + * If the process terminated on a signal, returns: + * + * {CHILDKILLED } + * + * Otherwise (core dump, stopped, continued, ...), returns: + * + * {CHILDSUSP none} + * + * With no arguments, reaps any finished background processes started by exec ... & */ -static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr) +static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - int i; + struct WaitInfoTable *table = Jim_CmdPrivData(interp); + int nohang = 0; + pidtype pid; + long pidarg; + int status; + Jim_Obj *errCodeObj; - /* Find it in the table */ - for (i = 0; i < table->used; i++) { - if (pid == table->info[i].pid) { - /* wait for it */ - JimWaitPid(pid, statusPtr, 0); + /* With no arguments, reap detached children */ + if (argc == 1) { + JimReapDetachedPids(table); + return JIM_OK; + } - /* Remove it from the table */ - if (i != table->used - 1) { - table->info[i] = table->info[table->used - 1]; - } - table->used--; - return pid; - } + if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nohang")) { + nohang = 1; + } + if (argc != nohang + 2) { + Jim_WrongNumArgs(interp, 1, argv, "?-nohang? ?pid?"); + return JIM_ERR; + } + if (Jim_GetLong(interp, argv[nohang + 1], &pidarg) != JIM_OK) { + return JIM_ERR; } - /* Not found */ - return JIM_BAD_PID; -} + pid = waitpid((pidtype)pidarg, &status, nohang ? WNOHANG : 0); -/** - * Indicates that one or more child processes have been placed in - * background and are no longer cared about. - * These children can be cleaned up with JimReapDetachedPids(). - */ -static void JimDetachPids(Jim_Interp *interp, int numPids, const pidtype *pidPtr) -{ - int j; - struct WaitInfoTable *table = Jim_CmdPrivData(interp); + errCodeObj = JimMakeErrorCode(interp, pid, status, NULL); - for (j = 0; j < numPids; j++) { - /* Find it in the table */ - int i; - for (i = 0; i < table->used; i++) { - if (pidPtr[j] == table->info[i].pid) { - table->info[i].flags |= WI_DETACHED; - break; - } - } + if (pid != JIM_BAD_PID && (WIFEXITED(status) || WIFSIGNALED(status))) { + /* The process has finished. Remove it from the wait table if it exists there */ + JimWaitRemove(table, pid); } + Jim_SetResult(interp, errCodeObj); + return JIM_OK; } -static FILE *JimGetAioFilehandle(Jim_Interp *interp, const char *name) +static int Jim_PidCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - FILE *fh; - Jim_Obj *fhObj; - - fhObj = Jim_NewStringObj(interp, name, -1); - Jim_IncrRefCount(fhObj); - fh = Jim_AioFilehandle(interp, fhObj); - Jim_DecrRefCount(interp, fhObj); + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, ""); + return JIM_ERR; + } - return fh; + Jim_SetResultInt(interp, (jim_wide)getpid()); + return JIM_OK; } /* @@ -634,7 +665,7 @@ static FILE *JimGetAioFilehandle(Jim_Interp *interp, const char *name) */ static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr, - fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr) + int *inPipePtr, int *outPipePtr, int *errFilePtr) { pidtype *pidPtr = NULL; /* Points to malloc-ed array holding all * the pids of child processes. */ @@ -671,23 +702,23 @@ JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype ** * or NULL if output goes to stdout/pipe. */ const char *error = NULL; /* Holds name of stderr file to pipe to, * or NULL if stderr goes to stderr/pipe. */ - fdtype inputId = JIM_BAD_FD; + int inputId = -1; /* Readable file id input to current command in - * pipeline (could be file or pipe). JIM_BAD_FD + * pipeline (could be file or pipe). -1 * means use stdin. */ - fdtype outputId = JIM_BAD_FD; + int outputId = -1; /* Writable file id for output from current * command in pipeline (could be file or pipe). - * JIM_BAD_FD means use stdout. */ - fdtype errorId = JIM_BAD_FD; + * -1 means use stdout. */ + int errorId = -1; /* Writable file id for all standard error - * output from all commands in pipeline. JIM_BAD_FD + * output from all commands in pipeline. -1 * means use stderr. */ - fdtype lastOutputId = JIM_BAD_FD; + int lastOutputId = -1; /* Write file id for output from last command * in pipeline (could be file or pipe). * -1 means use stdout. */ - fdtype pipeIds[2]; /* File ids for pipe that's being created. */ + int pipeIds[2]; /* File ids for pipe that's being created. */ int firstArg, lastArg; /* Indexes of first and last arguments in * current command. */ int lastBar; @@ -700,18 +731,16 @@ JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype ** char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1)); int arg_count = 0; - JimReapDetachedPids(table); - if (inPipePtr != NULL) { - *inPipePtr = JIM_BAD_FD; + *inPipePtr = -1; } if (outPipePtr != NULL) { - *outPipePtr = JIM_BAD_FD; + *outPipePtr = -1; } if (errFilePtr != NULL) { - *errFilePtr = JIM_BAD_FD; + *errFilePtr = -1; } - pipeIds[0] = pipeIds[1] = JIM_BAD_FD; + pipeIds[0] = pipeIds[1] = -1; /* * First, scan through all the arguments to figure out the structure @@ -825,39 +854,44 @@ badargs: * Immediate data in command. Create temporary file and * put data into file. */ - inputId = JimCreateTemp(interp, input, input_len); - if (inputId == JIM_BAD_FD) { + inputId = Jim_MakeTempFile(interp, NULL, 1); + if (inputId == -1) { goto error; } + if (write(inputId, input, input_len) != input_len) { + Jim_SetResultErrno(interp, "couldn't write temp file"); + close(inputId); + goto error; + } + lseek(inputId, 0L, SEEK_SET); } else if (inputFile == FILE_HANDLE) { - /* Should be a file descriptor */ - FILE *fh = JimGetAioFilehandle(interp, input); + int fd = JimGetChannelFd(interp, input); - if (fh == NULL) { + if (fd < 0) { goto error; } - inputId = JimDupFd(JimFileno(fh)); + inputId = dup(fd); } else { /* * File redirection. Just open the file. */ - inputId = JimOpenForRead(input); - if (inputId == JIM_BAD_FD) { - Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, JimStrError()); + inputId = Jim_OpenForRead(input); + if (inputId == -1) { + Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, strerror(Jim_Errno())); goto error; } } } else if (inPipePtr != NULL) { - if (JimPipe(pipeIds) != 0) { + if (pipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create input pipe for command"); goto error; } inputId = pipeIds[0]; *inPipePtr = pipeIds[1]; - pipeIds[0] = pipeIds[1] = JIM_BAD_FD; + pipeIds[0] = pipeIds[1] = -1; } /* @@ -866,20 +900,19 @@ badargs: */ if (output != NULL) { if (outputFile == FILE_HANDLE) { - FILE *fh = JimGetAioFilehandle(interp, output); - if (fh == NULL) { + int fd = JimGetChannelFd(interp, output); + if (fd < 0) { goto error; } - fflush(fh); - lastOutputId = JimDupFd(JimFileno(fh)); + lastOutputId = dup(fd); } else { /* * Output is to go to a file. */ - lastOutputId = JimOpenForWrite(output, outputFile == FILE_APPEND); - if (lastOutputId == JIM_BAD_FD) { - Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, JimStrError()); + lastOutputId = Jim_OpenForWrite(output, outputFile == FILE_APPEND); + if (lastOutputId == -1) { + Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, strerror(Jim_Errno())); goto error; } } @@ -888,43 +921,42 @@ badargs: /* * Output is to go to a pipe. */ - if (JimPipe(pipeIds) != 0) { + if (pipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create output pipe"); goto error; } lastOutputId = pipeIds[1]; *outPipePtr = pipeIds[0]; - pipeIds[0] = pipeIds[1] = JIM_BAD_FD; + pipeIds[0] = pipeIds[1] = -1; } /* If we are redirecting stderr with 2>filename or 2>@fileId, then we ignore errFilePtr */ if (error != NULL) { if (errorFile == FILE_HANDLE) { if (strcmp(error, "1") == 0) { /* Special 2>@1 */ - if (lastOutputId != JIM_BAD_FD) { - errorId = JimDupFd(lastOutputId); + if (lastOutputId != -1) { + errorId = dup(lastOutputId); } else { /* No redirection of stdout, so just use 2>@stdout */ error = "stdout"; } } - if (errorId == JIM_BAD_FD) { - FILE *fh = JimGetAioFilehandle(interp, error); - if (fh == NULL) { + if (errorId == -1) { + int fd = JimGetChannelFd(interp, error); + if (fd < 0) { goto error; } - fflush(fh); - errorId = JimDupFd(JimFileno(fh)); + errorId = dup(fd); } } else { /* * Output is to go to a file. */ - errorId = JimOpenForWrite(error, errorFile == FILE_APPEND); - if (errorId == JIM_BAD_FD) { - Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, JimStrError()); + errorId = Jim_OpenForWrite(error, errorFile == FILE_APPEND); + if (errorId == -1) { + Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, strerror(Jim_Errno())); goto error; } } @@ -938,11 +970,11 @@ badargs: * to complete before reading stderr, and processes couldn't complete * because stderr was backed up. */ - errorId = JimCreateTemp(interp, NULL, 0); - if (errorId == JIM_BAD_FD) { + errorId = Jim_MakeTempFile(interp, NULL, 1); + if (errorId == -1) { goto error; } - *errFilePtr = JimDupFd(errorId); + *errFilePtr = dup(errorId); } /* @@ -956,7 +988,7 @@ badargs: } for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) { int pipe_dup_err = 0; - fdtype origErrorId = errorId; + int origErrorId = errorId; for (lastArg = firstArg; lastArg < arg_count; lastArg++) { if (strcmp(arg_array[lastArg], "|") == 0) { @@ -979,14 +1011,14 @@ badargs: outputId = lastOutputId; } else { - if (JimPipe(pipeIds) != 0) { + if (pipe(pipeIds) != 0) { Jim_SetResultErrno(interp, "couldn't create pipe"); goto error; } outputId = pipeIds[1]; } - /* Need to do this befor vfork() */ + /* Need to do this before vfork() */ if (pipe_dup_err) { errorId = outputId; } @@ -1011,10 +1043,9 @@ badargs: } if (pid == 0) { /* Child */ - - if (inputId != -1) dup2(inputId, 0); - if (outputId != -1) dup2(outputId, 1); - if (errorId != -1) dup2(errorId, 2); + if (inputId != -1) dup2(inputId, fileno(stdin)); + if (outputId != -1) dup2(outputId, fileno(stdout)); + if (errorId != -1) dup2(errorId, fileno(stderr)); for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) { close(i); @@ -1025,7 +1056,6 @@ badargs: execvpe(arg_array[firstArg], &arg_array[firstArg], Jim_GetEnviron()); - /* Need to prep an error message before vfork(), just in case */ fprintf(stderr, "couldn't exec \"%s\"\n", arg_array[firstArg]); #ifdef JIM_MAINTAINER { @@ -1063,15 +1093,15 @@ badargs: * this child, then set up the input for the next child. */ - if (inputId != JIM_BAD_FD) { - JimCloseFd(inputId); + if (inputId != -1) { + close(inputId); } - if (outputId != JIM_BAD_FD) { - JimCloseFd(outputId); - outputId = JIM_BAD_FD; + if (outputId != -1) { + close(outputId); + outputId = -1; } inputId = pipeIds[0]; - pipeIds[0] = pipeIds[1] = JIM_BAD_FD; + pipeIds[0] = pipeIds[1] = -1; } *pidArrayPtr = pidPtr; @@ -1080,14 +1110,14 @@ badargs: */ cleanup: - if (inputId != JIM_BAD_FD) { - JimCloseFd(inputId); + if (inputId != -1) { + close(inputId); } - if (lastOutputId != JIM_BAD_FD) { - JimCloseFd(lastOutputId); + if (lastOutputId != -1) { + close(lastOutputId); } - if (errorId != JIM_BAD_FD) { - JimCloseFd(errorId); + if (errorId != -1) { + close(errorId); } Jim_Free(arg_array); @@ -1102,28 +1132,28 @@ badargs: */ error: - if ((inPipePtr != NULL) && (*inPipePtr != JIM_BAD_FD)) { - JimCloseFd(*inPipePtr); - *inPipePtr = JIM_BAD_FD; + if ((inPipePtr != NULL) && (*inPipePtr != -1)) { + close(*inPipePtr); + *inPipePtr = -1; } - if ((outPipePtr != NULL) && (*outPipePtr != JIM_BAD_FD)) { - JimCloseFd(*outPipePtr); - *outPipePtr = JIM_BAD_FD; + if ((outPipePtr != NULL) && (*outPipePtr != -1)) { + close(*outPipePtr); + *outPipePtr = -1; } - if ((errFilePtr != NULL) && (*errFilePtr != JIM_BAD_FD)) { - JimCloseFd(*errFilePtr); - *errFilePtr = JIM_BAD_FD; + if ((errFilePtr != NULL) && (*errFilePtr != -1)) { + close(*errFilePtr); + *errFilePtr = -1; } - if (pipeIds[0] != JIM_BAD_FD) { - JimCloseFd(pipeIds[0]); + if (pipeIds[0] != -1) { + close(pipeIds[0]); } - if (pipeIds[1] != JIM_BAD_FD) { - JimCloseFd(pipeIds[1]); + if (pipeIds[1] != -1) { + close(pipeIds[1]); } if (pidPtr != NULL) { for (i = 0; i < numPids; i++) { if (pidPtr[i] != JIM_BAD_PID) { - JimDetachPids(interp, 1, &pidPtr[i]); + JimDetachPids(table, 1, &pidPtr[i]); } } Jim_Free(pidPtr); @@ -1174,6 +1204,7 @@ static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, int Jim_execInit(Jim_Interp *interp) { + struct WaitInfoTable *waitinfo; if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) return JIM_ERR; @@ -1191,227 +1222,18 @@ int Jim_execInit(Jim_Interp *interp) (void)signal(SIGPIPE, SIG_IGN); #endif - Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable); + waitinfo = JimAllocWaitInfoTable(); + Jim_CreateCommand(interp, "exec", Jim_ExecCmd, waitinfo, JimFreeWaitInfoTable); + waitinfo->refcount++; + Jim_CreateCommand(interp, "wait", Jim_WaitCommand, waitinfo, JimFreeWaitInfoTable); + Jim_CreateCommand(interp, "pid", Jim_PidCommand, 0, 0); + return JIM_OK; } #if defined(__MINGW32__) /* Windows-specific (mingw) implementation */ -static SECURITY_ATTRIBUTES *JimStdSecAttrs(void) -{ - static SECURITY_ATTRIBUTES secAtts; - - secAtts.nLength = sizeof(SECURITY_ATTRIBUTES); - secAtts.lpSecurityDescriptor = NULL; - secAtts.bInheritHandle = TRUE; - return &secAtts; -} - -static int JimErrno(void) -{ - switch (GetLastError()) { - case ERROR_FILE_NOT_FOUND: return ENOENT; - case ERROR_PATH_NOT_FOUND: return ENOENT; - case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; - case ERROR_ACCESS_DENIED: return EACCES; - case ERROR_INVALID_HANDLE: return EBADF; - case ERROR_BAD_ENVIRONMENT: return E2BIG; - case ERROR_BAD_FORMAT: return ENOEXEC; - case ERROR_INVALID_ACCESS: return EACCES; - case ERROR_INVALID_DRIVE: return ENOENT; - case ERROR_CURRENT_DIRECTORY: return EACCES; - case ERROR_NOT_SAME_DEVICE: return EXDEV; - case ERROR_NO_MORE_FILES: return ENOENT; - case ERROR_WRITE_PROTECT: return EROFS; - case ERROR_BAD_UNIT: return ENXIO; - case ERROR_NOT_READY: return EBUSY; - case ERROR_BAD_COMMAND: return EIO; - case ERROR_CRC: return EIO; - case ERROR_BAD_LENGTH: return EIO; - case ERROR_SEEK: return EIO; - case ERROR_WRITE_FAULT: return EIO; - case ERROR_READ_FAULT: return EIO; - case ERROR_GEN_FAILURE: return EIO; - case ERROR_SHARING_VIOLATION: return EACCES; - case ERROR_LOCK_VIOLATION: return EACCES; - case ERROR_SHARING_BUFFER_EXCEEDED: return ENFILE; - case ERROR_HANDLE_DISK_FULL: return ENOSPC; - case ERROR_NOT_SUPPORTED: return ENODEV; - case ERROR_REM_NOT_LIST: return EBUSY; - case ERROR_DUP_NAME: return EEXIST; - case ERROR_BAD_NETPATH: return ENOENT; - case ERROR_NETWORK_BUSY: return EBUSY; - case ERROR_DEV_NOT_EXIST: return ENODEV; - case ERROR_TOO_MANY_CMDS: return EAGAIN; - case ERROR_ADAP_HDW_ERR: return EIO; - case ERROR_BAD_NET_RESP: return EIO; - case ERROR_UNEXP_NET_ERR: return EIO; - case ERROR_NETNAME_DELETED: return ENOENT; - case ERROR_NETWORK_ACCESS_DENIED: return EACCES; - case ERROR_BAD_DEV_TYPE: return ENODEV; - case ERROR_BAD_NET_NAME: return ENOENT; - case ERROR_TOO_MANY_NAMES: return ENFILE; - case ERROR_TOO_MANY_SESS: return EIO; - case ERROR_SHARING_PAUSED: return EAGAIN; - case ERROR_REDIR_PAUSED: return EAGAIN; - case ERROR_FILE_EXISTS: return EEXIST; - case ERROR_CANNOT_MAKE: return ENOSPC; - case ERROR_OUT_OF_STRUCTURES: return ENFILE; - case ERROR_ALREADY_ASSIGNED: return EEXIST; - case ERROR_INVALID_PASSWORD: return EPERM; - case ERROR_NET_WRITE_FAULT: return EIO; - case ERROR_NO_PROC_SLOTS: return EAGAIN; - case ERROR_DISK_CHANGE: return EXDEV; - case ERROR_BROKEN_PIPE: return EPIPE; - case ERROR_OPEN_FAILED: return ENOENT; - case ERROR_DISK_FULL: return ENOSPC; - case ERROR_NO_MORE_SEARCH_HANDLES: return EMFILE; - case ERROR_INVALID_TARGET_HANDLE: return EBADF; - case ERROR_INVALID_NAME: return ENOENT; - case ERROR_PROC_NOT_FOUND: return ESRCH; - case ERROR_WAIT_NO_CHILDREN: return ECHILD; - case ERROR_CHILD_NOT_COMPLETE: return ECHILD; - case ERROR_DIRECT_ACCESS_HANDLE: return EBADF; - case ERROR_SEEK_ON_DEVICE: return ESPIPE; - case ERROR_BUSY_DRIVE: return EAGAIN; - case ERROR_DIR_NOT_EMPTY: return EEXIST; - case ERROR_NOT_LOCKED: return EACCES; - case ERROR_BAD_PATHNAME: return ENOENT; - case ERROR_LOCK_FAILED: return EACCES; - case ERROR_ALREADY_EXISTS: return EEXIST; - case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG; - case ERROR_BAD_PIPE: return EPIPE; - case ERROR_PIPE_BUSY: return EAGAIN; - case ERROR_PIPE_NOT_CONNECTED: return EPIPE; - case ERROR_DIRECTORY: return ENOTDIR; - } - return EINVAL; -} - -static int JimPipe(fdtype pipefd[2]) -{ - if (CreatePipe(&pipefd[0], &pipefd[1], NULL, 0)) { - return 0; - } - return -1; -} - -static fdtype JimDupFd(fdtype infd) -{ - fdtype dupfd; - pidtype pid = GetCurrentProcess(); - - if (DuplicateHandle(pid, infd, pid, &dupfd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { - return dupfd; - } - return JIM_BAD_FD; -} - -static int JimRewindFd(fdtype fd) -{ - return SetFilePointer(fd, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ? -1 : 0; -} - -#if 0 -static int JimReadFd(fdtype fd, char *buffer, size_t len) -{ - DWORD num; - - if (ReadFile(fd, buffer, len, &num, NULL)) { - return num; - } - if (GetLastError() == ERROR_HANDLE_EOF || GetLastError() == ERROR_BROKEN_PIPE) { - return 0; - } - return -1; -} -#endif - -static FILE *JimFdOpenForRead(fdtype fd) -{ - return _fdopen(_open_osfhandle((int)fd, _O_RDONLY | _O_TEXT), "r"); -} - -static fdtype JimFileno(FILE *fh) -{ - return (fdtype)_get_osfhandle(_fileno(fh)); -} - -static fdtype JimOpenForRead(const char *filename) -{ - return CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - JimStdSecAttrs(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); -} - -static fdtype JimOpenForWrite(const char *filename, int append) -{ - fdtype fd = CreateFile(filename, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - JimStdSecAttrs(), append ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL); - if (append && fd != JIM_BAD_FD) { - SetFilePointer(fd, 0, NULL, FILE_END); - } - return fd; -} - -static FILE *JimFdOpenForWrite(fdtype fd) -{ - return _fdopen(_open_osfhandle((int)fd, _O_TEXT), "w"); -} - -static pidtype JimWaitPid(pidtype pid, int *status, int nohang) -{ - DWORD ret = WaitForSingleObject(pid, nohang ? 0 : INFINITE); - if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) { - /* WAIT_TIMEOUT can only happend with WNOHANG */ - return JIM_BAD_PID; - } - GetExitCodeProcess(pid, &ret); - *status = ret; - CloseHandle(pid); - return pid; -} - -static HANDLE JimCreateTemp(Jim_Interp *interp, const char *contents, int len) -{ - char name[MAX_PATH]; - HANDLE handle; - - if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, "JIM", 0, name)) { - return JIM_BAD_FD; - } - - handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, JimStdSecAttrs(), - CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, - NULL); - - if (handle == INVALID_HANDLE_VALUE) { - goto error; - } - - if (contents != NULL) { - /* Use fdopen() to get automatic text-mode translation */ - FILE *fh = JimFdOpenForWrite(JimDupFd(handle)); - if (fh == NULL) { - goto error; - } - - if (fwrite(contents, len, 1, fh) != 1) { - fclose(fh); - goto error; - } - fseek(fh, 0, SEEK_SET); - fclose(fh); - } - return handle; - - error: - Jim_SetResultErrno(interp, "failed to create temp file"); - CloseHandle(handle); - DeleteFile(name); - return JIM_BAD_FD; -} - static int JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH]) { @@ -1524,12 +1346,15 @@ JimWinBuildCommandLine(Jim_Interp *interp, char **argv) return strObj; } +/** + * Note that inputId, etc. are osf_handles. + */ static pidtype -JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, fdtype inputId, fdtype outputId, fdtype errorId) +JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId) { STARTUPINFO startInfo; PROCESS_INFORMATION procInfo; - HANDLE hProcess, h; + HANDLE hProcess; char execPath[MAX_PATH]; pidtype pid = JIM_BAD_PID; Jim_Obj *cmdLineObj; @@ -1561,42 +1386,37 @@ JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, fdtype inputId, * and stderr of the child process. The duplicate handles are set to * be inheritable, so the child process can use them. */ - if (inputId == JIM_BAD_FD) { - if (CreatePipe(&startInfo.hStdInput, &h, JimStdSecAttrs(), 0) != FALSE) { - CloseHandle(h); - } - } else { - DuplicateHandle(hProcess, inputId, hProcess, &startInfo.hStdInput, - 0, TRUE, DUPLICATE_SAME_ACCESS); + /* + * If stdin was not redirected, input should come from the parent's stdin + */ + if (inputId == -1) { + inputId = _fileno(stdin); } - if (startInfo.hStdInput == JIM_BAD_FD) { + DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(inputId), hProcess, &startInfo.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + if (startInfo.hStdInput == INVALID_HANDLE_VALUE) { goto end; } - if (outputId == JIM_BAD_FD) { - startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0, - JimStdSecAttrs(), OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } else { - DuplicateHandle(hProcess, outputId, hProcess, &startInfo.hStdOutput, - 0, TRUE, DUPLICATE_SAME_ACCESS); + /* + * If stdout was not redirected, output should go to the parent's stdout + */ + if (outputId == -1) { + outputId = _fileno(stdout); } - if (startInfo.hStdOutput == JIM_BAD_FD) { + DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(outputId), hProcess, &startInfo.hStdOutput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) { goto end; } - if (errorId == JIM_BAD_FD) { - /* - * If handle was not set, errors should be sent to an infinitely - * deep sink. - */ - - startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0, - JimStdSecAttrs(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - } else { - DuplicateHandle(hProcess, errorId, hProcess, &startInfo.hStdError, - 0, TRUE, DUPLICATE_SAME_ACCESS); + /* Ditto stderr */ + if (errorId == -1) { + errorId = _fileno(stderr); } - if (startInfo.hStdError == JIM_BAD_FD) { + DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(errorId), hProcess, &startInfo.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS); + if (startInfo.hStdError == INVALID_HANDLE_VALUE) { goto end; } @@ -1636,46 +1456,19 @@ JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, fdtype inputId, end: Jim_FreeNewObj(interp, cmdLineObj); - if (startInfo.hStdInput != JIM_BAD_FD) { + if (startInfo.hStdInput != INVALID_HANDLE_VALUE) { CloseHandle(startInfo.hStdInput); } - if (startInfo.hStdOutput != JIM_BAD_FD) { + if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) { CloseHandle(startInfo.hStdOutput); } - if (startInfo.hStdError != JIM_BAD_FD) { + if (startInfo.hStdError != INVALID_HANDLE_VALUE) { CloseHandle(startInfo.hStdError); } return pid; } -#else -/* Unix-specific implementation */ -static int JimOpenForWrite(const char *filename, int append) -{ - return open(filename, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666); -} - -static int JimRewindFd(int fd) -{ - return lseek(fd, 0L, SEEK_SET); -} -static int JimCreateTemp(Jim_Interp *interp, const char *contents, int len) -{ - int fd = Jim_MakeTempFile(interp, NULL); - - if (fd != JIM_BAD_FD) { - unlink(Jim_String(Jim_GetResult(interp))); - if (contents) { - if (write(fd, contents, len) != len) { - Jim_SetResultErrno(interp, "couldn't write temp file"); - close(fd); - return -1; - } - lseek(fd, 0L, SEEK_SET); - } - } - return fd; -} +#else static char **JimOriginalEnviron(void) { diff --git a/jim-file.c b/jim-file.c index 07fe699..66522e3 100644 --- a/jim-file.c +++ b/jim-file.c @@ -492,7 +492,7 @@ static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv) static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL); + int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL, 0); if (fd < 0) { return JIM_ERR; diff --git a/jim-nosignal.c b/jim-nosignal.c new file mode 100644 index 0000000..bc39712 --- /dev/null +++ b/jim-nosignal.c @@ -0,0 +1,28 @@ +#include +#include + +#include +#include + +/* Implement trivial Jim_SignalId() just good enough for JimMakeErrorCode() in [exec] */ + + +/* This works for mingw, but is not really portable */ +#ifndef SIGPIPE +#define SIGPIPE 13 +#endif +#ifndef SIGINT +#define SIGINT 2 +#endif + +const char *Jim_SignalId(int sig) +{ + static char buf[10]; + switch (sig) { + case SIGINT: return "SIGINT"; + case SIGPIPE: return "SIGPIPE"; + + } + snprintf(buf, sizeof(buf), "%d", sig); + return buf; +} diff --git a/jim-posix.c b/jim-posix.c index af8c0f1..a4ba61e 100644 --- a/jim-posix.c +++ b/jim-posix.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include "jimautoconf.h" @@ -72,88 +71,6 @@ static int Jim_PosixForkCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar } #endif -/* - * os.wait ?-nohang? pid - * - * An interface to waitpid(2) - * - * Returns a 3 element list. - * - * If -nohang is specified, and the process is still alive, returns - * - * {0 none 0} - * - * If the process does not exist or has already been waited for, returns: - * - * {-1 error } - * - * If the process exited normally, returns: - * - * { exit } - * - * If the process terminated on a signal, returns: - * - * { signal } - * - * Otherwise (core dump, stopped, continued, ...), returns: - * - * { other 0} - */ -static int Jim_PosixWaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) -{ - int nohang = 0; - long pid; - int status; - Jim_Obj *listObj; - const char *type; - int value; - - if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nohang")) { - nohang = 1; - } - if (argc != nohang + 2) { - Jim_WrongNumArgs(interp, 1, argv, "?-nohang? pid"); - return JIM_ERR; - } - if (Jim_GetLong(interp, argv[nohang + 1], &pid) != JIM_OK) { - return JIM_ERR; - } - - pid = waitpid(pid, &status, nohang ? WNOHANG : 0); - listObj = Jim_NewListObj(interp, NULL, 0); - Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, pid)); - if (pid < 0) { - type = "error"; - value = errno; - } - else if (pid == 0) { - type = "none"; - value = 0; - } - else if (WIFEXITED(status)) { - type = "exit"; - value = WEXITSTATUS(status); - } - else if (WIFSIGNALED(status)) { - type = "signal"; - value = WTERMSIG(status); - } - else { - type = "other"; - value = 0; - } - - Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, type, -1)); - if (pid < 0) { - Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, strerror(value), -1)); - } - else { - Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, value)); - } - Jim_SetResult(interp, listObj); - return JIM_OK; -} - static int Jim_PosixGetidsCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objv[8]; @@ -218,17 +135,6 @@ static int Jim_PosixUptimeCommand(Jim_Interp *interp, int argc, Jim_Obj *const * return JIM_OK; } -static int Jim_PosixPidCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) -{ - if (argc != 1) { - Jim_WrongNumArgs(interp, 1, argv, ""); - return JIM_ERR; - } - - Jim_SetResultInt(interp, getpid()); - return JIM_OK; -} - int Jim_posixInit(Jim_Interp *interp) { if (Jim_PackageProvide(interp, "posix", "1.0", JIM_ERRMSG)) @@ -237,10 +143,8 @@ int Jim_posixInit(Jim_Interp *interp) #ifdef HAVE_FORK Jim_CreateCommand(interp, "os.fork", Jim_PosixForkCommand, NULL, NULL); #endif - Jim_CreateCommand(interp, "os.wait", Jim_PosixWaitCommand, NULL, NULL); Jim_CreateCommand(interp, "os.getids", Jim_PosixGetidsCommand, NULL, NULL); Jim_CreateCommand(interp, "os.gethostname", Jim_PosixGethostnameCommand, NULL, NULL); Jim_CreateCommand(interp, "os.uptime", Jim_PosixUptimeCommand, NULL, NULL); - Jim_CreateCommand(interp, "pid", Jim_PosixPidCommand, NULL, NULL); return JIM_OK; } diff --git a/jim-signal.c b/jim-signal.c index 483d021..5fb9c9f 100644 --- a/jim-signal.c +++ b/jim-signal.c @@ -128,16 +128,6 @@ const char *Jim_SignalId(int sig) return "unknown signal"; } -const char *Jim_SignalName(int sig) -{ -#ifdef HAVE_SYS_SIGLIST - if (sig >= 0 && sig < NSIG) { - return sys_siglist[sig]; - } -#endif - return Jim_SignalId(sig); -} - /** * Given the name of a signal, returns the signal value if found, * or returns -1 (and sets an error) if not found. diff --git a/jim-signal.h b/jim-signal.h index 84dcd8b..09f688a 100644 --- a/jim-signal.h +++ b/jim-signal.h @@ -11,14 +11,6 @@ extern "C" { */ const char *Jim_SignalId(int sig); -/** - * If available, returns a short description of the given signal. - * e.g. "Terminated", "Interrupted" - * - * Otherwise returns the same as Jim_SignalId() - */ -const char *Jim_SignalName(int sig); - #ifdef __cplusplus } #endif diff --git a/jim-win32compat.h b/jim-win32compat.h index 325d1dd..16133b5 100644 --- a/jim-win32compat.h +++ b/jim-win32compat.h @@ -67,11 +67,6 @@ DIR *opendir(const char *name); int closedir(DIR *dir); struct dirent *readdir(DIR *dir); -#elif defined(__MINGW32__) - -#include -#define strtod __strtod - #endif #endif /* WIN32 */ diff --git a/jim.h b/jim.h index 65bf7cc..1dd0f0c 100644 --- a/jim.h +++ b/jim.h @@ -609,7 +609,7 @@ JIM_EXPORT char *Jim_StrDupLen(const char *s, int l); /* environment */ JIM_EXPORT char **Jim_GetEnviron(void); JIM_EXPORT void Jim_SetEnviron(char **env); -JIM_EXPORT int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template); +JIM_EXPORT int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file); /* evaluation */ JIM_EXPORT int Jim_Eval(Jim_Interp *interp, const char *script); diff --git a/jim_tcl.txt b/jim_tcl.txt index 6027873..d1eaffb 100644 --- a/jim_tcl.txt +++ b/jim_tcl.txt @@ -62,6 +62,10 @@ Changes between 0.77 and 0.78 6. Add scriptable autocompletion support with `history completion` 7. Add support for `tree delete` 8. Add support for `defer` and '$jim::defer' +9. Renamed `os.wait` to `wait`, now more Tcl-compatible and compatible with `exec ... &` +10. `pipe` is now a synonym for `socket pipe` +11. Closing a pipe open with `open |...` now returns Tcl-like status +12. It is now possible to used `exec` redirection with a pipe opened with `open |...` Changes between 0.76 and 0.77 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3403,6 +3407,10 @@ switch. Output to files is buffered internally by Tcl; the `flush` command may be used to force buffered characters to be output. +pipe +~~~~ +Creates a pair of `aio` channels and returns the handles as a list: +{read write}+ + pwd ~~~ +*pwd*+ @@ -4459,6 +4467,35 @@ Although 'add2' could have been implemented using `uplevel` instead of `upvar`, `upvar` makes it simpler for 'add2' to access the variable in the caller's procedure frame. +wait +~~~~ ++*wait*+ + ++*wait -nohang* 'pid'+ + +With no arguments, cleans up any processes started by `exec ... &` that have completed +(reaps zombie processes). + +With one or two arguments, waits for a process by id, either returned by `exec ... &` +or by `os.fork` (if supported). + +Waits for the process to complete, unless +-nohang+ is specified, in which case returns +immediately if the process is still running. + +Returns a list of 3 elements. + ++{NONE x x}+ if the process does not exist or has already been waited for, or +if -nohang is specified, and the process is still alive. + ++{CHILDSTATUS }+ if the process exited normally. + ++{CHILDKILLED }+ if the process terminated on a signal. + ++{CHILDSUSP none}+ if the process terminated for some other reason. + +Note that on platforms supporting waitpid(2), +pid+ can also be given special values such +as 0 or -1. See waitpid(2) for more detail. + while ~~~~~ +*while* 'test body'+ @@ -4484,26 +4521,13 @@ OPTIONAL-EXTENSIONS The following extensions may or may not be available depending upon what options were selected when Jim Tcl was built. + [[cmd_1]] -posix: os.fork, os.wait, os.gethostname, os.getids, os.uptime -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +posix: os.fork, os.gethostname, os.getids, os.uptime +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*os.fork*+:: Invokes 'fork(2)' and returns the result. -+*os.wait -nohang* 'pid'+:: - Invokes waitpid(2), with WNOHANG if +-nohang+ is specified. - Returns a list of 3 elements. - - {0 none 0} if -nohang is specified, and the process is still alive. - - {-1 error } if the process does not exist or has already been waited for. - - { exit } if the process exited normally. - - { signal } if the process terminated on a signal. - - { other 0} otherwise (core dump, stopped, continued, etc.) - +*os.gethostname*+:: Invokes 'gethostname(3)' and returns the result. @@ -4759,11 +4783,10 @@ Various socket types may be created. A UDP socket server. +*socket pipe*+:: - A pipe. Note that unlike all other socket types, this command returns - a list of two channels: {read write} + A synonym for `pipe` +*socket pair*+:: - A socketpair (see socketpair(2)). Like `socket pipe`, this command returns + A socketpair (see socketpair(2)). Like `pipe`, this command returns a list of two channels: {s1 s2}. These channels are both readable and writable. This command creates a socket connected (client) or bound (server) to the given diff --git a/jimiocompat.c b/jimiocompat.c new file mode 100644 index 0000000..c8c3f86 --- /dev/null +++ b/jimiocompat.c @@ -0,0 +1,214 @@ +#include +#include "jimiocompat.h" + +void Jim_SetResultErrno(Jim_Interp *interp, const char *msg) +{ + Jim_SetResultFormatted(interp, "%s: %s", msg, strerror(Jim_Errno())); +} + +#if defined(__MINGW32__) +#include + +int Jim_Errno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: return ENOENT; + case ERROR_PATH_NOT_FOUND: return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; + case ERROR_ACCESS_DENIED: return EACCES; + case ERROR_INVALID_HANDLE: return EBADF; + case ERROR_BAD_ENVIRONMENT: return E2BIG; + case ERROR_BAD_FORMAT: return ENOEXEC; + case ERROR_INVALID_ACCESS: return EACCES; + case ERROR_INVALID_DRIVE: return ENOENT; + case ERROR_CURRENT_DIRECTORY: return EACCES; + case ERROR_NOT_SAME_DEVICE: return EXDEV; + case ERROR_NO_MORE_FILES: return ENOENT; + case ERROR_WRITE_PROTECT: return EROFS; + case ERROR_BAD_UNIT: return ENXIO; + case ERROR_NOT_READY: return EBUSY; + case ERROR_BAD_COMMAND: return EIO; + case ERROR_CRC: return EIO; + case ERROR_BAD_LENGTH: return EIO; + case ERROR_SEEK: return EIO; + case ERROR_WRITE_FAULT: return EIO; + case ERROR_READ_FAULT: return EIO; + case ERROR_GEN_FAILURE: return EIO; + case ERROR_SHARING_VIOLATION: return EACCES; + case ERROR_LOCK_VIOLATION: return EACCES; + case ERROR_SHARING_BUFFER_EXCEEDED: return ENFILE; + case ERROR_HANDLE_DISK_FULL: return ENOSPC; + case ERROR_NOT_SUPPORTED: return ENODEV; + case ERROR_REM_NOT_LIST: return EBUSY; + case ERROR_DUP_NAME: return EEXIST; + case ERROR_BAD_NETPATH: return ENOENT; + case ERROR_NETWORK_BUSY: return EBUSY; + case ERROR_DEV_NOT_EXIST: return ENODEV; + case ERROR_TOO_MANY_CMDS: return EAGAIN; + case ERROR_ADAP_HDW_ERR: return EIO; + case ERROR_BAD_NET_RESP: return EIO; + case ERROR_UNEXP_NET_ERR: return EIO; + case ERROR_NETNAME_DELETED: return ENOENT; + case ERROR_NETWORK_ACCESS_DENIED: return EACCES; + case ERROR_BAD_DEV_TYPE: return ENODEV; + case ERROR_BAD_NET_NAME: return ENOENT; + case ERROR_TOO_MANY_NAMES: return ENFILE; + case ERROR_TOO_MANY_SESS: return EIO; + case ERROR_SHARING_PAUSED: return EAGAIN; + case ERROR_REDIR_PAUSED: return EAGAIN; + case ERROR_FILE_EXISTS: return EEXIST; + case ERROR_CANNOT_MAKE: return ENOSPC; + case ERROR_OUT_OF_STRUCTURES: return ENFILE; + case ERROR_ALREADY_ASSIGNED: return EEXIST; + case ERROR_INVALID_PASSWORD: return EPERM; + case ERROR_NET_WRITE_FAULT: return EIO; + case ERROR_NO_PROC_SLOTS: return EAGAIN; + case ERROR_DISK_CHANGE: return EXDEV; + case ERROR_BROKEN_PIPE: return EPIPE; + case ERROR_OPEN_FAILED: return ENOENT; + case ERROR_DISK_FULL: return ENOSPC; + case ERROR_NO_MORE_SEARCH_HANDLES: return EMFILE; + case ERROR_INVALID_TARGET_HANDLE: return EBADF; + case ERROR_INVALID_NAME: return ENOENT; + case ERROR_PROC_NOT_FOUND: return ESRCH; + case ERROR_WAIT_NO_CHILDREN: return ECHILD; + case ERROR_CHILD_NOT_COMPLETE: return ECHILD; + case ERROR_DIRECT_ACCESS_HANDLE: return EBADF; + case ERROR_SEEK_ON_DEVICE: return ESPIPE; + case ERROR_BUSY_DRIVE: return EAGAIN; + case ERROR_DIR_NOT_EMPTY: return EEXIST; + case ERROR_NOT_LOCKED: return EACCES; + case ERROR_BAD_PATHNAME: return ENOENT; + case ERROR_LOCK_FAILED: return EACCES; + case ERROR_ALREADY_EXISTS: return EEXIST; + case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG; + case ERROR_BAD_PIPE: return EPIPE; + case ERROR_PIPE_BUSY: return EAGAIN; + case ERROR_PIPE_NOT_CONNECTED: return EPIPE; + case ERROR_DIRECTORY: return ENOTDIR; + } + return EINVAL; +} + +pidtype waitpid(pidtype pid, int *status, int nohang) +{ + DWORD ret = WaitForSingleObject(pid, nohang ? 0 : INFINITE); + if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) { + /* WAIT_TIMEOUT can only happend with WNOHANG */ + return JIM_BAD_PID; + } + GetExitCodeProcess(pid, &ret); + *status = ret; + CloseHandle(pid); + return pid; +} + +int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file) +{ + char name[MAX_PATH]; + HANDLE handle; + + if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, filename_template ? filename_template : "JIM", 0, name)) { + return -1; + } + + handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | (unlink_file ? FILE_FLAG_DELETE_ON_CLOSE : 0), + NULL); + + if (handle == INVALID_HANDLE_VALUE) { + goto error; + } + + Jim_SetResultString(interp, name, -1); + return _open_osfhandle((int)handle, _O_RDWR | _O_TEXT); + + error: + Jim_SetResultErrno(interp, name); + DeleteFile(name); + return -1; +} + +int Jim_OpenForWrite(const char *filename, int append) +{ + if (strcmp(filename, "/dev/null") == 0) { + filename = "nul:"; + } + int fd = _open(filename, _O_WRONLY | _O_CREAT | _O_TEXT | (append ? _O_APPEND : _O_TRUNC), _S_IREAD | _S_IWRITE); + if (fd >= 0 && append) { + /* Note that _O_APPEND doesn't actually work. need to do it manually */ + _lseek(fd, 0L, SEEK_END); + } + return fd; +} + +int Jim_OpenForRead(const char *filename) +{ + if (strcmp(filename, "/dev/null") == 0) { + filename = "nul:"; + } + return _open(filename, _O_RDONLY | _O_TEXT, 0); +} + +#elif defined(HAVE_UNISTD_H) + +/* Unix-specific implementation */ + +int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file) +{ + int fd; + mode_t mask; + Jim_Obj *filenameObj; + + if (filename_template == NULL) { + const char *tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) { + tmpdir = "/tmp/"; + } + filenameObj = Jim_NewStringObj(interp, tmpdir, -1); + if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') { + Jim_AppendString(interp, filenameObj, "/", 1); + } + Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1); + } + else { + filenameObj = Jim_NewStringObj(interp, filename_template, -1); + } + + /* Update the template name directly with the filename */ + mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); +#ifdef HAVE_MKSTEMP + fd = mkstemp(filenameObj->bytes); +#else + if (mktemp(filenameObj->bytes) == NULL) { + fd = -1; + } + else { + fd = open(filenameObj->bytes, O_RDWR | O_CREAT | O_TRUNC); + } +#endif + umask(mask); + if (fd < 0) { + Jim_SetResultErrno(interp, Jim_String(filenameObj)); + Jim_FreeNewObj(interp, filenameObj); + return -1; + } + if (unlink_file) { + remove(Jim_String(filenameObj)); + } + + Jim_SetResult(interp, filenameObj); + return fd; +} + +int Jim_OpenForWrite(const char *filename, int append) +{ + return open(filename, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666); +} + +int Jim_OpenForRead(const char *filename) +{ + return open(filename, O_RDONLY, 0); +} + +#endif diff --git a/jimiocompat.h b/jimiocompat.h new file mode 100644 index 0000000..2cbb578 --- /dev/null +++ b/jimiocompat.h @@ -0,0 +1,80 @@ +#ifndef JIMIOCOMPAT_H +#define JIMIOCOMPAT_H + +/* + * Cross-platform compatibility functions and types for I/O. + * Currently used by jim-aio.c and jim-exec.c + */ + +#include +#include + +#include "jimautoconf.h" +#include +#include + +/** + * Set an error result based on errno and the given message. + */ +void Jim_SetResultErrno(Jim_Interp *interp, const char *msg); + +/** + * Opens the file for writing (and appending if append is true). + * Returns the file descriptor, or -1 on failure. + */ +int Jim_OpenForWrite(const char *filename, int append); + +/** + * Opens the file for reading. + * Returns the file descriptor, or -1 on failure. + */ +int Jim_OpenForRead(const char *filename); + +#if defined(__MINGW32__) + #ifndef STRICT + #define STRICT + #endif + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #include + + typedef HANDLE pidtype; + #define JIM_BAD_PID INVALID_HANDLE_VALUE + /* Note that this isn't a separate value on Windows since we don't have os.fork */ + #define JIM_NO_PID INVALID_HANDLE_VALUE + + /* These seem to accord with the conventions used by msys/mingw32 */ + #define WIFEXITED(STATUS) (((STATUS) & 0xff00) == 0) + #define WEXITSTATUS(STATUS) ((STATUS) & 0x00ff) + #define WIFSIGNALED(STATUS) (((STATUS) & 0xff00) != 0) + #define WTERMSIG(STATUS) (((STATUS) >> 8) & 0xff) + #define WNOHANG 1 + + /** + * Unix-compatible errno + */ + int Jim_Errno(void); + pidtype waitpid(pidtype pid, int *status, int nohang); + + #define HAVE_PIPE + #define pipe(P) _pipe((P), 0, O_NOINHERIT) + +#elif defined(HAVE_UNISTD_H) + #include + #include + #include + #include + + typedef int pidtype; + #define Jim_Errno() errno + #define JIM_BAD_PID -1 + #define JIM_NO_PID 0 + + #ifndef HAVE_EXECVPE + #define execvpe(ARG0, ARGV, ENV) execvp(ARG0, ARGV) + #endif +#endif + +#endif diff --git a/make-bootstrap-jim b/make-bootstrap-jim index 23f852a..7484b11 100755 --- a/make-bootstrap-jim +++ b/make-bootstrap-jim @@ -115,7 +115,7 @@ outputsource() } # Now output header files, removing references to jim header files -for i in jim-win32compat.h utf8.h jim.h jim-subcmd.h jimregexp.h ; do +for i in jim-win32compat.h utf8.h jim.h jim-subcmd.h jimregexp.h jim-signal.h jimiocompat.h; do outputsource $i done @@ -129,7 +129,7 @@ done makeloadexts $allexts # And finally the core source code -for i in jim.c jim-subcmd.c utf8.c jim-format.c jimregexp.c jim-win32compat.c; do +for i in jim.c jim-subcmd.c utf8.c jim-format.c jimregexp.c jimiocompat.c jim-win32compat.c jim-nosignal.c; do outputsource $i done echo "#ifndef JIM_BOOTSTRAP_LIB_ONLY" diff --git a/tclcompat.tcl b/tclcompat.tcl index e2ae56e..b3452d8 100644 --- a/tclcompat.tcl +++ b/tclcompat.tcl @@ -120,7 +120,7 @@ proc {file copy} {{force {}} source target} { # 'open "|..." ?mode?" will invoke this wrapper around exec/pipe # Note that we return a lambda which also provides the 'pid' command proc popen {cmd {mode r}} { - lassign [socket pipe] r w + lassign [pipe] r w try { if {[string match "w*" $mode]} { lappend cmd <@$r & @@ -137,11 +137,26 @@ proc popen {cmd {mode r}} { if {$cmd eq "pid"} { return $pids } + if {$cmd eq "getfd"} { + $f getfd + } if {$cmd eq "close"} { $f close # And wait for the child processes to complete - foreach p $pids { os.wait $p } - return + set retopts {} + foreach p $pids { + lassign [wait $p] status - rc + if {$status eq "CHILDSTATUS"} { + if {$rc == 0} { + continue + } + set msg "child process exited abnormally" + } else { + set msg "child killed: received signal" + } + set retopts [list -code error -errorcode [list $status $p $rc] $msg] + } + return {*}$retopts } tailcall $f $cmd {*}$args } diff --git a/tests/exec.test b/tests/exec.test index 76a1b2d..0eb218a 100644 --- a/tests/exec.test +++ b/tests/exec.test @@ -17,26 +17,23 @@ source [file dirname [info script]]/testing.tcl needs cmd exec needs cmd flush -needs cmd after eventloop testConstraint unix [expr {$tcl_platform(platform) eq {unix}}] # Sleep which supports fractions of a second if {[info commands sleep] eq {}} { proc sleep {n} { - after [expr {int($n * 1000)}] + exec {*}$::sleepx $n } } set f [open sleepx w] -puts $f "#![info nameofexecutable]" puts $f { - set seconds [lindex $argv 0] - after [expr {int($seconds * 1000)}] + sleep "$@" } close $f #catch {exec chmod +x sleepx} -set sleepx [list [info nameofexecutable] sleepx] +set sleepx [list sh sleepx] # Basic operations. @@ -416,6 +413,29 @@ test exec-16.1 {flush output before exec} -body { Second line Third line} +test exec-17.1 {redirecting from command pipeline} -setup { + makeFile "abc\nghi\njkl" gorp.file +} -body { + set f [open "|cat gorp.file | wc -l" r] + set result [lindex [exec cat <@$f] 0] + close $f + set result +} -cleanup { + file delete gorp.file +} -result {3} + +test exec-17.2 {redirecting to command pipeline} -setup { + makeFile "abc\nghi\njkl" gorp.file +} -body { + set f [open "|wc -l >gorp2.file" w] + exec cat gorp.file >@$f + flush $f + close $f + lindex [exec cat gorp2.file] 0 +} -cleanup { + file delete gorp.file gorp2.file +} -result {3} + file delete sleepx testreport diff --git a/tests/exec2.test b/tests/exec2.test index e43bba0..91108da 100644 --- a/tests/exec2.test +++ b/tests/exec2.test @@ -44,4 +44,30 @@ test exec2-2.4 "Remove all env var" { array set env [array get saveenv] +test exec2-3.1 "close pipeline return value" { + set f [open |false] + set rc [catch {close $f} msg opts] + lassign [dict get $opts -errorcode] status pid exitcode + list $rc $msg $status $exitcode +} {1 {child process exited abnormally} CHILDSTATUS 1} + +test exec2-3.2 "close pipeline return value" -body { + set f [open "|echo abc | grep def | wc" ] + set rc [catch {close $f} msg opts] + lassign [dict get $opts -errorcode] status pid exitcode + list $rc $msg $status $exitcode +} -match glob -result {1 {child killed*} CHILDKILLED SIGPIPE} + + +test exec2-3.4 "wait for background task" { + set pid [exec sleep 0.1 &] + lassign [wait $pid] status newpid exitcode + if {$pid != $newpid} { + error "Got wrong pid from wait" + } else { + list $status $exitcode + } +} {CHILDSTATUS 0} + + testreport diff --git a/tests/pid.test b/tests/pid.test index 6a534a5..56ffcf8 100644 --- a/tests/pid.test +++ b/tests/pid.test @@ -19,7 +19,7 @@ needs cmd pid posix needs cmd exec catch {package require regexp} testConstraint regexp [expr {[info commands regexp] ne {}}] -testConstraint socket [expr {[info commands socket] ne {}}] +testConstraint pipe [expr {[info commands pipe] ne {}}] testConstraint getpid [expr {[catch pid] == 0}] # This is a proxy for tcl || tclcompat testConstraint pidchan [expr {[info commands fconfigure] ne {}}] @@ -29,7 +29,7 @@ file delete test1 test pid-1.1 {pid command} {regexp getpid} { regexp {(^[0-9]+$)|(^0x[0-9a-fA-F]+$)} [pid] } 1 -test pid-1.2 {pid command} {regexp socket pidchan} { +test pid-1.2 {pid command} {regexp pipe pidchan} { set f [open {| echo foo | cat >test1} w] set pids [pid $f] close $f @@ -38,7 +38,7 @@ test pid-1.2 {pid command} {regexp socket pidchan} { [regexp {^[0-9]+$} [lindex $pids 1]] \ [expr {[lindex $pids 0] == [lindex $pids 1]}] } {2 1 1 0} -test pid-1.3 {pid command} {socket pidchan} { +test pid-1.3 {pid command} {pipe pidchan} { set f [open test1 w] set pids [pid $f] close $f -- cgit v1.1