diff options
-rw-r--r-- | bin/.gitignore | 1 | ||||
-rw-r--r-- | bin/Makefile | 1 | ||||
-rw-r--r-- | bin/README | 3 | ||||
-rw-r--r-- | bin/bin.7 | 5 | ||||
-rw-r--r-- | bin/man1/ptee.1 | 27 | ||||
-rw-r--r-- | bin/ptee.c | 98 |
6 files changed, 133 insertions, 2 deletions
diff --git a/bin/.gitignore b/bin/.gitignore index 1b9d0034..40cd902e 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -23,6 +23,7 @@ pbpaste pngo psf2png psfed +ptee relay scheme scheme.h diff --git a/bin/Makefile b/bin/Makefile index 71540ce0..322e6c43 100644 --- a/bin/Makefile +++ b/bin/Makefile @@ -28,6 +28,7 @@ BINS += order BINS += pbd BINS += pngo BINS += psf2png +BINS += ptee BINS += scheme BINS += setopt BINS += ttpre diff --git a/bin/README b/bin/README index f55b95d1..29a20e30 100644 --- a/bin/README +++ b/bin/README @@ -25,6 +25,7 @@ DESCRIPTION pngo(1) PNG optimizer psf2png(1) PSF2 to PNG renderer psfed(1) PSF2 font editor + ptee(1) tee for PTYs relay(1) IRC relay scheme(1) color scheme ttpre(1) man output to HTML @@ -39,4 +40,4 @@ DESCRIPTION GFX=fb GFX=x11 -Causal Agency May 4, 2019 Causal Agency +Causal Agency July 8, 2019 Causal Agency diff --git a/bin/bin.7 b/bin/bin.7 index 7858bc48..e3bbbb7c 100644 --- a/bin/bin.7 +++ b/bin/bin.7 @@ -1,4 +1,4 @@ -.Dd May 4, 2019 +.Dd July 8, 2019 .Dt BIN 7 .Os "Causal Agency" . @@ -70,6 +70,9 @@ PSF2 to PNG renderer .It Xr psfed 1 PSF2 font editor . +.It Xr ptee 1 +tee for PTYs +. .It Xr relay 1 IRC relay . diff --git a/bin/man1/ptee.1 b/bin/man1/ptee.1 new file mode 100644 index 00000000..661c4888 --- /dev/null +++ b/bin/man1/ptee.1 @@ -0,0 +1,27 @@ +.Dd July 8, 2019 +.Dt PTEE 1 +.Os +. +.Sh NAME +.Nm ptee +.Nd tee for PTYs +. +.Sh SYNOPSIS +.Nm +.Ar command ... +.Cm > +.Ar file +. +.Sh DESCRIPTION +.Nm +runs +.Ar command +in a new PTY +which is mirrored to +the current PTY +and standard output. +Standard output must be redirected +to a file or pipe. +. +.Sh SEE ALSO +.Xr tee 1 diff --git a/bin/ptee.c b/bin/ptee.c new file mode 100644 index 00000000..c187ba92 --- /dev/null +++ b/bin/ptee.c @@ -0,0 +1,98 @@ +/* Copyright (C) 2019 C. McEnroe <june@causal.agency> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <err.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <sysexits.h> +#include <termios.h> +#include <unistd.h> + +#if defined __FreeBSD__ +#include <libutil.h> +#elif defined __linux__ +#include <pty.h> +#else +#include <util.h> +#endif + +typedef unsigned char byte; + +static struct termios saveTerm; +static void restoreTerm(void) { + tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm); +} + +int main(int argc, char *argv[]) { + if (argc < 2) return EX_USAGE; + if (isatty(STDOUT_FILENO)) errx(EX_USAGE, "stdout is not redirected"); + + int error = tcgetattr(STDIN_FILENO, &saveTerm); + if (error) err(EX_IOERR, "tcgetattr"); + atexit(restoreTerm); + + struct termios raw = saveTerm; + cfmakeraw(&raw); + error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw); + if (error) err(EX_IOERR, "tcsetattr"); + + struct winsize window; + error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window); + if (error) err(EX_IOERR, "ioctl"); + + int pty; + pid_t pid = forkpty(&pty, NULL, NULL, &window); + if (pid < 0) err(EX_OSERR, "forkpty"); + + if (!pid) { + execvp(argv[1], &argv[1]); + err(EX_NOINPUT, "%s", argv[1]); + } + + byte buf[4096]; + struct pollfd fds[2] = { + { .events = POLLIN, .fd = STDIN_FILENO }, + { .events = POLLIN, .fd = pty }, + }; + while (0 < poll(fds, 2, -1)) { + if (fds[0].revents & POLLIN) { + ssize_t rlen = read(STDIN_FILENO, buf, sizeof(buf)); + if (rlen < 0) err(EX_IOERR, "read"); + ssize_t wlen = write(pty, buf, rlen); + if (wlen < 0) err(EX_IOERR, "write"); + } + + if (fds[1].revents & POLLIN) { + ssize_t rlen = read(pty, buf, sizeof(buf)); + if (rlen < 0) err(EX_IOERR, "read"); + + ssize_t wlen = write(STDIN_FILENO, buf, rlen); + if (wlen < 0) err(EX_IOERR, "write"); + + wlen = write(STDOUT_FILENO, buf, rlen); + if (wlen < 0) err(EX_IOERR, "write"); + } + + int status; + pid_t dead = waitpid(pid, &status, WNOHANG); + if (dead < 0) err(EX_OSERR, "waitpid"); + if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE; + } + err(EX_IOERR, "poll"); +} |