summaryrefslogtreecommitdiff
path: root/trunk/daemon/pty_open.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/daemon/pty_open.c')
-rw-r--r--trunk/daemon/pty_open.c843
1 files changed, 843 insertions, 0 deletions
diff --git a/trunk/daemon/pty_open.c b/trunk/daemon/pty_open.c
new file mode 100644
index 00000000..6f6d83e8
--- /dev/null
+++ b/trunk/daemon/pty_open.c
@@ -0,0 +1,843 @@
+/* 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., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ */
+
+/*
+ * 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", 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;
+}
+
+/* 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