diff options
author | Alexander Larsson <alexl@src.gnome.org> | 2007-09-13 15:06:37 +0000 |
---|---|---|
committer | Alexander Larsson <alexl@src.gnome.org> | 2007-09-13 15:06:37 +0000 |
commit | d09a97e68789db1bd341a716d834239012d5a64d (patch) | |
tree | fbc83ae77e49b2b472779e6a5e7c299d5a041a11 /daemon/pty_open.c | |
parent | 8ab127f09890f455f494cfe966d335f79036a8e5 (diff) | |
download | gvfs-d09a97e68789db1bd341a716d834239012d5a64d.tar.gz |
Initial sftp code (doesn't do anything yet)
Original git commit by Alexander Larsson <alexl@redhat.com> at 1189150726 +0200
svn path=/trunk/; revision=897
Diffstat (limited to 'daemon/pty_open.c')
-rw-r--r-- | daemon/pty_open.c | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/daemon/pty_open.c b/daemon/pty_open.c new file mode 100644 index 00000000..76a5bedc --- /dev/null +++ b/daemon/pty_open.c @@ -0,0 +1,821 @@ +/* + * 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 <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_STROPTS_H +#include <stropts.h> +#endif +#ifdef HAVE_TERMIOS_H +#include <termios.h> +#endif +#ifdef HAVE_UTMP_H +#include <utmp.h> +#endif +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#include <glib.h> +#include "pty_open.h" + +int _pty_set_size(int master, int columns, int rows); + +/* 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_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) { + chdir(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", 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; +} + +/* 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, + 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; + } + + /* 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(ready_a[0]); + close(ready_b[1]); + + close(stdin_pipe[1]); + close(stdout_pipe[0]); + close(stderr_pipe[0]); + + 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(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. + */ +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 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, + child, + flags & PTY_REAP_CHILD, + flags & PTY_LOGIN_TTY) != 0) { + close(fd); + fd = -1; + } + g_free(buf); + } + } + return fd; +} + +/** + * 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 ret = -1; + if (ret == -1) { + ret = _pty_open_unix98(child, flags, env_add, command, + argv, directory, columns, rows, + stdin_fd, stdout_fd, stderr_fd); + } + 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 |