io.c   [plain text]


#pragma prototyped
/* Lefteris Koutsofios - AT&T Bell Laboratories */

#include "common.h"
#include "io.h"
#ifdef FEATURE_CS
#include "cs2l.h"
#endif
#include "mem.h"
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#ifndef HAVE_TERMIOS_H
#include <termio.h>
#else
#include <termios.h>
#endif
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef HAVEVFORK
#define FORK vfork
#else
#define FORK fork
#endif
#include <netdb.h>

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

io_t *iop;
int ion;

static char *shell;
static char *shbname;

static FILE *serverconnect (char *);
static void ptyopen (char *, FILE **, FILE **, int *);
static int findpty (int *);
static void pipeopen (char *, FILE **, FILE **, int *);
static void socketopen (char *, int, FILE **, FILE **, int *);

static void sigchldhandler (int);

void IOinit (void) {
    struct stat statbuf;
    int ioi;

    if (!(shell = getenv ("SHELL")))
        shell = "/bin/sh";
    if (shell[0] != '/' && shell[0] != '.') {
        if (!(shell = buildpath (shell, TRUE)))
            shell = "/bin/sh";
        else
            shell = strdup (shell);
    }
    shbname = shell + strlen (shell) - 1;
    while (shbname >= shell && *shbname != '/')
        shbname--;
    if (*shbname == '/')
        shbname++;
    ion = IOINCR;
    for (ioi = FD_SETSIZE - 1; ioi >= 0; ioi--) {
        if (fstat (ioi, &statbuf) == 0) {
           ion = (ioi / IOINCR + 1) * IOINCR;
           break;
        }
    }
    iop = Marrayalloc ((long) ion * IOSIZE);
    for (ioi = 0; ioi < ion; ioi++)
        iop[ioi].inuse = FALSE;
    for (ioi = 0; ioi < ion; ioi++) {
        if (fstat (ioi, &statbuf) == 0) {
            if ((iop[ioi].ifp = iop[ioi].ofp = fdopen (ioi,
                    (ioi == 0 ? "r" : "w")))) {
                iop[ioi].inuse = TRUE;
                iop[ioi].type = IO_FILE;
                iop[ioi].ismonitored = FALSE;
                iop[ioi].pid = -1;
                iop[ioi].buf = NULL;
            }
        }
    }
    signal (SIGCHLD, sigchldhandler);
    signal (SIGPIPE, SIG_IGN);
}

void IOterm (void) {
    int ioi;

    for (ioi = 3; ioi < ion; ioi++)
        if (iop[ioi].inuse)
            IOclose (ioi, NULL);
    Marrayfree (iop), iop = NULL, ion = 0;
}

int IOopen (char *kind, char *name, char *mode, char *fmt) {
    io_t *p;
    iotype_t type;
    char *path, *command;
    char hname[200];
    int sfd, slen;
    int i;
    struct sockaddr_in sname;

    if (Strcmp (kind, "file") == 0)
        type = IO_FILE;
    else if (Strcmp (kind, "pty") == 0)
        type = IO_PTY;
    else if (Strcmp (kind, "pipe") == 0)
        type = IO_PIPE;
    else if (Strcmp (kind, "socket") == 0)
        type = IO_SOCKET;
#ifdef FEATURE_CS
    else if (Strcmp (kind, "cs") == 0)
        type = IO_CS;
#endif
    else
        return -1;
    for (i = 0; i < ion; i++)
        if (!iop[i].inuse)
            break;
    if (i == ion) {
        iop = Marraygrow (iop, (long) (ion + IOINCR) * IOSIZE);
        for (i = ion + IOINCR - 1; i >= ion; i--)
            iop[i].inuse = FALSE;
        i++, ion += IOINCR;
    }
    p = &iop[i];
    p->type = type;
    p->pid = -1;
    switch (type) {
    case IO_FILE:
        if (!(p->ifp = p->ofp = fopen (name, mode))) {
            if (strncmp (name, "/dev/tcp/", 9) == 0) {
                if (!(p->ifp = p->ofp = serverconnect (name)))
                    return -1;
                break;
            }
            path = buildpath (name, FALSE);
            if (!path || !(p->ifp = p->ofp = fopen (path, mode)))
                return -1;
        }
        break;
    case IO_PTY:
        if (!fmt)
            fmt = "%e";
        if (!(path = buildpath (name, TRUE)) ||
                !(command = buildcommand (path, NULL, -1, -1, fmt)))
            return -1;
        ptyopen (command, &p->ifp, &p->ofp, &p->pid);
        if (!p->ifp || !p->ofp)
            return -1;
        break;
    case IO_PIPE:
        if (!fmt)
            fmt = "%e";
        if (!(path = buildpath (name, TRUE)) ||
                !(command = buildcommand (path, NULL, -1, -1, fmt)))
            return -1;
        pipeopen (command, &p->ifp, &p->ofp, &p->pid);
        if (!p->ifp || !p->ofp)
            return -1;
        break;
    case IO_SOCKET:
        if (!fmt)
            fmt = "%e";
        if ((sfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
            return -1;
        sname.sin_family = AF_INET;
        sname.sin_port = 0;
        sname.sin_addr.s_addr = htonl (INADDR_ANY);
        slen = sizeof (sname);
        if (bind (sfd, (struct sockaddr *) &sname, slen) < 0 ||
                getsockname (sfd, (struct sockaddr *) &sname, &slen) < 0)
            return -1;
        if (listen (sfd, 5) < 0)
            return -1;
        gethostname (hname, sizeof (hname));
        if (!(path = buildpath (name, TRUE)) ||
                !(command = buildcommand (path, hname,
                (int) ntohs (sname.sin_port),
                (int) ntohs (sname.sin_port), fmt)))
            return -1;
        socketopen (command, sfd, &p->ifp, &p->ofp, &p->pid);
        if (!p->ifp || !p->ofp)
            return -1;
        close (sfd);
        break;
#ifdef FEATURE_CS
    case IO_CS:
        if (C2Lopen (name, mode, &p->ifp, &p->ofp) == -1)
            return -1;
        break;
#endif
    default:
	break;
    }
    p->inuse = TRUE;
    FD_CLR (fileno (p->ifp), &inputfds);
    FD_CLR (fileno (p->ofp), &inputfds);
    return i;
}

int IOclose (int ioi, char *action) {
    io_t *p;

    if (ioi < 0 || ioi >= ion || !iop[ioi].inuse)
        return -1;
    p = &iop[ioi];
    FD_CLR (fileno (p->ifp), &inputfds);
    FD_CLR (fileno (p->ofp), &inputfds);
    if (p->ifp != p->ofp)
        fclose (p->ifp);
    fclose (p->ofp);
    p->inuse = FALSE;
    if (action && Strcmp (action, "kill") == 0 && p->pid != -1)
        kill (p->pid, 15);
    return 0;
}

int IOreadline (int ioi, char *bufp, int bufn) {
    io_t *p;
    int l;

    if (ioi < 0 || ioi >= ion || !iop[ioi].inuse)
        return -1;
    p = &iop[ioi];
    fseek (p->ofp, 0L, 1);
    if (fgets (bufp, bufn, p->ifp) == NULL)
        return -1;
    l = strlen (bufp) - 1;
    while (bufp[l] == '\n' || bufp[l] == '\r')
        bufp[l--] = '\000';
    return l + 1;
}

int IOread (int ioi, char *bufp, int bufn) {
    io_t *p;
    int l;

    if (ioi < 0 || ioi >= ion || !iop[ioi].inuse)
        return -1;
    p = &iop[ioi];
    if ((l = read (fileno (p->ifp), bufp, bufn - 1)) == -1)
        return -1;
    else if (l == 0)
        return 0;
    bufp[l] = '\000';
    return l;
}

int IOwriteline (int ioi, char *bufp) {
    io_t *p;

    if (ioi < 0 || ioi >= ion || !iop[ioi].inuse)
        return -1;
    p = &iop[ioi];
    fseek (p->ofp, 0L, 1);
    if (fputs (bufp, p->ofp) == EOF || fputs ("\n", p->ofp) == EOF)
        return -1;
    fflush (p->ofp);
    fseek (p->ofp, 0L, 1);
    return 0;
}

static FILE *serverconnect (char *name) {
    char *host, *portp, buf[1024];
    int port;
    struct hostent *hp;
    struct sockaddr_in sin;
    int cfd;

    strcpy (buf, name);
    host = buf + 9;
    portp = strchr (host, '/');
    if (*host == 0 || !portp)
        return NULL;
    *portp++ = 0, port = atoi (portp);
    if (!(hp = gethostbyname (host)))
        return NULL;
    memset ((char *) &sin, 1, sizeof (sin));
    memcpy ((char *) &sin.sin_addr, hp->h_addr, hp->h_length);
    sin.sin_family = hp->h_addrtype;
    sin.sin_port = htons (port);
    if ((cfd = socket (hp->h_addrtype, SOCK_STREAM, 0)) < 0)
        return NULL;
    if (connect (cfd, (struct sockaddr *) &sin, sizeof (sin)) < 0)
        return NULL;
    return fdopen (cfd, "w+");
}

static void ptyopen (char *cmd, FILE **ifp, FILE **ofp, int *pidp) {
    int fd[2];

    if (findpty (fd) == -1) {
        *ifp = NULL;
        return;
    }
    switch ((*pidp = FORK ())) {
    case -1:
        panic2 (POS, "ptyopen", "cannot fork");
    case 0:
        close (fd[0]), close (0), dup (fd[1]);
        close (1), dup (fd[1]), close (fd[1]);
        execl (shell, shbname, "-c", cmd, (void *)NULL);
        panic2 (POS, "ptyopen", "child cannot exec: %s\n", cmd);
    default:
        close (fd[1]);
    }
    fcntl (fd[0], F_SETFD, FD_CLOEXEC);
    *ifp = fdopen (fd[0], "r"), *ofp = fdopen (fd[0], "a+");
    return;
}

static int findpty (int *fd) {
    char *majorp, *minorp;
    char pty[32], tty[32];
#ifndef HAVE_TERMIOS_H
    struct termio tio;
#else
    struct termios tio;
#endif
    static char ptymajor[] = "pqrs";
    static char ptyminor[] = "0123456789abcdefghijklmnopqrstuvwxyz";

    for (majorp = ptymajor; *majorp; majorp++) {
        for (minorp = ptyminor; *minorp; minorp++) {
            sprintf (pty, "/dev/pty%c%c", *majorp, *minorp);
            if ((fd[0] = open (pty, O_RDWR)) >= 0) {
                sprintf (tty, "/dev/tty%c%c", *majorp, *minorp);
                if ((fd[1] = open (tty, O_RDWR)) >= 0) {
#ifndef HAVE_TERMIOS_H
                    ioctl (fd[1], TCGETA, &tio);
                    tio.c_lflag &= ~ECHO;
                    ioctl (fd[1], TCSETA, &tio);
#else
                    tcgetattr(fd[1], &tio);
                    tio.c_lflag &= ~ECHO;
                    tcsetattr(fd[1], TCSANOW, &tio);
#endif
                    return 0;
                }
                close (fd[0]);
            }
        }
    }
    return -1;
}

static void pipeopen (char *cmd, FILE **ifp, FILE **ofp, int *pidp) {
    int p1[2], p2[2];
    char cmd2[1024];
    char *s;

    if (pipe (p1) == -1 || pipe (p2) == -1) {
        *ifp = NULL;
        return;
    }
    switch ((*pidp = FORK ())) {
    case -1:
        panic2 (POS, "pipeopen", "cannot fork");
    case 0:
        close (p1[0]), close (p2[1]);
        for (s = cmd; *s; s++)
            if (*s == '%' && *(s + 1) && *(s + 1) == 'd') {
                sprintf (cmd2, cmd, p2[0], p1[1]);
                execl (shell, shbname, "-c", cmd2, (void *)NULL);
                panic2 (POS, "pipeopen", "child cannot exec: %s\n", cmd2);
            }
        close (1), dup (p1[1]), close (p1[1]);
        close (0), dup (p2[0]), close (p2[0]);
        execl (shell, shbname, "-c", cmd, 0);
        panic2 (POS, "pipeopen", "child cannot exec: %s\n", cmd);
    default:
        close (p1[1]), close (p2[0]);
    }
    fcntl (p1[0], F_SETFD, FD_CLOEXEC);
    fcntl (p2[1], F_SETFD, FD_CLOEXEC);
    *ifp = fdopen (p1[0], "r"), *ofp = fdopen (p2[1], "a");
    return;
}

static void socketopen (char *cmd, int sfd, FILE **ifp, FILE **ofp, int *pidp) {
    int fd;

    switch ((*pidp = FORK ())) {
    case -1:
        panic2 (POS, "socketopen", "cannot fork");
    case 0:
        execl (shell, shbname, "-c", cmd, 0);
        panic2 (POS, "socketopen", "child cannot exec: %s\n", cmd);
    default:
        if ((fd = accept (sfd, NULL, NULL)) < 0) {
            *ifp = NULL;
            return;
        }
    }
    fcntl (fd, F_SETFD, FD_CLOEXEC);
    *ifp = fdopen (fd, "r"), *ofp = fdopen (fd, "a+");
    return;
}

static void sigchldhandler (int data) {
    while (waitpid (-1, NULL, WNOHANG) > 0)
        ;
    signal (SIGCHLD, sigchldhandler);
}