Jim Tcl
Check-in [da86539f12]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment: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 <steveb@workware.net.au>

Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: da86539f12d2558ca97fdf1d8226f04fda17e56f
User & Date: steveb@workware.net.au 2017-10-16 21:39:39
Context
2017-10-16
21:51
tests: Try to fix exec2-3.2 on Windows

On some mingw platforms exec2-3.2 isn't producing SIGPIPE Try to make it do so.

Signed-off-by: Steve Bennett <steveb@workware.net.au> check-in: 010735d763 user: steveb@workware.net.au tags: trunk

21:39
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 <steveb@workware.net.au> check-in: da86539f12 user: steveb@workware.net.au tags: trunk

2017-10-15
23:53
jim: cmdPrivData needs to be saved and restored

If a C command invokes another C command (including indirectly), cmdPrivData was being changed but not restored check-in: 04fe854dc1 user: steveb@workware.net.au tags: trunk

Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Makefile.in.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

@if HAVE_CXX_EXTENSIONS
JIMSH_CC := $(CXX) $(CXXFLAGS)
@else
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 \
    @EXTRA_OBJS@ @C_EXT_OBJS@ @TCL_EXT_OBJS@

JIMSH := jimsh@EXEEXT@

@if JIM_INSTALL
INSTALL_DATA_DIR ?= mkdir -p
INSTALL_DATA ?= cp







|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

@if HAVE_CXX_EXTENSIONS
JIMSH_CC := $(CXX) $(CXXFLAGS)
@else
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 jimiocompat.o \
    @EXTRA_OBJS@ @C_EXT_OBJS@ @TCL_EXT_OBJS@

JIMSH := jimsh@EXEEXT@

@if JIM_INSTALL
INSTALL_DATA_DIR ?= mkdir -p
INSTALL_DATA ?= cp

Changes to auto.def.

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
...
429
430
431
432
433
434
435





436
437
438
439
440
441
442
    readdir  { check {[have-feature opendir]} }
    readline { pkg-config readline check {[cc-check-function-in-lib readline readline]} libdep lib_readline}
    rlprompt { dep readline }
    tree     { dep oo }
    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]} }
    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]} }
    tree     { dep oo }
    win32    { check {[have-feature windows]} }
}

................................................................................

    # If the built-in regexp overrides the system regcomp, etc.
    # jim must be built shared so that the correct symbols are found
    if {[ext-get-status regexp] eq "m" && [get-define JIM_STATICLIB] && [have-feature regcomp]} {
        user-error "Must use --shared with regexp module and built-in regexp"
    }
}






if {[ext-get-status load] eq "n"} {
    # If we don't have load, no need to support shared objects
    define SH_LINKFLAGS ""
}

msg-result "Jim static extensions: [lsort [concat $extinfo(static-tcl) $extinfo(static-c)]]"







|







 







>
>
>
>
>







350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
...
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
    readdir  { check {[have-feature opendir]} }
    readline { pkg-config readline check {[cc-check-function-in-lib readline readline]} libdep lib_readline}
    rlprompt { dep readline }
    tree     { dep oo }
    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]} }
    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]} }
    tree     { dep oo }
    win32    { check {[have-feature windows]} }
}

................................................................................

    # If the built-in regexp overrides the system regcomp, etc.
    # jim must be built shared so that the correct symbols are found
    if {[ext-get-status regexp] eq "m" && [get-define JIM_STATICLIB] && [have-feature regcomp]} {
        user-error "Must use --shared with regexp module and built-in 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 ""
}

msg-result "Jim static extensions: [lsort [concat $extinfo(static-tcl) $extinfo(static-c)]]"

Changes to jim-aio.c.

48
49
50
51
52
53
54

55
56
57
58
59
60
61
62
63
64



65
66
67
68
69
70
71
..
94
95
96
97
98
99
100






101
102
103
104
105
106
107
108
109
110
111
...
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
...
586
587
588
589
590
591
592










593
594
595
596
597
598
599
...
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
...
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
...
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
....
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
....
1370
1371
1372
1373
1374
1375
1376







1377
1378
1379
1380
1381
1382
1383
....
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
....
1420
1421
1422
1423
1424
1425
1426







1427
1428
1429
1430
1431
1432
1433
....
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
....
1521
1522
1523
1524
1525
1526
1527
1528

1529
1530
1531
1532
1533
1534
1535
....
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
....
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568

1569
1570
1571
1572
1573
1574
1575
....
1661
1662
1663
1664
1665
1666
1667
1668
1669

1670

1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691

1692

1693
1694
1695
1696
1697
1698
1699
....
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
....
1733
1734
1735
1736
1737
1738
1739




















1740
1741
1742
1743
1744
1745
1746
1747
....
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997

1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
....
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090



2091
2092
2093
2094
2095
2096
2097
2098
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#include <sys/stat.h>
#endif

#include "jim.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 <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif



#else
#define JIM_ANSIC
#endif

#if defined(JIM_SSL)
#include <openssl/ssl.h>
#include <openssl/err.h>
................................................................................
#define IPV6 1
#else
#define IPV6 0
#ifndef PF_INET6
#define PF_INET6 0
#endif
#endif







#define JimCheckStreamError(interp, af) af->fops->error(af)

#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
union sockaddr_any {
    struct sockaddr sa;
    struct sockaddr_in sin;
#if IPV6
    struct sockaddr_in6 sin6;
#endif
};
................................................................................
};
#endif /* JIM_BOOTSTRAP */

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)
static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
{
#if IPV6
    /*
     * An IPv6 addr/port looks like:
     *   [::1]
     *   [::1]:2000
................................................................................
    af = Jim_AioFile(interp, command);
    if (af == NULL) {
        return NULL;
    }

    return af->fp;
}











static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    AioFile *af = Jim_CmdPrivData(interp);
    jim_wide count = 0;
    jim_wide maxlen = JIM_WIDE_MAX;
    AioFile *outf = Jim_AioFile(interp, argv[0]);
................................................................................
#else
    Jim_SetResultInt(interp, 0);
#endif

    return JIM_OK;
}

#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    AioFile *af = Jim_CmdPrivData(interp);
    char *buf;
    union sockaddr_any sa;
    long len;
    socklen_t salen = sizeof(sa);
................................................................................
    Jim_SetResultInt(interp, feof(af->fp));
    return JIM_OK;
}

static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    if (argc == 3) {
#if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN)
        static const char * const options[] = { "r", "w", NULL };
        enum { OPT_R, OPT_W, };
        int option;
        AioFile *af = Jim_CmdPrivData(interp);

        if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
            return JIM_ERR;
................................................................................
        (void)fcntl(af->fd, F_SETFL, fmode);
    }
    Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
    return JIM_OK;
}
#endif

#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
#define SOCKOPT_BOOL 0
#define SOCKOPT_INT 1
#define SOCKOPT_TIMEVAL 2   /* not currently supported */

static const struct sockopt_def {
    const char *name;
    int level;
................................................................................
            return JIM_OK;
        }
    }
    /* Not found */
    Jim_SetResultFormatted(interp, "Unknown sockopt %#s", argv[0]);
    return JIM_ERR;
}
#endif

#ifdef HAVE_FSYNC
static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    AioFile *af = Jim_CmdPrivData(interp);

    fflush(af->fp);
................................................................................
    {   "copyto",
        "handle ?size?",
        aio_cmd_copy,
        1,
        2,
        /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
    },







    {   "gets",
        "?var?",
        aio_cmd_gets,
        0,
        1,
        /* Description: Read one line and return it or store it in the var */
    },
................................................................................
    {   "isatty",
        NULL,
        aio_cmd_isatty,
        0,
        0,
        /* Description: Is the file descriptor a tty? */
    },
#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
    {   "recvfrom",
        "len ?addrvar?",
        aio_cmd_recvfrom,
        1,
        2,
        /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
    },
................................................................................
    {   "listen",
        "backlog",
        aio_cmd_listen,
        1,
        1,
        /* Description: Set the listen backlog for server socket */
    },







#endif /* JIM_BOOTSTRAP */
    {   "flush",
        NULL,
        aio_cmd_flush,
        0,
        0,
        /* Description: Flush the stream */
................................................................................
    {   "ndelay",
        "?0|1?",
        aio_cmd_ndelay,
        0,
        1,
        /* 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,
        aio_cmd_sync,
        0,
        0,
................................................................................
        "?exception-script?",
        aio_cmd_onexception,
        0,
        1,
        /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
    },
#endif
#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)

    {   "ssl",
        "?-server cert priv?",
        aio_cmd_ssl,
        0,
        3,
        JIM_MODFLAG_FULLARGV
        /* Description: Wraps a stream socket with SSL/TLS and returns a new channel */
................................................................................
    {   "verify",
        NULL,
        aio_cmd_verify,
        0,
        0,
        /* Description: Verifies the certificate of a SSL/TLS channel */
    },
#endif /* JIM_BOOTSTRAP */
#if defined(HAVE_STRUCT_FLOCK) && !defined(JIM_BOOTSTRAP)
    {   "lock",
        NULL,
        aio_cmd_lock,
        0,
        0,
        /* Description: Attempt to get a lock. */
    },
................................................................................
    {   "unlock",
        NULL,
        aio_cmd_unlock,
        0,
        0,
        /* Description: Relase a lock. */
    },
#endif /* JIM_BOOTSTRAP */
#if defined(HAVE_TERMIOS_H) && !defined(JIM_BOOTSTRAP)
    {   "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,
        0,
        -1,
        /* Description: Get or set tty settings - valid only on a tty */
    },

#endif /* JIM_BOOTSTRAP */
    { NULL }
};

static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
................................................................................
    if (!filename) {
        filename = Jim_NewStringObj(interp, buf, -1);
    }

    Jim_IncrRefCount(filename);

    if (fh == NULL) {
#if !defined(JIM_ANSIC)
        if (fd >= 0) {

            fh = fdopen(fd, mode);

        }
        else
#endif
            fh = fopen(Jim_String(filename), mode);

        if (fh == NULL) {
            JimAioSetError(interp, filename);
#if !defined(JIM_ANSIC)
            if (fd >= 0) {
                close(fd);
            }
#endif
            Jim_DecrRefCount(interp, filename);
            return NULL;
        }
    }

    /* Create the file command */
    af = Jim_Alloc(sizeof(*af));
    memset(af, 0, sizeof(*af));
    af->fp = fh;

    af->fd = fileno(fh);

    af->filename = filename;
#ifdef FD_CLOEXEC
    if ((openFlags & AIO_KEEPOPEN) == 0) {
        (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC);
    }
#endif
    af->openFlags = openFlags;
................................................................................
 */
static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
    const char *hdlfmt, int family, const char *mode[2])
{
    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);
            return JIM_OK;
        }
    }

................................................................................
    close(p[0]);
    close(p[1]);
    JimAioSetError(interp, NULL);
    return JIM_ERR;
}
#endif





















#if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)

static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    const char *hdlfmt = "aio.unknown%ld";
    const char *socktypes[] = {
        "unix",
        "unix.server",
................................................................................
                return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
            }
            break;
#endif

#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);
            }
            break;

#endif

        default:
            Jim_SetResultString(interp, "Unsupported socket type", -1);
            return JIM_ERR;
    }

    return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode) ? JIM_OK : JIM_ERR;
}
#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)
{
    SSL_CTX *ssl_ctx;

    if (argc != 2) {
        Jim_WrongNumArgs(interp, 1, argv, "dir");
................................................................................
        return JIM_ERR;

#if defined(JIM_SSL)
    Jim_CreateCommand(interp, "load_ssl_certs", JimAioLoadSSLCertsCommand, NULL, NULL);
#endif

    Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
#ifndef JIM_ANSIC
    Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
#endif




    /* Create filehandles for stdin, stdout and stderr */
    JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
    JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
    JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");

    return JIM_OK;
}







>










>
>
>







 







>
>
>
>
>
>



|







 







|







 







>
>
>
>
>
>
>
>
>
>







 







|







 







|







 







|







 







|







 







>
>
>
>
>
>
>







 







|







 







>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<







 







|
>







 







|
|







 







|
|







>







 







<

>

>


<




|













>

>







 







<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







 







<
<
<
<
|
|
|
<
<
<
<
<
<
<
<
<
>











<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|


>
>
>








48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
..
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
...
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
...
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
...
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
...
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
....
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
....
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
....
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
....
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
....
1506
1507
1508
1509
1510
1511
1512









1513
1514
1515
1516
1517
1518
1519
....
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
....
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
....
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
....
1688
1689
1690
1691
1692
1693
1694

1695
1696
1697
1698
1699
1700

1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
....
1746
1747
1748
1749
1750
1751
1752

1753
1754
1755
1756
1757
1758
1759
....
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
....
2023
2024
2025
2026
2027
2028
2029




2030
2031
2032









2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044















































2045
2046
2047
2048
2049
2050
2051
....
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#include <sys/stat.h>
#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 <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#define HAVE_SOCKETS
#elif defined (__MINGW32__)
/* currently mingw32 doesn't support sockets, but has pipe, fdopen */
#else
#define JIM_ANSIC
#endif

#if defined(JIM_SSL)
#include <openssl/ssl.h>
#include <openssl/err.h>
................................................................................
#define IPV6 1
#else
#define IPV6 0
#ifndef PF_INET6
#define PF_INET6 0
#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(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
union sockaddr_any {
    struct sockaddr sa;
    struct sockaddr_in sin;
#if IPV6
    struct sockaddr_in6 sin6;
#endif
};
................................................................................
};
#endif /* JIM_BOOTSTRAP */

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(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
{
#if IPV6
    /*
     * An IPv6 addr/port looks like:
     *   [::1]
     *   [::1]:2000
................................................................................
    af = Jim_AioFile(interp, command);
    if (af == NULL) {
        return NULL;
    }

    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);
    jim_wide count = 0;
    jim_wide maxlen = JIM_WIDE_MAX;
    AioFile *outf = Jim_AioFile(interp, argv[0]);
................................................................................
#else
    Jim_SetResultInt(interp, 0);
#endif

    return JIM_OK;
}

#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);
    char *buf;
    union sockaddr_any sa;
    long len;
    socklen_t salen = sizeof(sa);
................................................................................
    Jim_SetResultInt(interp, feof(af->fp));
    return JIM_OK;
}

static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    if (argc == 3) {
#if defined(HAVE_SOCKETS) && defined(HAVE_SHUTDOWN)
        static const char * const options[] = { "r", "w", NULL };
        enum { OPT_R, OPT_W, };
        int option;
        AioFile *af = Jim_CmdPrivData(interp);

        if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
            return JIM_ERR;
................................................................................
        (void)fcntl(af->fd, F_SETFL, fmode);
    }
    Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
    return JIM_OK;
}
#endif

#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
#define SOCKOPT_BOOL 0
#define SOCKOPT_INT 1
#define SOCKOPT_TIMEVAL 2   /* not currently supported */

static const struct sockopt_def {
    const char *name;
    int level;
................................................................................
            return JIM_OK;
        }
    }
    /* Not found */
    Jim_SetResultFormatted(interp, "Unknown sockopt %#s", argv[0]);
    return JIM_ERR;
}
#endif /* JIM_BOOTSTRAP */

#ifdef HAVE_FSYNC
static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    AioFile *af = Jim_CmdPrivData(interp);

    fflush(af->fp);
................................................................................
    {   "copyto",
        "handle ?size?",
        aio_cmd_copy,
        1,
        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,
        0,
        1,
        /* Description: Read one line and return it or store it in the var */
    },
................................................................................
    {   "isatty",
        NULL,
        aio_cmd_isatty,
        0,
        0,
        /* Description: Is the file descriptor a tty? */
    },
#if defined(HAVE_SOCKETS) && !defined(JIM_BOOTSTRAP)
    {   "recvfrom",
        "len ?addrvar?",
        aio_cmd_recvfrom,
        1,
        2,
        /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
    },
................................................................................
    {   "listen",
        "backlog",
        aio_cmd_listen,
        1,
        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,
        aio_cmd_flush,
        0,
        0,
        /* Description: Flush the stream */
................................................................................
    {   "ndelay",
        "?0|1?",
        aio_cmd_ndelay,
        0,
        1,
        /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
    },









#endif
#ifdef HAVE_FSYNC
    {   "sync",
        NULL,
        aio_cmd_sync,
        0,
        0,
................................................................................
        "?exception-script?",
        aio_cmd_onexception,
        0,
        1,
        /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
    },
#endif
#if !defined(JIM_BOOTSTRAP)
#if defined(JIM_SSL)
    {   "ssl",
        "?-server cert priv?",
        aio_cmd_ssl,
        0,
        3,
        JIM_MODFLAG_FULLARGV
        /* Description: Wraps a stream socket with SSL/TLS and returns a new channel */
................................................................................
    {   "verify",
        NULL,
        aio_cmd_verify,
        0,
        0,
        /* Description: Verifies the certificate of a SSL/TLS channel */
    },
#endif
#if defined(HAVE_STRUCT_FLOCK)
    {   "lock",
        NULL,
        aio_cmd_lock,
        0,
        0,
        /* Description: Attempt to get a lock. */
    },
................................................................................
    {   "unlock",
        NULL,
        aio_cmd_unlock,
        0,
        0,
        /* Description: Relase a lock. */
    },
#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,
        0,
        -1,
        /* Description: Get or set tty settings - valid only on a tty */
    },
#endif
#endif /* JIM_BOOTSTRAP */
    { NULL }
};

static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
................................................................................
    if (!filename) {
        filename = Jim_NewStringObj(interp, buf, -1);
    }

    Jim_IncrRefCount(filename);

    if (fh == NULL) {

        if (fd >= 0) {
#ifndef JIM_ANSIC
            fh = fdopen(fd, mode);
#endif
        }
        else

            fh = fopen(Jim_String(filename), mode);

        if (fh == NULL) {
            JimAioSetError(interp, filename);
#ifndef JIM_ANSIC
            if (fd >= 0) {
                close(fd);
            }
#endif
            Jim_DecrRefCount(interp, filename);
            return NULL;
        }
    }

    /* Create the file command */
    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) {
        (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC);
    }
#endif
    af->openFlags = openFlags;
................................................................................
 */
static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
    const char *hdlfmt, int family, const char *mode[2])
{
    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);
            return JIM_OK;
        }
    }

................................................................................
    close(p[0]);
    close(p[1]);
    JimAioSetError(interp, NULL);
    return JIM_ERR;
}
#endif

#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)
{
    const char *hdlfmt = "aio.unknown%ld";
    const char *socktypes[] = {
        "unix",
        "unix.server",
................................................................................
                return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
            }
            break;
#endif

#if defined(HAVE_PIPE)
        case SOCK_STREAM_PIPE:




            if (argc != 2 || ipv6) {
                goto wrongargs;
            }









            return JimAioPipeCommand(interp, 1, &argv[1]);
#endif

        default:
            Jim_SetResultString(interp, "Unsupported socket type", -1);
            return JIM_ERR;
    }

    return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode) ? JIM_OK : JIM_ERR;
}
#endif /* JIM_BOOTSTRAP */
















































#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
static int JimAioLoadSSLCertsCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    SSL_CTX *ssl_ctx;

    if (argc != 2) {
        Jim_WrongNumArgs(interp, 1, argv, "dir");
................................................................................
        return JIM_ERR;

#if defined(JIM_SSL)
    Jim_CreateCommand(interp, "load_ssl_certs", JimAioLoadSSLCertsCommand, NULL, NULL);
#endif

    Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
#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");
    JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
    JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");

    return JIM_OK;
}

Changes to jim-exec.c.

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

147
148
149
150
151
152
153
154
155
156
157
158

159
160
161
162
163

164
165
166
167
168
169
170
171
172
173



174
175
176
177
178
179
180
...
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324




325
326
327
328
329
330
331
332
333
334
335

336
337
338
339

340
341
342
343

344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

















359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375

376
377
378
379

380
381
382
383
384
385
386
...
391
392
393
394
395
396
397

398
399

400
401
402
403
404
405
406

407
408
409






















410
411
412
413
414
415
416
417
418
419
420
421
422

423
424
425
426
427
428
429
...
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
...
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
...
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
...
505
506
507
508
509
510
511



























































512
513
514
515
516
517
518
...
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546


547
548

549
550

551
552
553
554

555
556

557
558
559
560

561













562
563



564
565
566


567
568


569
570


571
572
573
574
575
576
577
578
579
580

581
582
583
584
585
586
587
588

589



590


591
592

593
594
595
596
597



598
599
600
601
602
603

604
605
606
607
608
609
610
611
...
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
...
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
...
821
822
823
824
825
826
827
828
829
830
831




832


833
834
835
836
837

838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
...
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
....
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
....
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
....
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
....
1170
1171
1172
1173
1174
1175
1176

1177
1178
1179
1180
1181
1182
1183
....
1187
1188
1189
1190
1191
1192
1193

1194




1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
....
1520
1521
1522
1523
1524
1525
1526



1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
....
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583

1584
1585
1586

1587
1588
1589
1590
1591
1592
1593
1594
1595
1596

1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
....
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678

1679
1680
1681
1682
1683
1684
1685
    return JIM_OK;
}
#else
/* Full exec implementation for unix and mingw */

#include <errno.h>
#include <signal.h>

#if defined(__MINGW32__)
    /* XXX: Should we use this implementation for cygwin too? msvc? */
    #ifndef STRICT
    #define STRICT
    #endif
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    #include <fcntl.h>

    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 <unistd.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <sys/stat.h>

    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

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);
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 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 the last character of 'objPtr' is a newline, then remove
 * the newline character.
 */
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)
{
    char buf[256];
    FILE *fh = JimFdOpenForRead(fd);
    int ret = 0;

    if (fh == NULL) {
        return -1;
    }

    while (1) {
................................................................................
static void JimFreeEnv(char **env, char **original_environ)
{
    if (env != original_environ) {
        Jim_Free(env);
    }
}

#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)
{
    Jim_Obj *errorCode;

    if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) {
        return JIM_OK;




    }
    errorCode = Jim_NewListObj(interp, NULL, 0);

    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)));
    }
    else {
        const char *type;
        const char *action;


        if (WIFSIGNALED(waitStatus)) {
            type = "CHILDKILLED";
            action = "killed";

        }
        else {
            type = "CHILDSUSP";
            action = "suspended";

        }

        Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1));

        if (errStrObj) {
            /* Append the message to 'errStrObj' with a newline.
             * The last newline will be stripped later
             */
            Jim_AppendStrings(interp, errStrObj, "child ", action, " by signal ", Jim_SignalId(WTERMSIG(waitStatus)), "\n", NULL);
        }

        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_SetGlobalVariableStr(interp, "errorCode", errorCode);

    return JIM_ERR;
}

/*
 * Data structures of the following type are used by JimFork and
 * JimWaitPids to keep track of child processes.
 */

struct WaitInfo
{
    pidtype pid;                /* Process id of child. */
    int status;                 /* Status returned when child exited or suspended. */
    int flags;                  /* Various flag bits;  see below for definitions. */
};


struct WaitInfoTable {
    struct WaitInfo *info;      /* Table of outstanding processes */
    int size;                   /* Size of the allocated table */
    int used;                   /* Number of entries in use */

};

/*
 * Flag bits in WaitInfo structures:
 *
 * WI_DETACHED -        Non-zero means no-one cares about the
 *                      process anymore.  Ignore it until it
................................................................................

#define WAIT_TABLE_GROW_BY 4

static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData)
{
    struct WaitInfoTable *table = privData;


    Jim_Free(table->info);
    Jim_Free(table);

}

static struct WaitInfoTable *JimAllocWaitInfoTable(void)
{
    struct WaitInfoTable *table = Jim_Alloc(sizeof(*table));
    table->info = NULL;
    table->size = table->used = 0;


    return table;
}























/*
 * 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. */
    pidtype *pidPtr;
    int numPids, result;
    int child_siginfo = 1;
    Jim_Obj *childErrObj;
    Jim_Obj *errStrObj;


    /*
     * See if the command is to be run in the background; if so, create
     * the command, detach it, and return.
     */
    if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
        Jim_Obj *listObj;
................................................................................
        }
        /* The return value is a list of the pids */
        listObj = Jim_NewListObj(interp, NULL, 0);
        for (i = 0; i < numPids; i++) {
            Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i]));
        }
        Jim_SetResult(interp, listObj);
        JimDetachPids(interp, numPids, pidPtr);
        Jim_Free(pidPtr);
        return JIM_OK;
    }

    /*
     * Create the command's pipeline.
     */
................................................................................
    }

    result = JIM_OK;

    errStrObj = Jim_NewStringObj(interp, "", 0);

    /* Read from the output pipe until EOF */
    if (outputId != JIM_BAD_FD) {
        if (JimAppendStreamToString(interp, outputId, errStrObj) < 0) {
            result = JIM_ERR;
            Jim_SetResultErrno(interp, "error reading from output pipe");
        }
    }

    /* Now wait for children to finish. Any abnormal results are appended to childErrObj */
................................................................................

    /*
     * Read the child's error output (if any) and put it into the result.
     *
     * Note that unlike Tcl, the presence of stderr output does not cause
     * exec to return an error.
     */
    if (errorId != JIM_BAD_FD) {
        int ret;
        JimRewindFd(errorId);
        ret = JimAppendStreamToString(interp, errorId, errStrObj);
        if (ret < 0) {
            Jim_SetResultErrno(interp, "error reading from error pipe");
            result = JIM_ERR;
        }
        else if (ret > 0) {
            /* Got some error output, so discard the abnormal info string */
................................................................................
    Jim_RemoveTrailingNewline(errStrObj);

    /* Set this as the result */
    Jim_SetResult(interp, errStrObj);

    return result;
}




























































static void JimReapDetachedPids(struct WaitInfoTable *table)
{
    struct WaitInfo *waitPtr;
    int count;
    int dest;

................................................................................
    }

    waitPtr = table->info;
    dest = 0;
    for (count = table->used; count > 0; waitPtr++, count--) {
        if (waitPtr->flags & WI_DETACHED) {
            int status;
            pidtype pid = JimWaitPid(waitPtr->pid, &status, WNOHANG);
            if (pid == waitPtr->pid) {
                /* Process has exited, so remove it from the table */
                table->used--;
                continue;
            }
        }
        if (waitPtr != &table->info[dest]) {
            table->info[dest] = *waitPtr;
        }
        dest++;
    }
}

/**
 * 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)
{

    int i;

    /* 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);

            /* Remove it from the table */
            if (i != table->used - 1) {

                table->info[i] = table->info[table->used - 1];













            }
            table->used--;



            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(Jim_Interp *interp, int numPids, const pidtype *pidPtr)
{
    int j;
    struct WaitInfoTable *table = Jim_CmdPrivData(interp);


    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;

            }



        }


    }
}


static FILE *JimGetAioFilehandle(Jim_Interp *interp, const char *name)
{
    FILE *fh;
    Jim_Obj *fhObj;




    fhObj = Jim_NewStringObj(interp, name, -1);
    Jim_IncrRefCount(fhObj);
    fh = Jim_AioFilehandle(interp, fhObj);
    Jim_DecrRefCount(interp, fhObj);


    return fh;
}

/*
 *----------------------------------------------------------------------
 *
 * JimCreatePipeline --
 *
................................................................................
 * Side effects:
 *  Processes and pipes are created.
 *
 *----------------------------------------------------------------------
 */
static int
JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr,
    fdtype *inPipePtr, fdtype *outPipePtr, fdtype *errFilePtr)
{
    pidtype *pidPtr = NULL;         /* Points to malloc-ed array holding all
                                 * the pids of child processes. */
    int numPids = 0;            /* Actual number of processes that exist
                                 * at *pidPtr right now. */
    int cmdCount;               /* Count of number of distinct commands
                                 * found in argc/argv. */
................................................................................
                                 * 2 means error is filehandle name.
                                 * All this is ignored if error is NULL
                                 */
    const char *output = NULL;  /* Holds name of output file to pipe to,
                                 * 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;
                                 /* Readable file id input to current command in
                                 * pipeline (could be file or pipe).  JIM_BAD_FD
                                 * means use stdin. */
    fdtype outputId = JIM_BAD_FD;
                                 /* 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;
                                 /* Writable file id for all standard error
                                 * output from all commands in pipeline.  JIM_BAD_FD
                                 * means use stderr. */
    fdtype lastOutputId = JIM_BAD_FD;
                                 /* 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 firstArg, lastArg;      /* Indexes of first and last arguments in
                                 * current command. */
    int lastBar;
    int i;
    pidtype pid;
    char **save_environ;
    struct WaitInfoTable *table = Jim_CmdPrivData(interp);

    /* Holds the args which will be used to exec */
    char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1));
    int arg_count = 0;

    JimReapDetachedPids(table);

    if (inPipePtr != NULL) {
        *inPipePtr = JIM_BAD_FD;
    }
    if (outPipePtr != NULL) {
        *outPipePtr = JIM_BAD_FD;
    }
    if (errFilePtr != NULL) {
        *errFilePtr = JIM_BAD_FD;
    }
    pipeIds[0] = pipeIds[1] = JIM_BAD_FD;

    /*
     * First, scan through all the arguments to figure out the structure
     * of the pipeline.  Count the number of distinct processes (it's the
     * number of "|" arguments).  If there are "<", "<<", or ">" arguments
     * then make note of input and output redirection and remove these
     * arguments and the arguments that follow them.
................................................................................
     */
    if (input != NULL) {
        if (inputFile == FILE_TEXT) {
            /*
             * Immediate data in command.  Create temporary file and
             * put data into file.
             */
            inputId = JimCreateTemp(interp, input, input_len);
            if (inputId == JIM_BAD_FD) {
                goto error;
            }




        }


        else if (inputFile == FILE_HANDLE) {
            /* Should be a file descriptor */
            FILE *fh = JimGetAioFilehandle(interp, input);

            if (fh == NULL) {

                goto error;
            }
            inputId = JimDupFd(JimFileno(fh));
        }
        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());
                goto error;
            }
        }
    }
    else if (inPipePtr != NULL) {
        if (JimPipe(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;
    }

    /*
     * Set up the redirected output sink for the pipeline from one
     * of two places, if requested.
     */
    if (output != NULL) {
        if (outputFile == FILE_HANDLE) {
            FILE *fh = JimGetAioFilehandle(interp, output);
            if (fh == NULL) {
                goto error;
            }
            fflush(fh);
            lastOutputId = JimDupFd(JimFileno(fh));
        }
        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());
                goto error;
            }
        }
    }
    else if (outPipePtr != NULL) {
        /*
         * Output is to go to a pipe.
         */
        if (JimPipe(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;
    }
    /* 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);
                }
                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) {
                    goto error;
                }
                fflush(fh);
                errorId = JimDupFd(JimFileno(fh));
            }
        }
        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());
                goto error;
            }
        }
    }
    else if (errFilePtr != NULL) {
        /*
         * Set up the standard error output sink for the pipeline, if
         * requested.  Use a temporary file which is opened, then deleted.
         * Could potentially just use pipe, but if it filled up it could
         * cause the pipeline to deadlock:  we'd be waiting for processes
         * 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) {
            goto error;
        }
        *errFilePtr = JimDupFd(errorId);
    }

    /*
     * Scan through the argc array, forking off a process for each
     * group of arguments between "|" arguments.
     */

    pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr));
    for (i = 0; i < numPids; i++) {
        pidPtr[i] = JIM_BAD_PID;
    }
    for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
        int pipe_dup_err = 0;
        fdtype origErrorId = errorId;

        for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
            if (strcmp(arg_array[lastArg], "|") == 0) {
                break;
            }
            if (strcmp(arg_array[lastArg], "|&") == 0) {
                pipe_dup_err = 1;
................................................................................

        /* Replace | with NULL for execv() */
        arg_array[lastArg] = NULL;
        if (lastArg == arg_count) {
            outputId = lastOutputId;
        }
        else {
            if (JimPipe(pipeIds) != 0) {
                Jim_SetResultErrno(interp, "couldn't create pipe");
                goto error;
            }
            outputId = pipeIds[1];
        }

        /* Need to do this befor vfork() */
        if (pipe_dup_err) {
            errorId = outputId;
        }

        /* Now fork the child */

#ifdef __MINGW32__
................................................................................
        pid = vfork();
        if (pid < 0) {
            Jim_SetResultErrno(interp, "couldn't fork child process");
            goto error;
        }
        if (pid == 0) {
            /* Child */

            if (inputId != -1) dup2(inputId, 0);
            if (outputId != -1) dup2(outputId, 1);
            if (errorId != -1) dup2(errorId, 2);

            for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId); i++) {
                close(i);
            }

            /* Restore SIGPIPE behaviour */
            (void)signal(SIGPIPE, SIG_DFL);

            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
            {
                /* Keep valgrind happy */
                static char *const false_argv[2] = {"false", NULL};
                execvp(false_argv[0],false_argv);
            }
................................................................................
        errorId = origErrorId;

        /*
         * Close off our copies of file descriptors that were set up for
         * this child, then set up the input for the next child.
         */

        if (inputId != JIM_BAD_FD) {
            JimCloseFd(inputId);
        }
        if (outputId != JIM_BAD_FD) {
            JimCloseFd(outputId);
            outputId = JIM_BAD_FD;
        }
        inputId = pipeIds[0];
        pipeIds[0] = pipeIds[1] = JIM_BAD_FD;
    }
    *pidArrayPtr = pidPtr;

    /*
     * All done.  Cleanup open files lying around and then return.
     */

  cleanup:
    if (inputId != JIM_BAD_FD) {
        JimCloseFd(inputId);
    }
    if (lastOutputId != JIM_BAD_FD) {
        JimCloseFd(lastOutputId);
    }
    if (errorId != JIM_BAD_FD) {
        JimCloseFd(errorId);
    }
    Jim_Free(arg_array);

    JimRestoreEnv(save_environ);

    return numPids;

................................................................................
    /*
     * An error occurred.  There could have been extra files open, such
     * as pipes between children.  Clean them all up.  Detach any child
     * processes that have been created.
     */

  error:
    if ((inPipePtr != NULL) && (*inPipePtr != JIM_BAD_FD)) {
        JimCloseFd(*inPipePtr);
        *inPipePtr = JIM_BAD_FD;
    }
    if ((outPipePtr != NULL) && (*outPipePtr != JIM_BAD_FD)) {
        JimCloseFd(*outPipePtr);
        *outPipePtr = JIM_BAD_FD;
    }
    if ((errFilePtr != NULL) && (*errFilePtr != JIM_BAD_FD)) {
        JimCloseFd(*errFilePtr);
        *errFilePtr = JIM_BAD_FD;
    }
    if (pipeIds[0] != JIM_BAD_FD) {
        JimCloseFd(pipeIds[0]);
    }
    if (pipeIds[1] != JIM_BAD_FD) {
        JimCloseFd(pipeIds[1]);
    }
    if (pidPtr != NULL) {
        for (i = 0; i < numPids; i++) {
            if (pidPtr[i] != JIM_BAD_PID) {
                JimDetachPids(interp, 1, &pidPtr[i]);
            }
        }
        Jim_Free(pidPtr);
    }
    numPids = -1;
    goto cleanup;
}
................................................................................
    Jim_Free(pidPtr);

    return result;
}

int Jim_execInit(Jim_Interp *interp)
{

    if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
        return JIM_ERR;

#ifdef SIGPIPE
    /*
     * Disable SIGPIPE signals:  if they were allowed, this process
     * might go away unexpectedly if children misbehave.  This code
................................................................................
     * By doing this in the init function, applications can override
     * this later. Note that child processes have SIGPIPE restored
     * to the default after vfork().
     */
    (void)signal(SIGPIPE, SIG_IGN);
#endif


    Jim_CreateCommand(interp, "exec", Jim_ExecCmd, JimAllocWaitInfoTable(), JimFreeWaitInfoTable);




    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])
{
    int i;
    static char extensions[][5] = {".exe", "", ".bat"};

    for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {
................................................................................
        if (quote) {
            Jim_AppendString(interp, strObj, "\"", 1);
        }
    }
    return strObj;
}




static pidtype
JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, fdtype inputId, fdtype outputId, fdtype errorId)
{
    STARTUPINFO startInfo;
    PROCESS_INFORMATION procInfo;
    HANDLE hProcess, h;
    char execPath[MAX_PATH];
    pidtype pid = JIM_BAD_PID;
    Jim_Obj *cmdLineObj;
    char *winenv;

    if (JimWinFindExecutable(argv[0], execPath) < 0) {
        return JIM_BAD_PID;
................................................................................
    startInfo.hStdError = INVALID_HANDLE_VALUE;

    /*
     * Duplicate all the handles which will be passed off as stdin, stdout
     * 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 (startInfo.hStdInput == JIM_BAD_FD) {
        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 (startInfo.hStdOutput == JIM_BAD_FD) {

        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);
    }
    if (startInfo.hStdError == JIM_BAD_FD) {
        goto end;
    }

    /* If env is NULL, use the original environment.
     * If env[0] is NULL, use an empty environment.
     * Otherwise use the environment starting at env[0]
     */
................................................................................
    WaitForInputIdle(procInfo.hProcess, 5000);
    CloseHandle(procInfo.hThread);

    pid = procInfo.hProcess;

    end:
    Jim_FreeNewObj(interp, cmdLineObj);
    if (startInfo.hStdInput != JIM_BAD_FD) {
        CloseHandle(startInfo.hStdInput);
    }
    if (startInfo.hStdOutput != JIM_BAD_FD) {
        CloseHandle(startInfo.hStdOutput);
    }
    if (startInfo.hStdError != JIM_BAD_FD) {
        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;
}


static char **JimOriginalEnviron(void)
{
    return Jim_GetEnviron();
}

static char **JimSaveEnv(char **env)







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
|

<
<
<
<
<
<
<
<
<
<
<
<
<
>

<
<
<
<
<
<




<
>
|

<
<
<
>

<
<
<
<
<
<
<
<
<
>
>
>







 







|


|







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|

|

<
<
>
>
>
>

<
<
|







>




>




>












|
<

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|





|
|









>




>







 







>
|
|
>







>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






|
|





>







 







|







 







|







 







|

|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|













|
|
<

<
>
>
|
<
>
|
<
>
|
|
|
<
>
|
<
>
|
|
|
<
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
|
|
|
>
>
|
<
>
>
|
|
>
>
|
<
<
<
<
<
<
|
<
<
>

<
<
<
<
<
<
<
>
|
>
>
>
|
>
>
|
|
>
|
<
<
<
<
>
>
>
|
<
<
<
<

>
|







 







|







 







|

|

|


|
|

|

|



|












<
<

|


|


|

|







 







|
|


>
>
>
>
|
>
>

<
|

<
>


|





|
|
|





|





|








|
|


<
|





|
|
|








|





|






|
|






|
|
|


<
|






|
|
|













|
|


|













|







 







|






|







 







<
|
|
|










<







 







|
|

|
|
|


|








|
|

|
|

|
|







 







|
|
|

|
|
|

|
|
|

|
|

|
|




|







 







>







 







>
|
>
>
>
>






<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







>
>
>

|



|







 







|
|
|
|
|
|
|
|
|



|
|
|
|
|
|
|
|
>



>
|
<
|
<
<
|
<
<
<
<
>
|
<
|







 







|


|


|




<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
>







89
90
91
92
93
94
95
































96
97


98
99













100
101






102
103
104
105

106
107
108



109
110









111
112
113
114
115
116
117
118
119
120
...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
...
228
229
230
231
232
233
234
























235
236
237
238


239
240
241
242
243


244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
...
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
...
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
...
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
...
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
...
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
...
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565

566

567
568
569

570
571

572
573
574
575

576
577

578
579
580
581

582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597

598
599
600
601
602
603
604
605
606

607
608
609
610
611
612
613






614


615
616







617
618
619
620
621
622
623
624
625
626
627
628




629
630
631
632




633
634
635
636
637
638
639
640
641
642
...
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
...
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733


734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
...
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868

869
870

871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906

907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949

950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
....
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
....
1039
1040
1041
1042
1043
1044
1045

1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058

1059
1060
1061
1062
1063
1064
1065
....
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
....
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
....
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
....
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236






















































































































































































































1237
1238
1239
1240
1241
1242
1243
....
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
....
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414

1415


1416




1417
1418

1419
1420
1421
1422
1423
1424
1425
1426
....
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469



1470

























1471
1472
1473
1474
1475
1476
1477
1478
    return JIM_OK;
}
#else
/* Full exec implementation for unix and mingw */

#include <errno.h>
#include <signal.h>
































#include "jim-signal.h"
#include "jimiocompat.h"


#include <sys/stat.h>














struct WaitInfoTable;







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, 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 int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv);










#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
 * the newline character.
 */
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, int fd, Jim_Obj *strObj)
{
    char buf[256];
    FILE *fh = fdopen(fd, "r");
    int ret = 0;

    if (fh == NULL) {
        return -1;
    }

    while (1) {
................................................................................
static void JimFreeEnv(char **env, char **original_environ)
{
    if (env != original_environ) {
        Jim_Free(env);
    }
}

























static Jim_Obj *JimMakeErrorCode(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj)
{
    Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0);



    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));
    }


    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)));
    }
    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));

        if (errStrObj) {
            /* Append the message to 'errStrObj' with a newline.
             * The last newline will be stripped later
             */
            Jim_AppendStrings(interp, errStrObj, "child ", action, " by signal ", Jim_SignalId(WTERMSIG(waitStatus)), "\n", NULL);
        }

        Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid));
        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", JimMakeErrorCode(interp, pid, waitStatus, errStrObj));

    return JIM_ERR;
}

/*
 * Data structures of the following type are used by exec and
 * wait to keep track of child processes.
 */

struct WaitInfo
{
    pidtype pid;                /* Process id of child. */
    int status;                 /* Status returned when child exited or suspended. */
    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 */
};

/*
 * Flag bits in WaitInfo structures:
 *
 * WI_DETACHED -        Non-zero means no-one cares about the
 *                      process anymore.  Ignore it until it
................................................................................

#define WAIT_TABLE_GROW_BY 4

static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData)
{
    struct WaitInfoTable *table = privData;

    if (--table->refcount == 0) {
        Jim_Free(table->info);
        Jim_Free(table);
    }
}

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)
{
    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
     * the command, detach it, and return.
     */
    if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) {
        Jim_Obj *listObj;
................................................................................
        }
        /* The return value is a list of the pids */
        listObj = Jim_NewListObj(interp, NULL, 0);
        for (i = 0; i < numPids; i++) {
            Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i]));
        }
        Jim_SetResult(interp, listObj);
        JimDetachPids(table, numPids, pidPtr);
        Jim_Free(pidPtr);
        return JIM_OK;
    }

    /*
     * Create the command's pipeline.
     */
................................................................................
    }

    result = JIM_OK;

    errStrObj = Jim_NewStringObj(interp, "", 0);

    /* Read from the output pipe until EOF */
    if (outputId != -1) {
        if (JimAppendStreamToString(interp, outputId, errStrObj) < 0) {
            result = JIM_ERR;
            Jim_SetResultErrno(interp, "error reading from output pipe");
        }
    }

    /* Now wait for children to finish. Any abnormal results are appended to childErrObj */
................................................................................

    /*
     * Read the child's error output (if any) and put it into the result.
     *
     * Note that unlike Tcl, the presence of stderr output does not cause
     * exec to return an error.
     */
    if (errorId != -1) {
        int ret;
        lseek(errorId, 0, SEEK_SET);
        ret = JimAppendStreamToString(interp, errorId, errStrObj);
        if (ret < 0) {
            Jim_SetResultErrno(interp, "error reading from error pipe");
            result = JIM_ERR;
        }
        else if (ret > 0) {
            /* Got some error output, so discard the abnormal info string */
................................................................................
    Jim_RemoveTrailingNewline(errStrObj);

    /* Set this as the result */
    Jim_SetResult(interp, errStrObj);

    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;
    int count;
    int dest;

................................................................................
    }

    waitPtr = table->info;
    dest = 0;
    for (count = table->used; count > 0; waitPtr++, count--) {
        if (waitPtr->flags & WI_DETACHED) {
            int status;
            pidtype pid = waitpid(waitPtr->pid, &status, WNOHANG);
            if (pid == waitPtr->pid) {
                /* Process has exited, so remove it from the table */
                table->used--;
                continue;
            }
        }
        if (waitPtr != &table->info[dest]) {
            table->info[dest] = *waitPtr;
        }
        dest++;
    }
}

/*
 * wait ?-nohang? ?pid?

 *

 * 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 <pid> <exit-status>}

 *
 * If the process terminated on a signal, returns:
 *
 *   {CHILDKILLED <pid> <signal>}

 *
 * Otherwise (core dump, stopped, continued, ...), returns:
 *
 *   {CHILDSUSP <pid> none}
 *
 * With no arguments, reaps any finished background processes started by exec ... &
 */
static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    struct WaitInfoTable *table = Jim_CmdPrivData(interp);
    int nohang = 0;
    pidtype pid;
    long pidarg;
    int status;
    Jim_Obj *errCodeObj;


    /* With no arguments, reap detached children */
    if (argc == 1) {
        JimReapDetachedPids(table);
        return JIM_OK;
    }

    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;
    }









    pid = waitpid((pidtype)pidarg, &status, nohang ? WNOHANG : 0);








    errCodeObj = JimMakeErrorCode(interp, pid, status, NULL);

    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 int Jim_PidCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{




    if (argc != 1) {
        Jim_WrongNumArgs(interp, 1, argv, "");
        return JIM_ERR;
    }





    Jim_SetResultInt(interp, (jim_wide)getpid());
    return JIM_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * JimCreatePipeline --
 *
................................................................................
 * Side effects:
 *  Processes and pipes are created.
 *
 *----------------------------------------------------------------------
 */
static int
JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr,
    int *inPipePtr, int *outPipePtr, int *errFilePtr)
{
    pidtype *pidPtr = NULL;         /* Points to malloc-ed array holding all
                                 * the pids of child processes. */
    int numPids = 0;            /* Actual number of processes that exist
                                 * at *pidPtr right now. */
    int cmdCount;               /* Count of number of distinct commands
                                 * found in argc/argv. */
................................................................................
                                 * 2 means error is filehandle name.
                                 * All this is ignored if error is NULL
                                 */
    const char *output = NULL;  /* Holds name of output file to pipe to,
                                 * 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. */
    int inputId = -1;
                                 /* Readable file id input to current command in
                                 * pipeline (could be file or pipe).  -1
                                 * means use stdin. */
    int outputId = -1;
                                 /* Writable file id for output from current
                                 * command in pipeline (could be file or pipe).
                                 * -1 means use stdout. */
    int errorId = -1;
                                 /* Writable file id for all standard error
                                 * output from all commands in pipeline.  -1
                                 * means use stderr. */
    int lastOutputId = -1;
                                 /* Write file id for output from last command
                                 * in pipeline (could be file or pipe).
                                 * -1 means use stdout. */
    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;
    int i;
    pidtype pid;
    char **save_environ;
    struct WaitInfoTable *table = Jim_CmdPrivData(interp);

    /* Holds the args which will be used to exec */
    char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1));
    int arg_count = 0;



    if (inPipePtr != NULL) {
        *inPipePtr = -1;
    }
    if (outPipePtr != NULL) {
        *outPipePtr = -1;
    }
    if (errFilePtr != NULL) {
        *errFilePtr = -1;
    }
    pipeIds[0] = pipeIds[1] = -1;

    /*
     * First, scan through all the arguments to figure out the structure
     * of the pipeline.  Count the number of distinct processes (it's the
     * number of "|" arguments).  If there are "<", "<<", or ">" arguments
     * then make note of input and output redirection and remove these
     * arguments and the arguments that follow them.
................................................................................
     */
    if (input != NULL) {
        if (inputFile == FILE_TEXT) {
            /*
             * Immediate data in command.  Create temporary file and
             * put data into file.
             */
            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) {

            int fd = JimGetChannelFd(interp, input);


            if (fd < 0) {
                goto error;
            }
            inputId = dup(fd);
        }
        else {
            /*
             * File redirection.  Just open the file.
             */
            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 (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] = -1;
    }

    /*
     * Set up the redirected output sink for the pipeline from one
     * of two places, if requested.
     */
    if (output != NULL) {
        if (outputFile == FILE_HANDLE) {
            int fd = JimGetChannelFd(interp, output);
            if (fd < 0) {
                goto error;
            }

            lastOutputId = dup(fd);
        }
        else {
            /*
             * Output is to go to a file.
             */
            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;
            }
        }
    }
    else if (outPipePtr != NULL) {
        /*
         * Output is to go to a pipe.
         */
        if (pipe(pipeIds) != 0) {
            Jim_SetResultErrno(interp, "couldn't create output pipe");
            goto error;
        }
        lastOutputId = pipeIds[1];
        *outPipePtr = pipeIds[0];
        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 != -1) {
                    errorId = dup(lastOutputId);
                }
                else {
                    /* No redirection of stdout, so just use 2>@stdout */
                    error = "stdout";
                }
            }
            if (errorId == -1) {
                int fd = JimGetChannelFd(interp, error);
                if (fd < 0) {
                    goto error;
                }

                errorId = dup(fd);
            }
        }
        else {
            /*
             * Output is to go to a file.
             */
            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;
            }
        }
    }
    else if (errFilePtr != NULL) {
        /*
         * Set up the standard error output sink for the pipeline, if
         * requested.  Use a temporary file which is opened, then deleted.
         * Could potentially just use pipe, but if it filled up it could
         * cause the pipeline to deadlock:  we'd be waiting for processes
         * to complete before reading stderr, and processes couldn't complete
         * because stderr was backed up.
         */
        errorId = Jim_MakeTempFile(interp, NULL, 1);
        if (errorId == -1) {
            goto error;
        }
        *errFilePtr = dup(errorId);
    }

    /*
     * Scan through the argc array, forking off a process for each
     * group of arguments between "|" arguments.
     */

    pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr));
    for (i = 0; i < numPids; i++) {
        pidPtr[i] = JIM_BAD_PID;
    }
    for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) {
        int pipe_dup_err = 0;
        int origErrorId = errorId;

        for (lastArg = firstArg; lastArg < arg_count; lastArg++) {
            if (strcmp(arg_array[lastArg], "|") == 0) {
                break;
            }
            if (strcmp(arg_array[lastArg], "|&") == 0) {
                pipe_dup_err = 1;
................................................................................

        /* Replace | with NULL for execv() */
        arg_array[lastArg] = NULL;
        if (lastArg == arg_count) {
            outputId = lastOutputId;
        }
        else {
            if (pipe(pipeIds) != 0) {
                Jim_SetResultErrno(interp, "couldn't create pipe");
                goto error;
            }
            outputId = pipeIds[1];
        }

        /* Need to do this before vfork() */
        if (pipe_dup_err) {
            errorId = outputId;
        }

        /* Now fork the child */

#ifdef __MINGW32__
................................................................................
        pid = vfork();
        if (pid < 0) {
            Jim_SetResultErrno(interp, "couldn't fork child process");
            goto error;
        }
        if (pid == 0) {
            /* Child */

            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);
            }

            /* Restore SIGPIPE behaviour */
            (void)signal(SIGPIPE, SIG_DFL);

            execvpe(arg_array[firstArg], &arg_array[firstArg], Jim_GetEnviron());


            fprintf(stderr, "couldn't exec \"%s\"\n", arg_array[firstArg]);
#ifdef JIM_MAINTAINER
            {
                /* Keep valgrind happy */
                static char *const false_argv[2] = {"false", NULL};
                execvp(false_argv[0],false_argv);
            }
................................................................................
        errorId = origErrorId;

        /*
         * Close off our copies of file descriptors that were set up for
         * this child, then set up the input for the next child.
         */

        if (inputId != -1) {
            close(inputId);
        }
        if (outputId != -1) {
            close(outputId);
            outputId = -1;
        }
        inputId = pipeIds[0];
        pipeIds[0] = pipeIds[1] = -1;
    }
    *pidArrayPtr = pidPtr;

    /*
     * All done.  Cleanup open files lying around and then return.
     */

  cleanup:
    if (inputId != -1) {
        close(inputId);
    }
    if (lastOutputId != -1) {
        close(lastOutputId);
    }
    if (errorId != -1) {
        close(errorId);
    }
    Jim_Free(arg_array);

    JimRestoreEnv(save_environ);

    return numPids;

................................................................................
    /*
     * An error occurred.  There could have been extra files open, such
     * as pipes between children.  Clean them all up.  Detach any child
     * processes that have been created.
     */

  error:
    if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
        close(*inPipePtr);
        *inPipePtr = -1;
    }
    if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
        close(*outPipePtr);
        *outPipePtr = -1;
    }
    if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
        close(*errFilePtr);
        *errFilePtr = -1;
    }
    if (pipeIds[0] != -1) {
        close(pipeIds[0]);
    }
    if (pipeIds[1] != -1) {
        close(pipeIds[1]);
    }
    if (pidPtr != NULL) {
        for (i = 0; i < numPids; i++) {
            if (pidPtr[i] != JIM_BAD_PID) {
                JimDetachPids(table, 1, &pidPtr[i]);
            }
        }
        Jim_Free(pidPtr);
    }
    numPids = -1;
    goto cleanup;
}
................................................................................
    Jim_Free(pidPtr);

    return result;
}

int Jim_execInit(Jim_Interp *interp)
{
    struct WaitInfoTable *waitinfo;
    if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG))
        return JIM_ERR;

#ifdef SIGPIPE
    /*
     * Disable SIGPIPE signals:  if they were allowed, this process
     * might go away unexpectedly if children misbehave.  This code
................................................................................
     * By doing this in the init function, applications can override
     * this later. Note that child processes have SIGPIPE restored
     * to the default after vfork().
     */
    (void)signal(SIGPIPE, SIG_IGN);
#endif

    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 int
JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH])
{
    int i;
    static char extensions[][5] = {".exe", "", ".bat"};

    for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) {
................................................................................
        if (quote) {
            Jim_AppendString(interp, strObj, "\"", 1);
        }
    }
    return strObj;
}

/**
 * Note that inputId, etc. are osf_handles.
 */
static pidtype
JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId)
{
    STARTUPINFO startInfo;
    PROCESS_INFORMATION procInfo;
    HANDLE hProcess;
    char execPath[MAX_PATH];
    pidtype pid = JIM_BAD_PID;
    Jim_Obj *cmdLineObj;
    char *winenv;

    if (JimWinFindExecutable(argv[0], execPath) < 0) {
        return JIM_BAD_PID;
................................................................................
    startInfo.hStdError = INVALID_HANDLE_VALUE;

    /*
     * Duplicate all the handles which will be passed off as stdin, stdout
     * and stderr of the child process. The duplicate handles are set to
     * be inheritable, so the child process can use them.
     */
    /*
     * If stdin was not redirected, input should come from the parent's stdin
     */
    if (inputId == -1) {
        inputId = _fileno(stdin);
    }
    DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(inputId), hProcess, &startInfo.hStdInput,
            0, TRUE, DUPLICATE_SAME_ACCESS);
    if (startInfo.hStdInput == INVALID_HANDLE_VALUE) {
        goto end;
    }

    /*
     * If stdout was not redirected, output should go to the parent's stdout
     */
    if (outputId == -1) {
        outputId = _fileno(stdout);
    }
    DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(outputId), hProcess, &startInfo.hStdOutput,
            0, TRUE, DUPLICATE_SAME_ACCESS);
    if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) {
        goto end;
    }

    /* Ditto stderr */
    if (errorId == -1) {

        errorId = _fileno(stderr);


    }




    DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(errorId), hProcess, &startInfo.hStdError,
            0, TRUE, DUPLICATE_SAME_ACCESS);

    if (startInfo.hStdError == INVALID_HANDLE_VALUE) {
        goto end;
    }

    /* If env is NULL, use the original environment.
     * If env[0] is NULL, use an empty environment.
     * Otherwise use the environment starting at env[0]
     */
................................................................................
    WaitForInputIdle(procInfo.hProcess, 5000);
    CloseHandle(procInfo.hThread);

    pid = procInfo.hProcess;

    end:
    Jim_FreeNewObj(interp, cmdLineObj);
    if (startInfo.hStdInput != INVALID_HANDLE_VALUE) {
        CloseHandle(startInfo.hStdInput);
    }
    if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) {
        CloseHandle(startInfo.hStdOutput);
    }
    if (startInfo.hStdError != INVALID_HANDLE_VALUE) {
        CloseHandle(startInfo.hStdError);
    }
    return pid;
}





























#else

static char **JimOriginalEnviron(void)
{
    return Jim_GetEnviron();
}

static char **JimSaveEnv(char **env)

Changes to jim-file.c.

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
        argv++;
    }
    return JIM_OK;
}

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);

    if (fd < 0) {
        return JIM_ERR;
    }
    close(fd);

    return JIM_OK;







|







488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
        argv++;
    }
    return JIM_OK;
}

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, 0);

    if (fd < 0) {
        return JIM_ERR;
    }
    close(fd);

    return JIM_OK;

Added jim-nosignal.c.

























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <signal.h>

#include <jim-signal.h>
#include <jim.h>

/* 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;
}

Changes to jim-posix.c.

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
..
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
...
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#include "jimautoconf.h"
#include <jim.h>

#ifdef HAVE_SYS_SYSINFO_H
#include <sys/sysinfo.h>
................................................................................
        return JIM_ERR;
    }
    Jim_SetResultInt(interp, (jim_wide) pid);
    return JIM_OK;
}
#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 <error-description>}
 *
 * If the process exited normally, returns:
 *
 *   {<pid> exit <exit-status>}
 *
 * If the process terminated on a signal, returns:
 *
 *   {<pid> signal <signal-number>}
 *
 * Otherwise (core dump, stopped, continued, ...), returns:
 *
 *   {<pid> 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];

    if (argc != 1) {
        Jim_WrongNumArgs(interp, 1, argv, "");
        return JIM_ERR;
................................................................................
    Jim_SetResultInt(interp, info.uptime);
#else
    Jim_SetResultInt(interp, (long)time(NULL));
#endif
    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))
        return JIM_ERR;

#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;
}







<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







<
<
<
<
<
<
<
<
<
<
<








<



<


33
34
35
36
37
38
39

40
41
42
43
44
45
46
..
67
68
69
70
71
72
73


















































































74
75
76
77
78
79
80
...
131
132
133
134
135
136
137











138
139
140
141
142
143
144
145

146
147
148

149
150
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>

#include <errno.h>

#include "jimautoconf.h"
#include <jim.h>

#ifdef HAVE_SYS_SYSINFO_H
#include <sys/sysinfo.h>
................................................................................
        return JIM_ERR;
    }
    Jim_SetResultInt(interp, (jim_wide) pid);
    return JIM_OK;
}
#endif



















































































static int Jim_PosixGetidsCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
    Jim_Obj *objv[8];

    if (argc != 1) {
        Jim_WrongNumArgs(interp, 1, argv, "");
        return JIM_ERR;
................................................................................
    Jim_SetResultInt(interp, info.uptime);
#else
    Jim_SetResultInt(interp, (long)time(NULL));
#endif
    return JIM_OK;
}












int Jim_posixInit(Jim_Interp *interp)
{
    if (Jim_PackageProvide(interp, "posix", "1.0", JIM_ERRMSG))
        return JIM_ERR;

#ifdef HAVE_FORK
    Jim_CreateCommand(interp, "os.fork", Jim_PosixForkCommand, NULL, NULL);
#endif

    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);

    return JIM_OK;
}

Changes to jim-signal.c.

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
        if (siginfo[sig].name) {
            return siginfo[sig].name;
        }
    }
    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.
 * We accept -SIGINT, SIGINT, INT or any lowercase version or a number,
 * either positive or negative.
 */
static int find_signal_by_name(Jim_Interp *interp, const char *name)







<
<
<
<
<
<
<
<
<
<







124
125
126
127
128
129
130










131
132
133
134
135
136
137
        if (siginfo[sig].name) {
            return siginfo[sig].name;
        }
    }
    return "unknown signal";
}











/**
 * Given the name of a signal, returns the signal value if found,
 * or returns -1 (and sets an error) if not found.
 * We accept -SIGINT, SIGINT, INT or any lowercase version or a number,
 * either positive or negative.
 */
static int find_signal_by_name(Jim_Interp *interp, const char *name)

Changes to jim-signal.h.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

/**
 * Returns the canonical name for the given signal,
 * e.g. "SIGTERM", "SIGINT"
 */
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

#endif







<
<
<
<
<
<
<
<





7
8
9
10
11
12
13








14
15
16
17
18

/**
 * Returns the canonical name for the given signal,
 * e.g. "SIGTERM", "SIGINT"
 */
const char *Jim_SignalId(int sig);









#ifdef __cplusplus
}
#endif

#endif

Changes to jim-win32compat.h.

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
	char                *name;  /* null-terminated char string */
} DIR;

DIR *opendir(const char *name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);

#elif defined(__MINGW32__)

#include <stdlib.h>
#define strtod __strtod

#endif

#endif /* WIN32 */

#ifdef __cplusplus
}
#endif

#endif







<
<
<
<
<









63
64
65
66
67
68
69





70
71
72
73
74
75
76
77
78
	char                *name;  /* null-terminated char string */
} DIR;

DIR *opendir(const char *name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);






#endif

#endif /* WIN32 */

#ifdef __cplusplus
}
#endif

#endif

Changes to jim.h.

605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
JIM_EXPORT void Jim_Free (void *ptr);
JIM_EXPORT char * Jim_StrDup (const char *s);
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);

/* evaluation */
JIM_EXPORT int Jim_Eval(Jim_Interp *interp, const char *script);
/* in C code, you can do this and get better error messages */
/*   Jim_EvalSource( interp, __FILE__, __LINE__ , "some tcl commands"); */
JIM_EXPORT int Jim_EvalSource(Jim_Interp *interp, const char *filename, int lineno, const char *script);
/* Backwards compatibility */







|







605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
JIM_EXPORT void Jim_Free (void *ptr);
JIM_EXPORT char * Jim_StrDup (const char *s);
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, int unlink_file);

/* evaluation */
JIM_EXPORT int Jim_Eval(Jim_Interp *interp, const char *script);
/* in C code, you can do this and get better error messages */
/*   Jim_EvalSource( interp, __FILE__, __LINE__ , "some tcl commands"); */
JIM_EXPORT int Jim_EvalSource(Jim_Interp *interp, const char *filename, int lineno, const char *script);
/* Backwards compatibility */

Changes to jim_tcl.txt.

58
59
60
61
62
63
64




65
66
67
68
69
70
71
....
3399
3400
3401
3402
3403
3404
3405




3406
3407
3408
3409
3410
3411
3412
....
4455
4456
4457
4458
4459
4460
4461





























4462
4463
4464
4465
4466
4467
4468
....
4479
4480
4481
4482
4483
4484
4485
4486

4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
....
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
2. Add support for 'jimsh -'
3. Add hidden '-commands' option to many commands
4. Add scriptable autocompletion support in interactive mode with `tcl::autocomplete`
5. Add `aio sockopt`
6. Add scriptable autocompletion support with `history completion`
7. Add support for `tree delete`
8. Add support for `defer` and '$jim::defer'





Changes between 0.76 and 0.77
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Add support for `aio sync`
2. Add SSL and TLS support in aio
3. Added `zlib`
4. Added support for boolean constants in `expr`
................................................................................
`puts` normally outputs a newline character after +'string'+,
but this feature may be suppressed by specifying the +-nonewline+
switch.

Output to files is buffered internally by Tcl; the `flush`
command may be used to force buffered characters to be output.





pwd
~~~
+*pwd*+

Returns the path name of the current working directory.

rand
................................................................................

'add2' is invoked with an argument giving the name of a variable,
and it adds two to the value of that variable.
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.






























while
~~~~~
+*while* 'test body'+

The +'while'+ command evaluates +'test'+ as an expression
(in the same way that `expr` evaluates its argument).
The value of the expression must be numeric; if it is non-zero
................................................................................
The `while` command always returns an empty string.

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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+*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 <error-description>} if the process does not exist or has already been waited for.

   {<pid> exit <exit-status>} if the process exited normally.

   {<pid> signal <signal-number>} if the process terminated on a signal.

   {<pid> other 0} otherwise (core dump, stopped, continued, etc.)

+*os.gethostname*+::
    Invokes 'gethostname(3)' and returns the result.

+*os.getids*+::
    Returns the various user/group ids for the current process.

    jim> os.getids
................................................................................
    the client socket will be unbound and 'sendto' must be used
    to indicated the destination.

+*socket ?-ipv6? dgram.server* 'addr:port'+::
    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}

+*socket pair*+::
    A socketpair (see socketpair(2)). Like `socket 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
address.

The returned value is channel and may generally be used with the various file I/O
commands (gets, puts, read, etc.), either as object-based syntax or Tcl-compatible syntax.







>
>
>
>







 







>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 








>

|
|



<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
<


|







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
....
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
....
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
....
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530














4531
4532
4533
4534
4535
4536
4537
....
4779
4780
4781
4782
4783
4784
4785
4786

4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
2. Add support for 'jimsh -'
3. Add hidden '-commands' option to many commands
4. Add scriptable autocompletion support in interactive mode with `tcl::autocomplete`
5. Add `aio sockopt`
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Add support for `aio sync`
2. Add SSL and TLS support in aio
3. Added `zlib`
4. Added support for boolean constants in `expr`
................................................................................
`puts` normally outputs a newline character after +'string'+,
but this feature may be suppressed by specifying the +-nonewline+
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*+

Returns the path name of the current working directory.

rand
................................................................................

'add2' is invoked with an argument giving the name of a variable,
and it adds two to the value of that variable.
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 <pid> <exit-status>}+ if the process exited normally.

+{CHILDKILLED <pid> <signal>}+ if the process terminated on a signal.

+{CHILDSUSP <pid> 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'+

The +'while'+ command evaluates +'test'+ as an expression
(in the same way that `expr` evaluates its argument).
The value of the expression must be numeric; if it is non-zero
................................................................................
The `while` command always returns an empty string.

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.gethostname, os.getids, os.uptime
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+*os.fork*+::
    Invokes 'fork(2)' and returns the result.















+*os.gethostname*+::
    Invokes 'gethostname(3)' and returns the result.

+*os.getids*+::
    Returns the various user/group ids for the current process.

    jim> os.getids
................................................................................
    the client socket will be unbound and 'sendto' must be used
    to indicated the destination.

+*socket ?-ipv6? dgram.server* 'addr:port'+::
    A UDP socket server.

+*socket pipe*+::
	A synonym for `pipe`


+*socket pair*+::
    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
address.

The returned value is channel and may generally be used with the various file I/O
commands (gets, puts, read, etc.), either as object-based syntax or Tcl-compatible syntax.

Added jimiocompat.c.













































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#include <string.h>
#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 <sys/stat.h>

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

Added jimiocompat.h.

































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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 <stdio.h>
#include <errno.h>

#include "jimautoconf.h"
#include <jim.h>
#include <jim-win32compat.h>

/**
 * 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 <windows.h>
    #include <fcntl.h>
    #include <io.h>
    #include <process.h>

    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 <unistd.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <sys/stat.h>

    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

Changes to make-bootstrap-jim.

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
{
        sed -e '/#include.*jim/d' -e '/#include.*utf8/d' \
                -e '/^#.*if.*JIM_BOOTSTRAP/,/^#endif.*JIM_BOOTSTRAP/d' \
                -e 's/[ 	]*\/\*.*\*\///' -e '/^[ 	]*\/\*/,/\*\//d' $1
}

# 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
	outputsource $i
done

# Now extension source code
for i in $tclexts; do
	makeext $i.tcl
done
for i in $cexts; do
	outputsource jim-$i.c
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
	outputsource $i
done
echo "#ifndef JIM_BOOTSTRAP_LIB_ONLY"
outputsource jim-interactive.c
outputsource jimsh.c
echo "#endif"







|













|






111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
{
        sed -e '/#include.*jim/d' -e '/#include.*utf8/d' \
                -e '/^#.*if.*JIM_BOOTSTRAP/,/^#endif.*JIM_BOOTSTRAP/d' \
                -e 's/[ 	]*\/\*.*\*\///' -e '/^[ 	]*\/\*/,/\*\//d' $1
}

# 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 jim-signal.h jimiocompat.h; do
	outputsource $i
done

# Now extension source code
for i in $tclexts; do
	makeext $i.tcl
done
for i in $cexts; do
	outputsource jim-$i.c
done
makeloadexts $allexts

# And finally the core source code
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"
outputsource jim-interactive.c
outputsource jimsh.c
echo "#endif"

Changes to tclcompat.tcl.

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
132
133
134
135
136
137
138



139
140
141
142

143











144
145
146
147
148
149
150
151
		catch {$in close}
	}
}

# '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
	try {
		if {[string match "w*" $mode]} {
			lappend cmd <@$r &
			set pids [exec {*}$cmd]
			$r close
			set f $w
		} else {
................................................................................
			set pids [exec {*}$cmd]
			$w close
			set f $r
		}
		lambda {cmd args} {f pids} {
			if {$cmd eq "pid"} {
				return $pids



			}
			if {$cmd eq "close"} {
				$f close
				# And wait for the child processes to complete

				foreach p $pids { os.wait $p }











				return
			}
			tailcall $f $cmd {*}$args
		}
	} on error {error opts} {
		$r close
		$w close
		error $error







|







 







>
>
>




>
|
>
>
>
>
>
>
>
>
>
>
>
|







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
...
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
		catch {$in close}
	}
}

# '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 [pipe] r w
	try {
		if {[string match "w*" $mode]} {
			lappend cmd <@$r &
			set pids [exec {*}$cmd]
			$r close
			set f $w
		} else {
................................................................................
			set pids [exec {*}$cmd]
			$w close
			set f $r
		}
		lambda {cmd args} {f pids} {
			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
				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
		}
	} on error {error opts} {
		$r close
		$w close
		error $error

Changes to tests/exec.test.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43
44
45
46
...
411
412
413
414
415
416
417























418
419
420
421
#
# RCS: @(#) $Id: exec.test,v 1.8.2.1 2001/10/17 19:29:25 das Exp $

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)}]
    }
}

set f [open sleepx w]
puts $f "#![info nameofexecutable]"
puts $f {
    set seconds [lindex $argv 0]
    after [expr {int($seconds * 1000)}]

}
close $f
#catch {exec chmod +x sleepx}
set sleepx [list [info nameofexecutable] sleepx]

# Basic operations.

test exec-1.1 {basic exec operation} {
    exec echo a b c
} "a b c"
test exec-1.2 {pipelining} {
................................................................................
    close $f
    exec cat gorp.file
} -cleanup {
    file delete gorp.file
} -result {First line
Second line
Third line}
























file delete sleepx

testreport







<






|




<

<
<
>



|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




13
14
15
16
17
18
19

20
21
22
23
24
25
26
27
28
29
30

31


32
33
34
35
36
37
38
39
40
41
42
43
...
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
#
# RCS: @(#) $Id: exec.test,v 1.8.2.1 2001/10/17 19:29:25 das Exp $

source [file dirname [info script]]/testing.tcl

needs cmd exec
needs cmd flush


testConstraint unix [expr {$tcl_platform(platform) eq {unix}}]

# Sleep which supports fractions of a second
if {[info commands sleep] eq {}} {
    proc sleep {n} {
	exec {*}$::sleepx $n
    }
}

set f [open sleepx w]

puts $f {


    sleep "$@"
}
close $f
#catch {exec chmod +x sleepx}
set sleepx [list sh sleepx]

# Basic operations.

test exec-1.1 {basic exec operation} {
    exec echo a b c
} "a b c"
test exec-1.2 {pipelining} {
................................................................................
    close $f
    exec cat gorp.file
} -cleanup {
    file delete gorp.file
} -result {First line
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

Changes to tests/exec2.test.

39
40
41
42
43
44
45
46


























47

test exec2-2.4 "Remove all env var" {
	unset -nocomplain env
	exec printenv | sed -n -e /^TESTENV2=/p
} {}

array set env [array get saveenv]



























testreport








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

test exec2-2.4 "Remove all env var" {
	unset -nocomplain env
	exec printenv | sed -n -e /^TESTENV2=/p
} {}

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

Changes to tests/pid.test.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

source [file dirname [info script]]/testing.tcl

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 getpid [expr {[catch pid] == 0}]
# This is a proxy for tcl || tclcompat
testConstraint pidchan [expr {[info commands fconfigure] ne {}}]

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} {
    set f [open {| echo foo | cat >test1} w]
    set pids [pid $f]
    close $f
    catch {removeFile test1}
    list [llength $pids] [regexp {^[0-9]+$} [lindex $pids 0]] \
       [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} {
    set f [open test1 w]
    set pids [pid $f]
    close $f
    set pids
} {}
test pid-1.4 {pid command} pidchan {
    list [catch {pid a b} msg] $msg







|









|








|







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

source [file dirname [info script]]/testing.tcl

needs cmd pid posix
needs cmd exec
catch {package require regexp}
testConstraint regexp [expr {[info commands regexp] 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 {}}]

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 pipe pidchan} {
    set f [open {| echo foo | cat >test1} w]
    set pids [pid $f]
    close $f
    catch {removeFile test1}
    list [llength $pids] [regexp {^[0-9]+$} [lindex $pids 0]] \
       [regexp {^[0-9]+$} [lindex $pids 1]] \
       [expr {[lindex $pids 0] == [lindex $pids 1]}]
} {2 1 1 0}
test pid-1.3 {pid command} {pipe pidchan} {
    set f [open test1 w]
    set pids [pid $f]
    close $f
    set pids
} {}
test pid-1.4 {pid command} pidchan {
    list [catch {pid a b} msg] $msg