/* 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 #include #include /* TIOC* constants */ #include #include "mkpty.h" #ifdef PATH_MAX # define TTYNAME_MAX PATH_MAX #else # define TTYNAME_MAX 512 #endif /* PATH_MAX */ /* Allocate PTY master and slave file descriptors. * errno will have been set if newpty() fails. * * NOTE: This function is my alternative to GLibC's * 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 * login_tty() function. It exists as a learning resource. * REF: https://sourceware.org/git/glibc.git -> ./login/login_tty.c * 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. */ int bindpty(const int fdty) { /* We assume any kernel compiling this defines TIOCSCTTY, * otherwise this implementation won't exactly work... */ if (ioctl(fdty, TIOCSCTTY, 0) == -1) return EXIT_FAILURE; /* Adjust stdin/stdout/stderr to refer to fd*/ while (dup2(fdty, STDIN_FILENO) == -1 && errno == EBUSY) ; while (dup2(fdty, STDOUT_FILENO) == -1 && errno == EBUSY) ; while (dup2(fdty, STDERR_FILENO) == -1 && errno == EBUSY) ; if (fdty > 2) close(fdty); return EXIT_SUCCESS; } /* Allocate a PTY and fork, binding the parent to ptmx (master), * and the child to pts (slave). * Return value is indentical to fork(2). * NOTE: This function is my alternative to GLibC's * forkpty() function. It exists as a learning resource. * REF: https://sourceware.org/git/glibc.git -> ./login/forkpty.c */ pid_t forkmkpty(void) { 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); bindpty(fds); break; default: /* Parent Process */ close(fds); 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 */ }