/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Alexander Larsson */ /* * Copyright (C) 2001,2002,2004 Red Hat, Inc. * * This is free software; you can redistribute it and/or modify it under * the terms of the GNU Library General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Originally from vte */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_UN_H #include #endif #ifdef HAVE_STROPTS_H #include #endif #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_UTMP_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #include "pty_open.h" #if defined(HAVE_PTSNAME_R) || defined(HAVE_PTSNAME) || defined(TIOCGPTN) #define HAVE_UNIX98_PTY #else #undef HAVE_UNIX98_PTY #endif /* * force openpty(3) on BSD * https://bugzilla.gnome.org/show_bug.cgi?id=722001 */ #if defined(__FreeBSD__) || defined(__OpenBSD__) #undef HAVE_UNIX98_PTY #endif /* Solaris does not have the login_tty() function so implement locally. */ #ifndef HAVE_LOGIN_TTY static int login_tty(int pts) { #if defined(HAVE_STROPTS_H) /* push a terminal onto stream head */ if (ioctl(pts, I_PUSH, "ptem") == -1) return -1; if (ioctl(pts, I_PUSH, "ldterm") == -1) return -1; #endif setsid(); #if defined(TIOCSCTTY) ioctl(pts, TIOCSCTTY, 0); #endif dup2(pts, 0); dup2(pts, 1); dup2(pts, 2); if (pts > 2) close(pts); return 0; } #endif /* Reset the handlers for all known signals to their defaults. The parent * (or one of the libraries it links to) may have changed one to be ignored. */ static void _pty_reset_signal_handlers(void) { signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGKILL, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGPIPE, SIG_DFL); signal(SIGALRM, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGCHLD, SIG_DFL); signal(SIGCONT, SIG_DFL); signal(SIGSTOP, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGTTIN, SIG_DFL); signal(SIGTTOU, SIG_DFL); #ifdef SIGBUS signal(SIGBUS, SIG_DFL); #endif #ifdef SIGPOLL signal(SIGPOLL, SIG_DFL); #endif #ifdef SIGPROF signal(SIGPROF, SIG_DFL); #endif #ifdef SIGSYS signal(SIGSYS, SIG_DFL); #endif #ifdef SIGTRAP signal(SIGTRAP, SIG_DFL); #endif #ifdef SIGURG signal(SIGURG, SIG_DFL); #endif #ifdef SIGVTALARM signal(SIGVTALARM, SIG_DFL); #endif #ifdef SIGXCPU signal(SIGXCPU, SIG_DFL); #endif #ifdef SIGXFSZ signal(SIGXFSZ, SIG_DFL); #endif #ifdef SIGIOT signal(SIGIOT, SIG_DFL); #endif #ifdef SIGEMT signal(SIGEMT, SIG_DFL); #endif #ifdef SIGSTKFLT signal(SIGSTKFLT, SIG_DFL); #endif #ifdef SIGIO signal(SIGIO, SIG_DFL); #endif #ifdef SIGCLD signal(SIGCLD, SIG_DFL); #endif #ifdef SIGPWR signal(SIGPWR, SIG_DFL); #endif #ifdef SIGINFO signal(SIGINFO, SIG_DFL); #endif #ifdef SIGLOST signal(SIGLOST, SIG_DFL); #endif #ifdef SIGWINCH signal(SIGWINCH, SIG_DFL); #endif #ifdef SIGUNUSED signal(SIGUNUSED, SIG_DFL); #endif } #ifdef HAVE_UNIX98_PTY #ifdef HAVE_SOCKETPAIR static int _pty_pipe_open(int *a, int *b) { int p[2], ret = -1; #ifdef PF_UNIX #ifdef SOCK_STREAM ret = socketpair(PF_UNIX, SOCK_STREAM, 0, p); #else #ifdef SOCK_DGRAM ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, p); #endif #endif if (ret == 0) { *a = p[0]; *b = p[1]; return 0; } #endif return ret; } #else static int _pty_pipe_open(int *a, int *b) { int p[2], ret = -1; ret = pipe(p); if (ret == 0) { *a = p[0]; *b = p[1]; } return ret; } #endif static int _pty_pipe_open_bi(int *a, int *b, int *c, int *d) { int ret; ret = _pty_pipe_open(a, b); if (ret != 0) { return ret; } ret = _pty_pipe_open(c, d); if (ret != 0) { close(*a); close(*b); } return ret; } /* Like read, but hide EINTR and EAGAIN. */ static ssize_t n_read(int fd, void *buffer, size_t count) { size_t n = 0; char *buf = buffer; int i; while (n < count) { i = read(fd, buf + n, count - n); switch (i) { case 0: return n; break; case -1: switch (errno) { case EINTR: case EAGAIN: #ifdef ERESTART case ERESTART: #endif break; default: return -1; } break; default: n += i; break; } } return n; } /* Like write, but hide EINTR and EAGAIN. */ static ssize_t n_write(int fd, const void *buffer, size_t count) { size_t n = 0; const char *buf = buffer; int i; while (n < count) { i = write(fd, buf + n, count - n); switch (i) { case 0: return n; break; case -1: switch (errno) { case EINTR: case EAGAIN: #ifdef ERESTART case ERESTART: #endif break; default: return -1; } break; default: n += i; break; } } return n; } /* Run the given command (if specified), using the given descriptor as the * controlling terminal. */ static int _pty_run_on_pty(int fd, gboolean login, int stdin_fd, int stdout_fd, int stderr_fd, int ready_reader, int ready_writer, char **env_add, const char *command, char **argv, const char *directory) { int i; char c; char **args, *arg; #ifdef HAVE_STROPTS_H if (!ioctl (fd, I_FIND, "ptem") && ioctl (fd, I_PUSH, "ptem") == -1) { close (fd); _exit (0); return -1; } if (!ioctl (fd, I_FIND, "ldterm") && ioctl (fd, I_PUSH, "ldterm") == -1) { close (fd); _exit (0); return -1; } if (!ioctl (fd, I_FIND, "ttcompat") && ioctl (fd, I_PUSH, "ttcompat") == -1) { perror ("ioctl (fd, I_PUSH, \"ttcompat\")"); close (fd); _exit (0); return -1; } #endif /* HAVE_STROPTS_H */ /* Set any environment variables. */ for (i = 0; (env_add != NULL) && (env_add[i] != NULL); i++) { if (putenv(g_strdup(env_add[i])) != 0) { g_warning("Error adding `%s' to environment, " "continuing.", env_add[i]); } } /* Reset our signals -- our parent may have done any number of * weird things to them. */ _pty_reset_signal_handlers(); /* Change to the requested directory. */ if (directory != NULL) { if (chdir(directory) == -1) { g_warning ("Error changing directory."); } } #ifdef HAVE_UTMP_H /* This sets stdin, stdout, stderr to the socket */ if (login && login_tty (fd) == -1) { g_printerr ("mount child process login_tty failed: %s\n", g_strerror (errno)); return -1; } #endif /* Signal to the parent that we've finished setting things up by * sending an arbitrary byte over the status pipe and waiting for * a response. This synchronization step ensures that the pty is * fully initialized before the parent process attempts to do anything * with it, and is required on systems where additional setup, beyond * merely opening the device, is required. This is at least the case * on Solaris. */ /* Initialize so valgrind doesn't complain */ c = 0; n_write(ready_writer, &c, 1); fsync(ready_writer); n_read(ready_reader, &c, 1); close(ready_writer); if (ready_writer != ready_reader) { close(ready_reader); } /* If the caller provided a command, we can't go back, ever. */ if (command != NULL) { /* Outta here. */ if (argv != NULL) { for (i = 0; (argv[i] != NULL); i++) ; args = g_malloc0(sizeof(char*) * (i + 1)); for (i = 0; (argv[i] != NULL); i++) { args[i] = g_strdup(argv[i]); } execvp(command, args); } else { arg = g_strdup(command); execlp(command, arg, NULL); } /* Avoid calling any atexit() code. */ _exit(0); g_assert_not_reached(); } return 0; } static int _pty_set_size(int master, int columns, int rows); /* Open the named PTY slave, fork off a child (storing its PID in child), * and exec the named command in its own session as a process group leader */ static int _pty_fork_on_pty_name(const char *path, int parent_fd, char **env_add, const char *command, char **argv, const char *directory, int columns, int rows, int *stdin_fd, int *stdout_fd, int *stderr_fd, int *slave_fd, pid_t *child, gboolean reapchild, gboolean login) { int fd, i; char c; int ready_a[2] = { 0, 0 }; int ready_b[2] = { 0, 0 }; pid_t pid, grandchild_pid; int pid_pipe[2]; int stdin_pipe[2]; int stdout_pipe[2]; int stderr_pipe[2]; /* Open pipes for synchronizing between parent and child. */ if (_pty_pipe_open_bi(&ready_a[0], &ready_a[1], &ready_b[0], &ready_b[1]) == -1) { /* Error setting up pipes. Bail. */ goto bail_ready; } if (reapchild && pipe(pid_pipe)) { /* Error setting up pipes. Bail. */ goto bail_pid; } if (pipe(stdin_pipe)) { /* Error setting up pipes. Bail. */ goto bail_stdin; } if (pipe(stdout_pipe)) { /* Error setting up pipes. Bail. */ goto bail_stdout; } if (pipe(stderr_pipe)) { /* Error setting up pipes. Bail. */ goto bail_stderr; } /* Open the slave PTY in the parent (but not as a controlling terminal) * otherwise later when we want to poll the master fd, POLLHUP is * returned if the process hasn't opened the slave side yet. */ *slave_fd = open(path, O_RDWR | O_NOCTTY); if (*slave_fd == -1) goto bail_slavefd; /* Start up a child. */ pid = fork(); switch (pid) { case -1: /* Error fork()ing. Bail. */ *child = -1; return -1; break; case 0: /* Child. Close the parent's ends of the pipes. */ close(parent_fd); close(ready_a[0]); close(ready_b[1]); close(stdin_pipe[1]); close(stdout_pipe[0]); close(stderr_pipe[0]); /* Close the slave PTY opened in the parent. It is later * opened as a controlling terminal. */ close (*slave_fd); if(reapchild) { close(pid_pipe[0]); /* Fork a intermediate child. This is needed to not * produce zombies! */ grandchild_pid = fork(); if (grandchild_pid < 0) { /* Error during fork! */ n_write (pid_pipe[1], &grandchild_pid, sizeof (grandchild_pid)); _exit (1); } else if (grandchild_pid > 0) { /* Parent! (This is the actual intermediate child; * so write the pid to the parent and then exit */ n_write (pid_pipe[1], &grandchild_pid, sizeof (grandchild_pid)); close (pid_pipe[1]); _exit (0); } /* Start a new session and become process-group leader. */ setsid(); setpgid(0, 0); } /* Close most descriptors. */ for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) { if ((i != ready_b[0]) && (i != ready_a[1]) && (i != stdin_pipe[0]) && (i != stdout_pipe[1]) && (i != stderr_pipe[1])) { close(i); } } /* Set up stdin/out/err */ dup2(stdin_pipe[0], STDIN_FILENO); close (stdin_pipe[0]); dup2(stdout_pipe[1], STDOUT_FILENO); close (stdout_pipe[1]); dup2(stderr_pipe[1], STDERR_FILENO); close (stderr_pipe[1]); /* Open the slave PTY, acquiring it as the controlling terminal * for this process and its children. */ fd = open(path, O_RDWR); if (fd == -1) { return -1; } #ifdef TIOCSCTTY /* TIOCSCTTY is defined? Let's try that, too. */ ioctl(fd, TIOCSCTTY, fd); #endif /* Store 0 as the "child"'s ID to indicate to the caller that * it is now the child. */ *child = 0; return _pty_run_on_pty(fd, login, stdin_pipe[1], stdout_pipe[1], stderr_pipe[1], ready_b[0], ready_a[1], env_add, command, argv, directory); break; default: /* Parent. Close the child's ends of the pipes, do the ready * handshake, and return the child's PID. */ close(ready_b[0]); close(ready_a[1]); close(stdin_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[1]); if (reapchild) { close(pid_pipe[1]); /* Reap the intermediate child */ wait_again: if (waitpid (pid, NULL, 0) < 0) { if (errno == EINTR) { goto wait_again; } else if (errno == ECHILD) { ; /* NOOP! Child already reaped. */ } else { g_warning ("waitpid() should not fail in pty-open.c"); } } /* * Read the child pid from the pid_pipe * */ if (n_read (pid_pipe[0], child, sizeof (pid_t)) != sizeof (pid_t) || *child == -1) { g_warning ("Error while spanning child!"); goto bail_fork; } close(pid_pipe[0]); } else { /* No intermediate child, simple */ *child = pid; } /* Wait for the child to be ready, set the window size, then * signal that we're ready. We need to synchronize here to * avoid possible races when the child has to do more setup * of the terminal than just opening it. */ n_read(ready_a[0], &c, 1); _pty_set_size(parent_fd, columns, rows); n_write(ready_b[1], &c, 1); close(ready_a[0]); close(ready_b[1]); *stdin_fd = stdin_pipe[1]; *stdout_fd = stdout_pipe[0]; *stderr_fd = stderr_pipe[0]; return 0; break; } g_assert_not_reached(); return -1; bail_fork: close(*slave_fd); bail_slavefd: close(stderr_pipe[0]); close(stderr_pipe[1]); bail_stderr: close(stdout_pipe[0]); close(stdout_pipe[1]); bail_stdout: close(stdin_pipe[0]); close(stdin_pipe[1]); bail_stdin: if(reapchild) { close(pid_pipe[0]); close(pid_pipe[1]); } bail_pid: close(ready_a[0]); close(ready_a[1]); close(ready_b[0]); close(ready_b[1]); bail_ready: *child = -1; return -1; } /** * pty_set_size: * @master: the file descriptor of the pty master * @columns: the desired number of columns * @rows: the desired number of rows * * Attempts to resize the pseudo terminal's window size. If successful, the * OS kernel will send #SIGWINCH to the child process group. * * Returns: 0 on success, -1 on failure. */ static int _pty_set_size(int master, int columns, int rows) { struct winsize size; int ret; memset(&size, 0, sizeof(size)); size.ws_row = rows ? rows : 24; size.ws_col = columns ? columns : 80; ret = ioctl(master, TIOCSWINSZ, &size); return ret; } static char * _pty_ptsname(int master) { #if defined(HAVE_PTSNAME_R) gsize len = 1024; char *buf = NULL; int i; do { buf = g_malloc0(len); i = ptsname_r(master, buf, len - 1); switch (i) { case 0: /* Return the allocated buffer with the name in it. */ return buf; break; default: g_free(buf); buf = NULL; break; } len *= 2; } while ((i != 0) && (errno == ERANGE)); #elif defined(HAVE_PTSNAME) char *p; if ((p = ptsname(master)) != NULL) { return g_strdup(p); } #elif defined(TIOCGPTN) int pty = 0; if (ioctl(master, TIOCGPTN, &pty) == 0) { return g_strdup_printf("/dev/pts/%d", pty); } #endif return NULL; } static int _pty_getpt(void) { int fd, flags; #ifdef HAVE_GETPT /* Call the system's function for allocating a pty. */ fd = getpt(); #elif defined(HAVE_POSIX_OPENPT) fd = posix_openpt(O_RDWR | O_NOCTTY); #else /* Try to allocate a pty by accessing the pty master multiplex. */ fd = open("/dev/ptmx", O_RDWR | O_NOCTTY); if ((fd == -1) && (errno == ENOENT)) { fd = open("/dev/ptc", O_RDWR | O_NOCTTY); /* AIX */ } #endif /* Set it to blocking. */ flags = fcntl(fd, F_GETFL); flags &= ~(O_NONBLOCK); fcntl(fd, F_SETFL, flags); return fd; } static int _pty_grantpt(int master) { #ifdef HAVE_GRANTPT return grantpt(master); #else return 0; #endif } static int _pty_unlockpt(int fd) { #ifdef HAVE_UNLOCKPT return unlockpt(fd); #elif defined(TIOCSPTLCK) int zero = 0; return ioctl(fd, TIOCSPTLCK, &zero); #else return -1; #endif } static int _pty_open_unix98(pid_t *child, guint flags, char **env_add, const char *command, char **argv, const char *directory, int columns, int rows, int *stdin_fd, int *stdout_fd, int *stderr_fd, int *slave_fd) { int fd; char *buf; /* Attempt to open the master. */ fd = _pty_getpt(); if (fd != -1) { /* Read the slave number and unlock it. */ if (((buf = _pty_ptsname(fd)) == NULL) || (_pty_grantpt(fd) != 0) || (_pty_unlockpt(fd) != 0)) { close(fd); fd = -1; } else { /* Start up a child process with the given command. */ if (_pty_fork_on_pty_name(buf, fd, env_add, command, argv, directory, columns, rows, stdin_fd, stdout_fd, stderr_fd, slave_fd, child, flags & PTY_REAP_CHILD, flags & PTY_LOGIN_TTY) != 0) { close(fd); fd = -1; } g_free(buf); } } return fd; } #elif defined(HAVE_OPENPTY) static int _pty_open_bsd(pid_t *child, const char *command, char **argv, int *stdin_fd, int *stdout_fd, int *stderr_fd, int *slave_fd) { int master; char **args, *arg; int stdin_pipe[2]; int stdout_pipe[2]; int stderr_pipe[2]; pid_t pid; int i; if (pipe(stdin_pipe)) goto bail_stdin; if (pipe(stdout_pipe)) goto bail_stdout; if (pipe(stderr_pipe)) goto bail_stderr; if (openpty(&master, slave_fd, NULL, NULL, NULL) == -1) return (-1); switch(pid = fork()) { case -1: goto bail_fork; case 0: /* * Child */ close(master); close(stdin_pipe[1]); close(stdout_pipe[0]); close(stderr_pipe[0]); setsid(); if (ioctl(*slave_fd, TIOCSCTTY, (char *)NULL) == -1) _exit(0); /* Set up stdin/out/err */ dup2(stdin_pipe[0], STDIN_FILENO); dup2(stdout_pipe[1], STDOUT_FILENO); dup2(stderr_pipe[1], STDERR_FILENO); close(stdin_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[1]); /* Reset our signals -- our parent may have done any number of * weird things to them. */ _pty_reset_signal_handlers(); /* Outta here. */ if (argv != NULL) { for (i = 0; (argv[i] != NULL); i++) ; args = g_malloc0(sizeof(char*) * (i + 1)); for (i = 0; (argv[i] != NULL); i++) { args[i] = g_strdup(argv[i]); } execvp(command, args); } else { arg = g_strdup(command); execlp(command, arg, NULL); } /* Avoid calling any atexit() code. */ _exit(0); g_assert_not_reached(); } /* * Parent */ close(stdin_pipe[0]); close(stdout_pipe[1]); close(stderr_pipe[1]); *child = pid; *stdin_fd = stdin_pipe[1]; *stdout_fd = stdout_pipe[0]; *stderr_fd = stderr_pipe[0]; return (master); bail_fork: close(stderr_pipe[0]); close(stderr_pipe[1]); bail_stderr: close(stdout_pipe[0]); close(stdout_pipe[1]); bail_stdout: close(stdin_pipe[0]); close(stdin_pipe[1]); bail_stdin: *child = -1; return -1; } #else #error Have neither UNIX98 PTY nor BSD openpty! #endif /* HAVE_UNIX98_PTY */ /** * pty_open: * @child: location to store the new process's ID * @env_add: a list of environment variables to add to the child's environment * @command: name of the binary to run * @argv: arguments to pass to @command * @directory: directory to start the new command in, or NULL * @columns: desired window columns * @rows: desired window rows * @lastlog: TRUE if the lastlog should be updated * @utmp: TRUE if the utmp or utmpx log should be updated * @wtmp: TRUE if the wtmp or wtmpx log should be updated * * Starts a new copy of @command running under a psuedo-terminal, optionally in * the supplied @directory, with window size set to @rows x @columns * and variables in @env_add added to its environment. If any combination of * @lastlog, @utmp, and @wtmp is set, then the session is logged in the * corresponding system files. * * Returns: an open file descriptor for the pty master, -1 on failure */ int pty_open(pid_t *child, guint flags, char **env_add, const char *command, char **argv, const char *directory, int columns, int rows, int *stdin_fd, int *stdout_fd, int *stderr_fd, int *slave_fd) { int ret = -1; #if defined(HAVE_UNIX98_PTY) ret = _pty_open_unix98(child, flags, env_add, command, argv, directory, columns, rows, stdin_fd, stdout_fd, stderr_fd, slave_fd); #elif defined(HAVE_OPENPTY) ret = _pty_open_bsd(child, command, argv, stdin_fd, stdout_fd, stderr_fd, slave_fd); #else #error Have neither UNIX98 PTY nor BSD openpty! #endif return ret; } #ifdef PTY_MAIN int fd; static void sigchld_handler(int signum) { } int main(int argc, char **argv) { pid_t child = 0; char c; int ret; signal(SIGCHLD, sigchld_handler); fd = pty_open(&child, 0, NULL, (argc > 1) ? argv[1] : NULL, (argc > 1) ? argv + 1 : NULL, NULL, 0, 0, NULL, NULL, NULL); if (child == 0) { int i; for (i = 0; ; i++) { switch (i % 3) { case 0: case 1: fprintf(stdout, "%d\n", i); break; case 2: fprintf(stderr, "%d\n", i); break; default: g_assert_not_reached(); break; } sleep(1); } } g_print("Child pid is %d.\n", (int)child); do { ret = n_read(fd, &c, 1); if (ret == 0) { break; } if ((ret == -1) && (errno != EAGAIN) && (errno != EINTR)) { break; } if (argc < 2) { n_write(STDOUT_FILENO, "[", 1); } n_write(STDOUT_FILENO, &c, 1); if (argc < 2) { n_write(STDOUT_FILENO, "]", 1); } } while (TRUE); return 0; } #endif