diff options
| author | Louis Burda <dev@sinitax.com> | 2026-01-30 22:49:06 +0100 |
|---|---|---|
| committer | Louis Burda <dev@sinitax.com> | 2026-01-30 22:51:59 +0100 |
| commit | 0ff1fafa09eb0af827233d3e0906cf64282d096f (patch) | |
| tree | 3729dbad9023c155115d408a779a5b0210a6e4f9 | |
| download | taketty-master.tar.gz taketty-master.zip | |
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Makefile | 15 | ||||
| -rw-r--r-- | README.md | 67 | ||||
| -rw-r--r-- | attach.c | 75 | ||||
| -rwxr-xr-x | attach.sh | 17 | ||||
| -rw-r--r-- | taketty.c | 119 |
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; +} |
