/* Adapted from Kerrisk's script.c by Ken Brown */ /* Create a pty pair and fork/exec a shell running on the slave. Send the slave fd across an AF_UNIX socket to a process running recv_pty_slave. */ /*************************************************************************\ * Copyright (C) Michael Kerrisk, 2018. * * * * This program is free software. You may use, modify, and redistribute it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation, either version 3 or (at your option) any * * later version. This program is distributed without any warranty. See * * the file COPYING.gpl-v3 for details. * \*************************************************************************/ /* Listing 64-3 */ /* script.c A simple version of script(1). */ #include #include #include #include #if ! defined(__hpux) /* HP-UX 11 doesn't have this header file */ #include #endif #include "pty_fork.h" /* Declaration of ptyFork() */ #include "tty_functions.h" /* Declaration of ttySetRaw() */ #include "af_unix_hdr.h" #include "pty_slave.h" #define BUF_SIZE 256 #define MAX_SNAME 1000 struct termios ttyOrig; static void /* Reset terminal mode on program exit */ ttyReset(void) { if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1) errExit("tcsetattr"); } int main(int argc, char *argv[]) { char slaveName[MAX_SNAME]; char *shell; int masterFd, slaveFd, connFd; struct winsize ws; fd_set inFds; char buf[BUF_SIZE]; ssize_t numRead; pid_t childPid; /* Retrieve the attributes of terminal on which we are started */ if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1) errExit("tcgetattr"); if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) errExit("ioctl-TIOCGWINSZ"); /* Create a child process, with parent and child connected via a pty pair. The child is connected to the pty slave and its terminal attributes are set to be the same as those retrieved above. */ childPid = ptyFork(&masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws); if (childPid == -1) errExit("ptyFork"); if (childPid == 0) { /* Child: execute a shell on pty slave */ /* If the SHELL variable is set, use its value to determine the shell execed in child. Otherwise use /bin/sh. */ shell = getenv("SHELL"); if (shell == NULL || *shell == '\0') shell = "/bin/sh"; execlp(shell, shell, (char *) NULL); errExit("execlp"); /* If we get here, something went wrong */ } /* Parent */ if ((connFd = unixConnect (SOCK_PATH, SOCK_STREAM)) < 0) errExit ("unixConnect"); /* Open slave and send its fd across the socket. */ if ((slaveFd = open (slaveName, O_RDWR | O_NOCTTY)) < 0) errExit ("open"); if (sendfd (connFd, slaveFd) < 0) errExit ("sendfd"); if (close (slaveFd) < 0) errMsg ("close"); if (close (connFd) < 0) errMsg ("close"); /* Place terminal in raw mode so that we can pass all terminal input to the pseudoterminal master untouched */ ttySetRaw(STDIN_FILENO, &ttyOrig); if (atexit(ttyReset) != 0) errExit("atexit"); /* Loop monitoring terminal and pty master for input. If the terminal is ready for input, then read some bytes and write them to the pty master. If the pty master is ready for input, then read some bytes and write them to the terminal. */ for (;;) { FD_ZERO(&inFds); FD_SET(STDIN_FILENO, &inFds); FD_SET(masterFd, &inFds); if (select(masterFd + 1, &inFds, NULL, NULL, NULL) == -1) errExit("select"); if (FD_ISSET(STDIN_FILENO, &inFds)) { /* stdin --> pty */ numRead = read(STDIN_FILENO, buf, BUF_SIZE); if (numRead <= 0) exit(EXIT_SUCCESS); if (write(masterFd, buf, numRead) != numRead) fatal("partial/failed write (masterFd)"); } if (FD_ISSET(masterFd, &inFds)) { /* pty --> stdout */ numRead = read(masterFd, buf, BUF_SIZE); if (numRead <= 0) exit(EXIT_SUCCESS); if (write(STDOUT_FILENO, buf, numRead) != numRead) fatal("partial/failed write (STDOUT_FILENO)"); } } }