summary refs log tree commit diff
path: root/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bin')
-rw-r--r--bin/.gitignore1
-rw-r--r--bin/Makefile1
-rw-r--r--bin/README3
-rw-r--r--bin/bin.75
-rw-r--r--bin/man1/ptee.127
-rw-r--r--bin/ptee.c98
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..ee035e8a
--- /dev/null
+++ b/bin/ptee.c
@@ -0,0 +1,98 @@
+/* Copyright (C) 2019  June 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");
+}