ldmud-3.2.9/doc/
ldmud-3.2.9/doc/efun/
ldmud-3.2.9/mud/
ldmud-3.2.9/mud/heaven7/
ldmud-3.2.9/mud/heaven7/lib/
ldmud-3.2.9/mud/lp-245/
ldmud-3.2.9/mud/lp-245/banish/
ldmud-3.2.9/mud/lp-245/doc/
ldmud-3.2.9/mud/lp-245/doc/examples/
ldmud-3.2.9/mud/lp-245/doc/sefun/
ldmud-3.2.9/mud/lp-245/log/
ldmud-3.2.9/mud/lp-245/obj/Go/
ldmud-3.2.9/mud/lp-245/players/lars/
ldmud-3.2.9/mud/lp-245/room/death/
ldmud-3.2.9/mud/lp-245/room/maze1/
ldmud-3.2.9/mud/lp-245/room/sub/
ldmud-3.2.9/mud/lp-245/secure/
ldmud-3.2.9/mud/morgengrauen/
ldmud-3.2.9/mud/morgengrauen/lib/
ldmud-3.2.9/mud/sticklib/
ldmud-3.2.9/mud/sticklib/src/
ldmud-3.2.9/mudlib/uni-crasher/
ldmud-3.2.9/pkg/
ldmud-3.2.9/pkg/debugger/
ldmud-3.2.9/pkg/diff/
ldmud-3.2.9/pkg/misc/
ldmud-3.2.9/src/autoconf/
ldmud-3.2.9/src/bugs/
ldmud-3.2.9/src/bugs/MudCompress/
ldmud-3.2.9/src/bugs/b-020916-files/
ldmud-3.2.9/src/bugs/doomdark/
ldmud-3.2.9/src/bugs/ferrycode/ferry/
ldmud-3.2.9/src/bugs/ferrycode/obj/
ldmud-3.2.9/src/bugs/psql/
ldmud-3.2.9/src/done/
ldmud-3.2.9/src/done/order_alist/
ldmud-3.2.9/src/done/order_alist/obj/
ldmud-3.2.9/src/done/order_alist/room/
ldmud-3.2.9/src/gcc/
ldmud-3.2.9/src/gcc/2.7.0/
ldmud-3.2.9/src/gcc/2.7.1/
ldmud-3.2.9/src/hosts/
ldmud-3.2.9/src/hosts/GnuWin32/
ldmud-3.2.9/src/hosts/amiga/NetIncl/
ldmud-3.2.9/src/hosts/amiga/NetIncl/netinet/
ldmud-3.2.9/src/hosts/amiga/NetIncl/sys/
ldmud-3.2.9/src/hosts/i386/
ldmud-3.2.9/src/hosts/msdos/byacc/
ldmud-3.2.9/src/hosts/msdos/doc/
ldmud-3.2.9/src/hosts/os2/
ldmud-3.2.9/src/hosts/win32/
ldmud-3.2.9/src/util/
ldmud-3.2.9/src/util/erq/
ldmud-3.2.9/src/util/indent/hosts/next/
ldmud-3.2.9/src/util/xerq/
ldmud-3.2.9/src/util/xerq/lpc/
ldmud-3.2.9/src/util/xerq/lpc/www/
/*---------------------------------------------------------------------------
 * XErq - Command Execution
 * (C) Copyright 1995 by Brian Gerst.
 * (C) Copyright 2001 by Brian Gerst, Frank Kirschner, Lars Duening.
 *---------------------------------------------------------------------------
 * The functions here implement the subcommand features of the ERQ.
 *
 * ERQ_EXECUTE and _SPAWN commands are controlled with a child_t structure.
 * _SPAWN commands are additionally given two sockets so that the driver
 * can communicate with them.
 *---------------------------------------------------------------------------
 */

#include "defs.h"

/*-------------------------------------------------------------------------*/
static int
execute (char *buf, int buflen, char *status, int *esockets, int keep_sockets)

/* Execute the command <buf> (length <buflen> bytes) and return the pid
 * on success, and 0 on failure. *<status> is set to the ERQ_E_ code.
 *
 * If <esockets> is non-NULL, it points to an int[4] which is filled
 * with two socketpairs to communicate with the execute command:
 *   esocket[0]: stdin+stdout of the child, closed in the caller
 *   esocket[2]: stderr of the child, closed in the caller
 *   esocket[1]: callers socket to stdin+stdout of the child
 *   esocket[3]: callers socket to stderr of the child
 *
 * A second purpose of the socketpair is to synchronize the parent with
 * the child, so that the child runs after the parent completed the setup.
 *
 * If the sockets are used for just this purpose, <keep_sockets> is FALSE
 * and the sockets in the child are closed after synchronisation (the caller
 * parent has to close its copies, too). If <keep_sockets> is TRUE, the
 * child keeps the sockets and moves them over to the stdio/err channels.
 */

{
    char path[256], argbuf[1024], *p, *args[96], **argp, c;
    pid_t pid;
    int quoted;

    XPRINTF((stderr, "%s execute('%s':%d)\n", time_stamp(), buf, buflen));

    quoted = 0;
    status[1] = 0;
    if (buflen >= sizeof argbuf)
    {
        status[0] = ERQ_E_ARGLENGTH;
        return 0;
    }

    /* Split the commandline into words and store them in args[] */
    argp = &args[0];
    *argp++ = p = argbuf;
    while (--buflen >= 0)
    {
        c = *buf++;
        if (c == '\\')
        {
            if (--buflen >= 0) {
                *p++ = *buf++;
            }
            else
            {
                status[0] = ERQ_E_ARGFORMAT;
                return 0;
            }
        }
        else if (c == '"')
        {
            quoted = !quoted;
        }
        else if (isgraph(c) || quoted)
        {
            *p++ = c;
        }
        else
        {
            *p++ = '\0';
            *argp++ = p;
            XPRINTF((stderr, "%s   arg[%d]: '%s'\n"
                           , time_stamp(), argp - args - 1, argp[-1]));
            if (argp == &args[sizeof args/sizeof args[0]] - 1) {
                status[0] = ERQ_E_ARGNUMBER;
                return 0;
            }
            while( ( (c = *buf) == ' ' || c == '\t') && --buflen >= 0)
                buf++;
        }
    }
    *p++ = '\0';
    *argp++ = 0;

    /* Make sure the command to execute is legal (ie does not try
     * get out of its ERQ_DIR sandbox.
     */
    p = args[0];
    if (p[0] == '/' || strstr(p, ".."))
    {
        status[0] = ERQ_E_ILLEGAL;
        return 0;
    }
    if (strlen(erq_dir) + strlen(p) + 2 > sizeof(path))
    {
        status[0] = ERQ_E_PATHLEN;
        return 0;
    }
    sprintf(path, "%s/%s", erq_dir, p);

    /* Create the sockets to communicate with the caller */
    if (esockets)
    {
        if(socketpair(AF_UNIX, SOCK_STREAM, 0, esockets) < 0)
        {
            perror("socketpair");
            status[0] = ERQ_E_FORKFAIL;
            status[1] = errno;
            return 0;
        }

        if(socketpair(AF_UNIX, SOCK_STREAM, 0, esockets + 2) < 0)
        {
            perror("socketpair");
            close(esockets[0]);
            close(esockets[1]);
            status[0] = ERQ_E_FORKFAIL;
            status[1] = errno;
            return 0;
        }
    }

    pid = fork();
    if (!pid)
    {
        /* This is the child */

        close(0);
        close(1);

        if (esockets)
        {
            char ch, expected;
            int num;

            expected = getpid() & 0xff;
            XPRINTF((stderr, "%s Child process waits for go-ahead byte '%02x'.\n"
                           , time_stamp(), expected));

            do {
                num = read(esockets[0], &ch, sizeof(c));
            } while (num < 0 && errno == EINTR);

            if (num < 0)
            {
                int myerrno = errno;

                fprintf(stderr, "%s Child couldn't receive go-ahead byte, errno %d"
                              , time_stamp(), myerrno);
                errno = myerrno;
                perror(" ");
            }

            if (ch != expected)
            {
                fprintf(stderr, "%s Child received go-ahead byte '%02x', expected '%02x'\n"
                              , time_stamp(), ch, expected);
            }

            XPRINTF((stderr, "%s Child resumes.\n", time_stamp()));

            if (keep_sockets)
            {
                dup2(esockets[0], 0);
                dup2(esockets[0], 1);
                dup2(esockets[2], 2);
            }
            close(esockets[0]);
            close(esockets[1]);
            close(esockets[2]);
            close(esockets[3]);
        }

        execv(path, args);
        _exit(1);
    }

    /* This is the caller */

    XPRINTF((stderr, "%s   Forked process %d.\n", time_stamp(), pid));

    if (esockets)
    {
        /* Not our sockets */
        close(esockets[0]);
        close(esockets[2]);
    }

    if (pid < 0)
    {
        /* Error */
        if (esockets)
        {
            close(esockets[1]);
            close(esockets[3]);
        }
        status[0] = ERQ_E_FORKFAIL;
        status[1] = errno;
        return 0;
    }

    return pid;
} /* execute() */

/*-------------------------------------------------------------------------*/
static child_t *
new_child (void)

/* Create a new child_t structure, give it a ticket and add it to
 * the childs list.
 * Result is the new structure.
 */

{
    child_t *chp;

    chp = malloc(sizeof(struct child_s));
    XPRINTF((stderr, "%s New child %p.\n", time_stamp(), chp));
    chp->next = childs;
    chp->ticket.seq = (seq_number += seq_interval);
    chp->ticket.rnd = get_ticket();
    chp->fd = NULL;
    chp->err = NULL;
    chp->status = CHILD_RUNNING;
    chp->pid = 0;
    childs = chp;
    return chp;
} /* new_child() */

/*-------------------------------------------------------------------------*/
static void
free_child (struct child_s *chp)

/* Remove the child <chp> from the list of childs, if necessary, then
 * free the structure.
 */

{
    child_t **chpp;
    int found = 0;

    XPRINTF((stderr, "%s Freeing child %p.\n", time_stamp(), chp));
    for (chpp = &childs; *chpp; chpp=&(*chpp)->next)
    {
        if (*chpp != chp)
            continue;
        *chpp = chp->next;
        XPRINTF((stderr, "%s   Unlinking child %p.\n", time_stamp(), chp));
        found = 1;
        break;
    }
    if (!found)  /* child was removed already */
        fprintf(stderr, "%s Child %p to free not found.\n", time_stamp(), chp);
    else
        free(chp);
} /* free_child() */

/*-------------------------------------------------------------------------*/
void
remove_child (child_t *chp)

/* The child <chp> has exited. Get the exit status and send the proper
 * message to the driver, then clean up the child and remove it.
 */

{
    char mesg[2];
    child_t *chtmp;
    int found = 0;

    XPRINTF((stderr, "%s Removing child %p.\n", time_stamp(), chp));

    for (chtmp = childs; chtmp; chtmp = chtmp->next)
        if (chtmp == chp)
        {
            found = 1;
            break;
        }

    if (!found)  /* child was removed already */
    {
        XPRINTF((stderr, "%s   Child %p removed already.\n", time_stamp(), chp));
        return;
    }

    switch(chp->type)
    {
    case CHILD_FORK:
    case CHILD_EXECUTE:
      {
        mesg[0] = ERQ_OK;
        break;
      }

    case CHILD_SPAWN:
      {
        mesg[0] = ERQ_EXITED;
        if (chp->fd)
        {
            XPRINTF((stderr, "%s   Closing 'fd' socket %p\n", time_stamp(), chp->fd));
            if (!read_socket(chp->fd, 0))
                close_socket(chp->fd);
        }
        if (chp->err)
        {
            XPRINTF((stderr, "%s   Closing 'err' socket %p\n", time_stamp(), chp->err));
            if (!read_socket(chp->err, 0))
                close_socket(chp->err);
        }
        break;
      }
    }

    /* Send an exit message for non-forked children */
    if (chp->type != CHILD_FORK)
    {
        if (WIFEXITED(chp->return_code))
        {
            mesg[1] = WEXITSTATUS(chp->return_code);
        }
        else if (WIFSIGNALED(chp->return_code))
        {
            mesg[0] = ERQ_SIGNALED;
            mesg[1] = WTERMSIG(chp->return_code);
        }
        else
        {
            mesg[0] = ERQ_E_UNKNOWN;
        }
        XPRINTF((stderr, "%s   Sending exit message.\n", time_stamp()));
        reply1(chp->handle, mesg, 2);
    }

    free_child(chp);
} /* remove_child() */

/*-------------------------------------------------------------------------*/
void
erq_fork (char *mesg, int msglen)

/* ERQ_FORK: fire-and-(almost)forget a command.
 */

{
    char status[2];
    child_t *chp;
    pid_t pid;

    chp = new_child();
    chp->type = CHILD_FORK;
    chp->handle = 0;

    if (0 != (pid = execute(&mesg[9], msglen-9, status, NULL, 0)))
    {
        chp->pid = pid;
        status[0] = ERQ_OK;
    }
    reply1(get_handle(mesg), status, 2);
} /* erq_fork() */

/*-------------------------------------------------------------------------*/
void
erq_execute (char *mesg, int msglen)

/* ERQ_EXECUTE: start a command and keep an eye on its progress.
 */

{
    char status[2];
    int esockets[4];
    pid_t pid;
    child_t *chp;

    chp = new_child();
    chp->type = CHILD_EXECUTE;
    chp->handle = get_handle(mesg);

    if (0 != (pid = execute(&mesg[9], msglen-9, status, esockets, 0)))
    {
        char ga_data;
        int rc;

        /* The parent */
        chp->pid = pid;

        /* Send the GoAhead signal to the child process */
        ga_data = pid & 0xff;
        XPRINTF((stderr, "%s Sending go-ahead byte '%02x' to child.\n"
                       , time_stamp(), ga_data));
        fcntl(esockets[1], F_SETFD, 1);
        fcntl(esockets[1], F_SETFL, O_NONBLOCK);
        do {
            rc = write(esockets[1], &ga_data, sizeof(ga_data));
        } while (rc < 0 && errno == EINTR);

        if (rc < 0)
        {
            int myerrno = errno;

            fprintf(stderr, "%s Couldn't send go-ahead byte '%02x' to child %p, errno %d"
                          , time_stamp(), ga_data, chp, myerrno);
            errno = myerrno;
            perror(" ");
        }

        /* We don't need the sockets anymore */
        close(esockets[1]);
        close(esockets[3]);
        return;
    }

    free_child(chp); /* Don't need this in the child process */
    reply1(get_handle(mesg), status, 2);
} /* erq_execute() */

/*-------------------------------------------------------------------------*/
void
erq_spawn (char *mesg, int msglen)

/* ERQ_SPAWN: start a command and communicate with it.
 */

{
    int esockets[4];
    char status[2];
    pid_t pid;

    child_t *chp;

    /* Set up the child */
    chp = new_child();
    chp->type = CHILD_SPAWN;
    chp->handle = get_handle(mesg);

    if (0 != (pid = execute(&mesg[9], msglen-9, status, esockets, 1)))
    {
        char ga_data;
        int rc;

        /* The parent */
        chp->pid = pid;

        /* Make a socket for the stdio */
        chp->fd = new_socket(esockets[1], SOCKET_STDOUT);
        memcpy(&chp->fd->ticket, &chp->ticket, TICKET_SIZE);
        fcntl(esockets[1], F_SETFD, 1);
        fcntl(esockets[1], F_SETFL, O_NONBLOCK);
        chp->fd->handle = chp->handle;

        /* Make a socket for the stderr */
        chp->err = new_socket(esockets[3], SOCKET_STDERR);
        fcntl(esockets[3], F_SETFD, 1);
        fcntl(esockets[3], F_SETFL, O_NONBLOCK);
        chp->err->handle = chp->handle;

        /* Send the GoAhead signal to the child process */
        ga_data = pid & 0xff;
        XPRINTF((stderr, "%s Sending go-ahead byte '%02x' to child.\n"
                       , time_stamp(), ga_data));
        do {
            rc = write(esockets[1], &ga_data, sizeof(ga_data));
        } while (rc < 0 && errno == EINTR);

        if (rc < 0)
        {
            int myerrno = errno;

            fprintf(stderr, "%s Couldn't send go-ahead byte '%02x' to child %p, errno %d"
                          , time_stamp(), ga_data, chp, myerrno);
            errno = myerrno;
            perror(" ");
        }

        status[0] = ERQ_OK;
        replyn(chp->handle, 1, 2,
            status, 1,
            &chp->ticket, TICKET_SIZE);
        return;
    }

    free_child(chp); /* Don't need this in the child process */
    reply1(get_handle(mesg), status, 2);
} /* erq_spawn() */

/*-------------------------------------------------------------------------*/
void
erq_kill (char *mesg, int msglen)

/* ERQ_KILL: Send a signal to one of the children (default is SIGKILL).
 * If applied to a socket, the socket is closed.
 */

{
    ticket_t *ticket;
    child_t *chp;
    socket_t *sp;
    int sig;
    char status;

    /* Check the message */
    switch(msglen-TICKET_SIZE)
    {
    case 9:
        sig = SIGKILL;
        break;
    case 13:
        sig = read_32(mesg+9+TICKET_SIZE);
        break;
    default:
        bad_request(mesg);
        return;
    }

    ticket = (struct ticket_s *) (mesg+9);

    /* Find the command child by the ticket */
    for (chp = childs; chp; chp = chp->next)
        if (!memcmp(ticket, &chp->ticket, TICKET_SIZE))
            break;

    if (chp)
    {
        /* Found it - send the signal */
        XPRINTF((stderr, "%s Kill child %p (pid %d) with signal %d\n"
                       , time_stamp(), chp, chp->pid, sig));
        if (sig >= 0)
            sig = kill(chp->pid, sig);
        status = sig < 0 ? ERQ_E_ILLEGAL : ERQ_OK;
        reply1(get_handle(mesg), &status, 1);
        return;
    }

    /* Maybe its a socket? */
    for (sp = sockets; sp; sp = sp->next)
        if (!memcmp(ticket, &sp->ticket, TICKET_SIZE))
            break;

    if (sp)
    {
        /* Yup: close it */
        XPRINTF((stderr, "%s Close socket %p", time_stamp(), sp));
        close_socket(sp);
        status = ERQ_OK;
        reply1(get_handle(mesg), &status, 1);
        return;
    }

    status = ERQ_E_TICKET;
    reply1(get_handle(mesg), &status, 1);
} /* erq_kill() */

/***************************************************************************/