/* WARNING: The mkpty(), forkmkpty(), & bindpty() functions are based on * WARNING: the glibc openpty(), forkpty(), & login_tty() functions respectively. * WARNING: * WARNING: The GNU C Library's COPYING & COPYING.LIB licenses are available at * WARNING: LICENSES/GLIBC-COPYING & LICENSES/GLIBC-COPYING.LIB in this repo. * WARNING: * WARNING: login_tty() maintains the original University of California license * WARNING: available at LICENSES/UC-LICENSE in this repo. */ /* _XOPEN_SOURCE unlocks pty/ptmx/pts declarations. */ #define _XOPEN_SOURCE 600 /* _GNU_SOURCE unlocks the ptsname_r declaration*/ #define _GNU_SOURCE #include #include #include /* TIOC* constants */ #include #include "_pty.h" /* Allocate PTY master and slave file descriptors. * errno will have been set if newpty() fails. * * NOTE: This function is my alternative to GLibC's * NOTE: openpty() function. It exists as a learning resource. * REF: https://sourceware.org/git/glibc.git -> ./login/openpty.c */ int mkpty(int *fdmx, int *fds) { int _fdmx = -1, _fds = -1; char sname[TTYNAME_MAX]; // Configure PTY master (file descriptor) _fdmx = posix_openpt(O_RDWR | O_NOCTTY); if (_fdmx == -1) return EXIT_FAILURE; if (grantpt(_fdmx)) goto fail; if (unlockpt(_fdmx)) goto fail; #ifdef TIOCGPTPEER /* Try to allocate slave fd solely based on PTMX fd first. */ _fds = ioctl(_fdmx, TIOCGPTPEER, O_RDWR | O_NOCTTY); #endif if (_fds == -1) { /* Fallback to path-based slave fd allocation * (if the kernel doesn't support TIOCGPTPEER, ie Linux <4.13) */ if(ptsname_r(_fdmx, sname, sizeof(sname))) goto fail; _fds = open(sname, O_RDWR | O_NOCTTY); if (_fds == -1) goto fail; } // Propagate file descriptors via parameters *fdmx = _fdmx; *fds = _fds; return EXIT_SUCCESS; fail: if (_fdmx == -1) { close(_fdmx); if (_fds == -1) close(_fds); } return EXIT_FAILURE; } /* Set fdty as the controlling terminal for the calling process. * Returns 0 on success, and 1 on failure. * NOTE: This function is my alternative to GLibC's * NOTE: login_tty() function. It exists as a learning resource. * REF: https://sourceware.org/git/glibc.git -> ./login/login_tty.c */ inline int setctty(const int fdty) { /* We assume any kernel compiling this defines TIOCSCTTY, * otherwise this implementation won't exactly work... */ return (ioctl(fdty, TIOCSCTTY, 0) != -1); } /* Bind fdty (terminal fd) to stdin/stdout/stderr for the calling process. * This functions blocks until the EBUSY (see `man dup2`) race condition lifts. * NOTE: This function is my alternative to GLibC's * NOTE: login_tty() function. It exists as a learning resource. * WARNING: This function maintains the original University of California * WARNING: LICENSE (1990-1993) as per glibc.git:/login/login_tty.c * WARNING: available at LICENSES/UC-LICENSE in this repo. * REF: https://sourceware.org/git/glibc.git -> ./login/login_tty.c */ inline void bindpty(const int fdty) { /* Adjust stdin/stdout/stderr to refer to fd*/ BIND(fdty, STDIN_FILENO); BIND(fdty, STDOUT_FILENO); BIND(fdty, STDERR_FILENO); if (fdty > 2) close(fdty); } /* Allocate a PTY and fork, giving ptmx (master) to the parent * and binding the child's stdin/stdout/stderr to pts (slave). * Return value is indentical to fork(2). * NOTE: This function is my alternative to GLibC's * NOTE: forkpty() function. It exists as a learning resource. * REF: https://sourceware.org/git/glibc.git -> ./login/forkpty.c */ pid_t forkmkpty(int *fdmx) { int _fdmx, fds; pid_t pid; if (mkpty(&_fdmx, &fds)) return EXIT_FAILURE; switch (pid = fork()) { case -1: close(_fdmx); close(fds); return -1; case 0: /* Child Process */ close(_fdmx); setctty(fds); bindpty(fds); break; default: /* Parent Process */ close(fds); // propagate ptmx (master) fd *fdmx = _fdmx; break; } /* Both Processes */ return pid; } /* Set pseudoterminal slave's window size. * Returns 0 on success, and fails with -1 if the kernel doesn't * implement this, or 1 for general errors (errno will be set). * NOTE: Typically this is part of a glibc openpty() call. */ int setptsxy(const unsigned short rows, const unsigned short cols, const int fds) { #ifndef TIOCSWINSZ /* Fail if kernel doesn't support TIOCSWINSZ. */ return -1; #else struct winsize win = { .ws_row = rows, .ws_col = cols, }; if (ioctl(fds, TIOCSWINSZ, &win) == -1) return EXIT_FAILURE; return EXIT_SUCCESS; #endif /* TIOCSWINSZ */ } /* Get pseudoterminal slave's window size. * Returns 0 on success, and fails with -1 if the kernel doesn't * implement this, or 1 for general errors (errno will be set). */ int getptsxy(unsigned short *rows, unsigned short *cols, const int fds) { #ifndef TIOCGWINSZ /* Fail if kernel doesn't support TIOCGWINSZ. */ return -1; #else struct winsize win = (struct winsize){ 0 }; if (ioctl(fds, TIOCGWINSZ, &win) == -1) return EXIT_FAILURE; *rows = win.ws_row; *cols = win.ws_col; return EXIT_SUCCESS; #endif /* TIOCGWINSZ */ }