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.
#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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment