aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLouis Burda <dev@sinitax.com>2026-01-30 22:49:06 +0100
committerLouis Burda <dev@sinitax.com>2026-01-30 22:51:59 +0100
commit0ff1fafa09eb0af827233d3e0906cf64282d096f (patch)
tree3729dbad9023c155115d408a779a5b0210a6e4f9
downloadtaketty-main.tar.gz
taketty-main.zip
Add initial versionHEADmastermain
-rw-r--r--.gitignore2
-rw-r--r--Makefile15
-rw-r--r--README.md67
-rw-r--r--attach.c75
-rwxr-xr-xattach.sh17
-rw-r--r--taketty.c119
6 files changed, 295 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..553be88
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+taketty
+attach
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..a377fb4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,15 @@
+CC = gcc
+CFLAGS = -Wall -Wextra -O2
+
+all: taketty attach
+
+taketty: taketty.c
+ $(CC) $(CFLAGS) -o taketty taketty.c
+
+attach: attach.c
+ $(CC) $(CFLAGS) -o attach attach.c
+
+clean:
+ rm -f taketty attach
+
+.PHONY: all clean
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d4fa33c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,67 @@
+# taketty
+
+Create a new PTY that other programs can attach to as their controlling terminal.
+
+## Building
+
+```bash
+make
+```
+
+## Usage
+
+### Creating a PTY
+
+Run `taketty` to create a new PTY and forward I/O:
+
+```bash
+./taketty
+```
+
+This will:
+- Create a new PTY via `/dev/ptmx`
+- Configure and unlock the slave
+- Put the master terminal in raw mode
+- Forward stdin/stdout to the PTY master
+- Print the slave path to stderr as `TAKETTY_PTY=/dev/pts/N`
+
+### Attaching to the PTY
+
+#### Method 1: Using the attach helper (with job control)
+
+In another terminal, capture the PTY path and use the attach helper to exec a shell:
+
+```bash
+export TAKETTY_PTY=/dev/pts/N
+./attach $TAKETTY_PTY bash
+```
+
+Or pass the command directly:
+
+```bash
+./attach /dev/pts/N bash
+```
+
+The attach helper will:
+- Call `setsid()` to create a new session
+- Open the slave PTY
+- Use `ioctl(TIOCSCTTY)` to make it the controlling terminal
+- Redirect stdin/stdout/stderr to the PTY
+- Execute the specified program (or return if none specified)
+
+#### Method 2: Source the bash script (I/O only, no job control)
+
+If you only need I/O redirection without full job control:
+
+```bash
+export TAKETTY_PTY=/dev/pts/N
+source ./attach.sh
+```
+
+Note: This method redirects file descriptors but doesn't set the controlling terminal, so signals like Ctrl+C won't work as expected.
+
+## Architecture
+
+- **taketty.c**: Creates PTY master, forwards I/O between stdin/stdout and master FD
+- **attach.c**: Helper to attach a process to the PTY slave as controlling terminal
+- **attach.sh**: Bash script for simple I/O redirection (sourcing only)
diff --git a/attach.c b/attach.c
new file mode 100644
index 0000000..e318b89
--- /dev/null
+++ b/attach.c
@@ -0,0 +1,75 @@
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+
+int main(int argc, char *argv[]) {
+ int fd;
+ const char *tty_path;
+ const char *tty_path_env;
+ pid_t pid;
+
+ fprintf(stderr, "%i\n", argc);
+ tty_path_env = getenv("TAKETTY_PTY");
+ if (argc <= 1 || (!strcmp(argv[1], "-") && !tty_path_env)) {
+ fprintf(stderr, "Usage: %s (PATH|-)\n", argv[0]);
+ fprintf(stderr, "Or set TAKETTY_PTY environment variable\n");
+ exit(1);
+ }
+
+ tty_path = argv[1];
+ if (!strcmp(argv[1], "-")) {
+ tty_path = tty_path_env;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ exit(1);
+ }
+
+ if (pid > 0) {
+ int status;
+ waitpid(pid, &status, 0);
+ exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
+ }
+
+ if (setsid() < 0) {
+ perror("setsid");
+ exit(1);
+ }
+
+ fd = open(tty_path, O_RDWR);
+ if (fd < 0) {
+ perror("open tty");
+ exit(1);
+ }
+
+ if (ioctl(fd, TIOCSCTTY, 0) < 0) {
+ perror("ioctl TIOCSCTTY");
+ exit(1);
+ }
+
+ if (dup2(fd, STDIN_FILENO) < 0 ||
+ dup2(fd, STDOUT_FILENO) < 0 ||
+ dup2(fd, STDERR_FILENO) < 0) {
+ perror("dup2");
+ exit(1);
+ }
+
+ if (fd > STDERR_FILENO) {
+ close(fd);
+ }
+
+ if (argc > 2) {
+ execvp(argv[2], &argv[2]);
+ perror("execvp");
+ exit(1);
+ }
+
+ return 0;
+}
diff --git a/attach.sh b/attach.sh
new file mode 100755
index 0000000..055574a
--- /dev/null
+++ b/attach.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+if [ -z "$TAKETTY_PTY" ]; then
+ echo "Error: TAKETTY_PTY not set" >&2
+ return 1 2>/dev/null || exit 1
+fi
+
+if [ ! -e "$TAKETTY_PTY" ]; then
+ echo "Error: $TAKETTY_PTY does not exist" >&2
+ return 1 2>/dev/null || exit 1
+fi
+
+exec 0<>"$TAKETTY_PTY"
+exec 1>&0
+exec 2>&0
+
+echo "Attached to $TAKETTY_PTY (note: not controlling terminal without attach helper)"
diff --git a/taketty.c b/taketty.c
new file mode 100644
index 0000000..31fb25e
--- /dev/null
+++ b/taketty.c
@@ -0,0 +1,119 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <sys/select.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <string.h>
+
+static void set_raw_mode(int fd) {
+ struct termios tios;
+
+ if (tcgetattr(fd, &tios) < 0) {
+ perror("tcgetattr");
+ exit(1);
+ }
+
+ tios.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
+ tios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ tios.c_cflag &= ~(CSIZE | PARENB);
+ tios.c_cflag |= CS8;
+ tios.c_oflag &= ~(OPOST);
+
+ tios.c_cc[VMIN] = 1;
+ tios.c_cc[VTIME] = 0;
+
+ if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) {
+ perror("tcsetattr");
+ exit(1);
+ }
+}
+
+static void forward_io(int master_fd) {
+ fd_set rfds;
+ char buf[4096];
+ ssize_t n;
+
+ while (1) {
+ FD_ZERO(&rfds);
+ FD_SET(STDIN_FILENO, &rfds);
+ FD_SET(master_fd, &rfds);
+
+ int maxfd = (master_fd > STDIN_FILENO) ? master_fd : STDIN_FILENO;
+
+ if (select(maxfd + 1, &rfds, NULL, NULL, NULL) < 0) {
+ if (errno == EINTR) continue;
+ perror("select");
+ exit(1);
+ }
+
+ if (FD_ISSET(STDIN_FILENO, &rfds)) {
+ n = read(STDIN_FILENO, buf, sizeof(buf));
+ if (n <= 0) {
+ if (n < 0) perror("read stdin");
+ break;
+ }
+ if (write(master_fd, buf, n) != n) {
+ perror("write to master");
+ exit(1);
+ }
+ }
+
+ if (FD_ISSET(master_fd, &rfds)) {
+ n = read(master_fd, buf, sizeof(buf));
+ if (n <= 0) {
+ if (n < 0 && errno != EIO) perror("read master");
+ break;
+ }
+ if (write(STDOUT_FILENO, buf, n) != n) {
+ perror("write to stdout");
+ exit(1);
+ }
+ }
+ }
+}
+
+int main(int argc, char *argv[]) {
+ int master_fd;
+ char *slave_name;
+ struct winsize ws;
+
+ master_fd = posix_openpt(O_RDWR | O_NOCTTY);
+ if (master_fd < 0) {
+ perror("posix_openpt");
+ exit(1);
+ }
+
+ if (grantpt(master_fd) < 0) {
+ perror("grantpt");
+ exit(1);
+ }
+
+ if (unlockpt(master_fd) < 0) {
+ perror("unlockpt");
+ exit(1);
+ }
+
+ slave_name = ptsname(master_fd);
+ if (!slave_name) {
+ perror("ptsname");
+ exit(1);
+ }
+
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
+ ioctl(master_fd, TIOCSWINSZ, &ws);
+ }
+
+ fprintf(stderr, "TAKETTY_PTY=%s\n", slave_name);
+ fflush(stderr);
+
+ set_raw_mode(STDIN_FILENO);
+
+ forward_io(master_fd);
+
+ close(master_fd);
+ return 0;
+}