Skip to content

Instantly share code, notes, and snippets.

@apsun
Created March 2, 2025 18:35
Show Gist options
  • Save apsun/732a91eba1dcf921ba8c7f6d91ebfee2 to your computer and use it in GitHub Desktop.
Save apsun/732a91eba1dcf921ba8c7f6d91ebfee2 to your computer and use it in GitHub Desktop.

Revisions

  1. apsun created this gist Mar 2, 2025.
    317 changes: 317 additions & 0 deletions ptyception.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,317 @@
    #define _GNU_SOURCE
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <errno.h>
    #include <pty.h>
    #include <sys/ttydefaults.h>
    #include <sys/epoll.h>
    #include <sys/wait.h>
    #include <sys/signalfd.h>

    #define lengthof(x) (sizeof(x) / sizeof(*(x)))

    ssize_t pty2stdout(int ptymasterfd) {
    ssize_t n;
    char buf[4096];

    n = read(ptymasterfd, buf, sizeof(buf));
    if (n < 0) {
    if (errno == EIO) {
    return 0;
    }

    perror("read(ptymasterfd)");
    exit(1);
    }

    n = write(STDOUT_FILENO, buf, n);
    if (n < 0) {
    perror("write(stdout)");
    exit(1);
    }

    return 0;
    }

    ssize_t stdin2pty(int ptymasterfd) {
    ssize_t n;
    char buf[4096];

    n = read(STDIN_FILENO, buf, sizeof(buf));
    if (n < 0) {
    perror("read(stdin)");
    exit(1);
    }

    if (n == 0) {
    char c = CEOF;
    n = write(ptymasterfd, &c, 1);
    } else {
    n = write(ptymasterfd, buf, n);
    }

    if (n < 0) {
    perror("write(ptymasterfd)");
    exit(1);
    }

    return n;
    }

    ssize_t signalfd2pty(int sfd, int ptymasterfd) {
    ssize_t n;
    struct signalfd_siginfo si;

    n = read(sfd, &si, sizeof(si));
    if (n < 0) {
    perror("read(signalfd)");
    exit(1);
    }

    char c;
    if (si.ssi_signo == SIGINT) {
    c = CINTR;
    } else if (si.ssi_signo == SIGTSTP) {
    c = CSUSP;
    } else if (si.ssi_signo == SIGQUIT) {
    c = CQUIT;
    } else {
    fprintf(stderr, "unknown signal\n");
    exit(1);
    }

    n = write(ptymasterfd, &c, 1);
    if (n < 0) {
    perror("write(ptymasterfd)");
    exit(1);
    }

    return n;
    }

    int master(int ptymasterfd, int childpid) {
    int epollfd = epoll_create1(0);
    if (epollfd < 0) {
    perror("epoll_create1");
    exit(1);
    }

    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTSTP);
    sigaddset(&mask, SIGQUIT);

    if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
    perror("sigprocmask");
    exit(1);
    }

    int sfd = signalfd(-1, &mask, 0);
    if (sfd < 0) {
    perror("signalfd");
    exit(1);
    }

    struct epoll_event ev;

    ev.events = EPOLLIN;
    ev.data.fd = ptymasterfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ptymasterfd, &ev) < 0) {
    perror("epoll_ctl(ptymasterfd)");
    exit(1);
    }

    ev.events = EPOLLIN;
    ev.data.fd = STDIN_FILENO;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) < 0) {
    perror("epoll_ctl(STDIN_FILENO)");
    exit(1);
    }

    ev.events = EPOLLIN;
    ev.data.fd = sfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, &ev) < 0) {
    perror("epoll_ctl(signalfd)");
    exit(1);
    }

    while (1) {
    struct epoll_event events[3];
    int nfds = epoll_wait(epollfd, events, lengthof(events), -1);
    if (nfds < 0) {
    perror("epoll_wait");
    exit(1);
    }

    for (int i = 0; i < nfds; ++i) {
    struct epoll_event *ev = &events[i];
    ssize_t n;
    if (ev->data.fd == ptymasterfd) {
    n = pty2stdout(ptymasterfd);
    } else if (ev->data.fd == STDIN_FILENO) {
    n = stdin2pty(ptymasterfd);
    } else if (ev->data.fd == sfd) {
    n = signalfd2pty(sfd, ptymasterfd);
    } else {
    fprintf(stderr, "unknown fd\n");
    exit(1);
    }
    }

    int status;
    int waitret = waitpid(childpid, &status, WNOHANG);
    if (waitret == childpid) {
    if (WIFEXITED(status)) {
    break;
    }
    }
    }

    close(epollfd);
    close(sfd);
    }

    void dumptermios(void) {
    struct termios curr;
    if (tcgetattr(0, &curr) < 0) {
    perror("tcgetattr");
    exit(1);
    }

    #define X(x) printf("c_iflag[%s] = %d\n", #x, !!(curr.c_iflag & x));
    X(IGNBRK)
    X(BRKINT)
    X(IGNPAR)
    X(PARMRK)
    X(INPCK)
    X(ISTRIP)
    X(INLCR)
    X(IGNCR)
    X(ICRNL)
    X(IUCLC)
    X(IXON)
    X(IXANY)
    X(IXOFF)
    X(IMAXBEL)
    X(IUTF8)
    #undef X
    printf("\n");

    #define X(x) printf("c_oflag[%s] = %d\n", #x, !!(curr.c_oflag & x));
    X(OPOST)
    X(OLCUC)
    X(ONLCR)
    X(OCRNL)
    X(ONOCR)
    X(ONLRET)
    X(OFILL)
    X(OFDEL)
    X(NLDLY)
    X(NL0)
    X(NL1)
    X(CRDLY)
    X(CR0)
    X(CR1)
    X(CR2)
    X(CR3)
    X(TABDLY)
    X(TAB0)
    X(TAB1)
    X(TAB2)
    X(TAB3)
    X(BSDLY)
    X(BS0)
    X(BS1)
    X(FFDLY)
    X(FF0)
    X(FF1)
    X(VTDLY)
    X(VT0)
    X(VT1)
    X(XTABS)
    #undef X
    printf("\n");

    #define X(x) printf("c_lflag[%s] = %d\n", #x, !!(curr.c_lflag & x));
    X(ISIG)
    X(ICANON)
    X(XCASE)
    X(ECHO)
    X(ECHOE)
    X(ECHOK)
    X(ECHONL)
    X(NOFLSH)
    X(TOSTOP)
    X(ECHOCTL)
    X(ECHOPRT)
    X(ECHOKE)
    X(FLUSHO)
    X(PENDIN)
    X(IEXTEN)
    X(EXTPROC)
    #undef X
    printf("\n");

    #define X(x) printf("c_cc[%s] = %d\n", #x, curr.c_cc[x]);
    X(VINTR)
    X(VQUIT)
    X(VERASE)
    X(VKILL)
    X(VEOF)
    X(VTIME)
    X(VMIN)
    X(VSWTC)
    X(VSTART)
    X(VSTOP)
    X(VSUSP)
    X(VEOL)
    X(VREPRINT)
    X(VDISCARD)
    X(VWERASE)
    X(VLNEXT)
    X(VEOL2)
    #undef X
    }

    int main(void) {
    dumptermios();

    int ptymasterfd;
    struct termios term = {
    .c_iflag = ICRNL | /* IXON | */ IUTF8,
    .c_oflag = OPOST | ONLCR,
    .c_lflag = ISIG | ICANON | /* ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | */ IEXTEN,
    .c_cc = {
    [VINTR] = CINTR,
    [VEOF] = CEOF,
    [VSUSP] = CSUSP,
    [VQUIT] = CQUIT,
    },
    };
    struct winsize win = {
    .ws_row = 0,
    .ws_col = 0,
    .ws_xpixel = 0,
    .ws_ypixel = 0,
    };
    int childpid = forkpty(&ptymasterfd, NULL, &term, &win);
    if (childpid < 0) {
    perror("forkpty");
    exit(1);
    } else if (childpid == 0) {
    char *prog = "/bin/sh";
    char *argv[] = {prog, NULL};
    char *envp[] = {"TERM=dumb", NULL};
    if (execvpe(prog, argv, envp) < 0) {
    perror("exec");
    exit(1);
    }
    } else {
    int ret = master(ptymasterfd, childpid);
    close(ptymasterfd);
    return ret;
    }
    }