diff --git a/15-pty/scribble.c b/15-pty/scribble.c
index 6e30b87..8972a5c 100644
--- a/15-pty/scribble.c
+++ b/15-pty/scribble.c
@@ -95,8 +95,38 @@ void configure_terminal() {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
-// FIXME: Create a thread handler body that copies data from src_fd to two
-// destination file descriptors.
+struct copy_thread_arg {
+ int src_fd;
+ int dst_fd;
+ int dump_fd;
+};
+
+void* copy_thread(void* data) {
+ struct copy_thread_arg *arg = data;
+
+ char buf[1024];
+ while (true) {
+ int len = read(arg->src_fd, buf, sizeof(buf));
+ if (len < 0)
+ die("read");
+ if (len == 0) break;
+
+ for (int bufpos = 0; bufpos < len; ) {
+ int wlen = write(arg->dst_fd, buf+bufpos, len - bufpos);
+ if (wlen < 0)
+ die("write");
+ bufpos += wlen;
+ }
+
+ for (int bufpos = 0; bufpos < len; ) {
+ int wlen = write(arg->dump_fd, buf+bufpos, len - bufpos);
+ if (wlen < 0)
+ die("write");
+ bufpos += wlen;
+ }
+ }
+ return NULL;
+}
int main(int argc, char *argv[]) {
@@ -108,13 +138,89 @@ int main(int argc, char *argv[]) {
char *IN = argv[2];
char **CMD = &argv[3];
- (void) IN; (void) OUT; (void) CMD; (void) exec_in_pty;
- // FIXME: Open OUT and IN file
- // FIXME: Create a new primary PTY device (see pty(7))
- // FIXME: Get the PTN with ioctl(fd, TIOCGPTN, &pts)
- // FIXME: Open the child pty end (/dev/pts/{PTN})
+ // We open the dump file for the terminal output OUT and
+ // for the terminal input IN. We open it read-writable (O_RDWR),
+ // create it in case (O_CREAT), truncate it to zero bytes
+ // (O_TRUNC), and instruct the kernel to close the file descriptor
+ // on exec (O_CLOEXEC). With O_CLOEXEC, the descriptor is not available in our child.
+ int out_fd = open(OUT, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0600);
+ if (out_fd < 0)
+ die("open/dump");
+
+ int in_fd = open(IN, O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0600);
+ if (in_fd < 0)
+ die("open/dump");
+
+ // We allocate a new pseudo terminal by opening the special device
+ // file /dev/ptmx. Please see pty(7) for more details on this.
+ // Special is the O_NOCTTY flag which disables the magic of
+ // setting a control terminal.
+ int primary_fd = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_CLOEXEC);
+ if (primary_fd < 0) die("open/ptmux");
+
+ // And now it goes wild! Like a pipe, a pty has two ends. The
+ // primary and the child end (also referred to as secondary). But
+ // unlike the pipe2(2) system call, we have to deduce the child
+ // ptn (pseudo-terminal number) from the primary fd.
+
+ // For the Unix-98 interface, this can simply be done with an
+ // ioctl. With BSD pseudo-terminals it is not that easy.
+ int ptn;
+ if (ioctl(primary_fd, TIOCGPTN, &ptn) < 0)
+ die("ioctl/TIOCGPTN");
+
+ // This unlocks the secondary pseudo-terminal. In the libc, this is
+ // implemented by unlockpt(3).
+ int unlock = 0;
+ if (ioctl(primary_fd, TIOCSPTLCK, &unlock) < 0)
+ die("ioctl/TIOCSPTLOCK");
+
+
+ // Having the pts, we can create a ptsname(3) filename and open
+ // the child end of our pty. Somehow, with pipe2(2), this was much
+ // easier up to this point.
+ char ptsname[128];
+ sprintf(ptsname, "/dev/pts/%d", ptn);
+ int secondary_fd = open(ptsname, O_RDWR|O_NOCTTY|O_CLOEXEC);
+ if (secondary_fd < 0)
+ die("open/pts");
+
+ printf("primary=%d, pts=%s, child=%d\n", primary_fd, ptsname, secondary_fd);
+
+ // We configure our terminal to pass through some keys and to not
+ // echo everything twice.
+ configure_terminal();
+
+ // Spawn the process into our newly created pty.
+ pid_t pid = exec_in_pty(CMD, secondary_fd);
+ printf("child pid=%d\n", pid);
+
+ // We create two threads that copy data from
+ // 1. From STDIN to the primary fd _and_ the IN file descriptor
+ // 2. From primary_fd to our STDOUT _and_ the OUT file descriptor
+ struct copy_thread_arg args[] = {
+ { .src_fd = STDIN_FILENO, .dst_fd = primary_fd, .dump_fd = in_fd},
+ { .src_fd = primary_fd, .dst_fd = STDOUT_FILENO, .dump_fd = out_fd},
+ };
+
+ // We use two threads to perform this task to avoid all problems
+ // with blocking. Thereby, we avoid the need to coordinate
+ // everything with epoll(2), which you are probably tired of anyway.
+ pthread_t threads[2];
+ for (unsigned i = 0; i < 2; i++) {
+ int rc = pthread_create(&threads[i], NULL, copy_thread, &args[i]);
+ if (rc < 0) die("pthread_create");
+ }
- // FIXME: Spawn CMD into secondary_fd pty
- // FIXME: Create two threads to copy data around
- // FIXME: Use the main thread to waitpid(2) for the child to exit.
+ // In the main thread, we simply wait with waitpid(2) for the
+ // child process to exit.
+ while (1) {
+ int wstatus;
+ if (waitpid(pid, &wstatus, 0) < 0)
+ die("waitpid");
+ if (WIFEXITED(wstatus)) {
+ fprintf(stderr,"child process exited with: %d\n", WEXITSTATUS(wstatus));
+ exit(WEXITSTATUS(wstatus));
+ }
+ }
}