/* * Copyright (C) 2001,2002 Red Hat, Inc. * Copyright © 2009, 2010 Christian Persch * * 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.1 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 */ /** * SECTION: vte-pty * @short_description: Functions for starting a new process on a new pseudo-terminal and for * manipulating pseudo-terminals * * The terminal widget uses these functions to start commands with new controlling * pseudo-terminals and to resize pseudo-terminals. */ #include #include #include "vtepty-private.h" #include #include #include #ifdef HAVE_SYS_TERMIOS_H #include #endif #include #include #include #ifdef HAVE_SYS_SYSLIMITS_H #include #endif #include #include #include #include #ifdef HAVE_TERMIOS_H #include #endif #include #ifdef HAVE_UTIL_H #include #endif #ifdef HAVE_STROPTS_H #include #endif #include #include #include "debug.h" #include #if defined(HAVE_PTSNAME_R) || defined(HAVE_PTSNAME) || defined(TIOCGPTN) #define HAVE_UNIX98_PTY #else #undef HAVE_UNIX98_PTY #endif #define VTE_VERSION_NUMERIC ((VTE_MAJOR_VERSION) * 10000 + (VTE_MINOR_VERSION) * 100 + (VTE_MICRO_VERSION)) #if !GLIB_CHECK_VERSION(2, 42, 0) #define G_PARAM_EXPLICIT_NOTIFY 0 #endif #define I_(string) (g_intern_static_string(string)) /* 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 _vte_pty_reset_signal_handlers(void) { #ifdef SIGHUP signal(SIGHUP, SIG_DFL); #endif signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGABRT, SIG_DFL); signal(SIGFPE, SIG_DFL); #ifdef SIGKILL signal(SIGKILL, SIG_DFL); #endif signal(SIGSEGV, SIG_DFL); #ifdef SIGPIPE signal(SIGPIPE, SIG_DFL); #endif #ifdef SIGALRM signal(SIGALRM, SIG_DFL); #endif signal(SIGTERM, SIG_DFL); #ifdef SIGCHLD signal(SIGCHLD, SIG_DFL); #endif #ifdef SIGCONT signal(SIGCONT, SIG_DFL); #endif #ifdef SIGSTOP signal(SIGSTOP, SIG_DFL); #endif #ifdef SIGTSTP signal(SIGTSTP, SIG_DFL); #endif #ifdef SIGTTIN signal(SIGTTIN, SIG_DFL); #endif #ifdef SIGTTOU signal(SIGTTOU, SIG_DFL); #endif #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 } typedef struct _VtePtyPrivate VtePtyPrivate; enum TtyOpenMode { TTY_OPEN_BY_NAME, TTY_OPEN_BY_FD }; typedef struct { enum TtyOpenMode mode; union { const char *name; int fd; } tty; GSpawnChildSetupFunc extra_child_setup; gpointer extra_child_setup_data; } VtePtyChildSetupData; /** * VtePty: */ struct _VtePty { GObject parent_instance; /* */ VtePtyPrivate *priv; }; struct _VtePtyPrivate { VtePtyFlags flags; int pty_fd; VtePtyChildSetupData child_setup_data; guint utf8 : 1; guint foreign : 1; }; struct _VtePtyClass { GObjectClass parent_class; }; /** * vte_pty_child_setup: * @pty: a #VtePty * * FIXMEchpe */ void vte_pty_child_setup (VtePty *pty) { VtePtyPrivate *priv = pty->priv; VtePtyChildSetupData *data = &priv->child_setup_data; int fd = -1; const char *tty = NULL; char version[7]; if (priv->foreign) { fd = priv->pty_fd; } else { /* Save the name of the pty -- we'll need it later to acquire * it as our controlling terminal. */ switch (data->mode) { case TTY_OPEN_BY_NAME: tty = data->tty.name; break; case TTY_OPEN_BY_FD: fd = data->tty.fd; tty = ttyname(fd); break; } _vte_debug_print (VTE_DEBUG_PTY, "Setting up child pty: name = %s, fd = %d\n", tty ? tty : "(none)", fd); /* Try to reopen the pty to acquire it as our controlling terminal. */ /* FIXMEchpe: why not just use the passed fd in TTY_OPEN_BY_FD mode? */ if (tty != NULL) { int i = open(tty, O_RDWR); if (i != -1) { if (fd != -1){ close(fd); } fd = i; } } } if (fd == -1) _exit (127); /* Start a new session and become process-group leader. */ #if defined(HAVE_SETSID) && defined(HAVE_SETPGID) _vte_debug_print (VTE_DEBUG_PTY, "Starting new session\n"); setsid(); setpgid(0, 0); #endif #ifdef TIOCSCTTY /* TIOCSCTTY is defined? Let's try that, too. */ ioctl(fd, TIOCSCTTY, fd); #endif #ifdef HAVE_STROPTS_H if (isastream (fd) == 1) { if ((ioctl(fd, I_FIND, "ptem") == 0) && (ioctl(fd, I_PUSH, "ptem") == -1)) { _exit (127); } if ((ioctl(fd, I_FIND, "ldterm") == 0) && (ioctl(fd, I_PUSH, "ldterm") == -1)) { _exit (127); } if ((ioctl(fd, I_FIND, "ttcompat") == 0) && (ioctl(fd, I_PUSH, "ttcompat") == -1)) { perror ("ioctl (fd, I_PUSH, \"ttcompat\")"); _exit (127); } } #endif /* now setup child I/O through the tty */ if (fd != STDIN_FILENO) { if (dup2(fd, STDIN_FILENO) != STDIN_FILENO){ _exit (127); } } if (fd != STDOUT_FILENO) { if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO){ _exit (127); } } if (fd != STDERR_FILENO) { if (dup2(fd, STDERR_FILENO) != STDERR_FILENO){ _exit (127); } } /* Close the original slave descriptor, unless it's one of the stdio * descriptors. */ if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) { close(fd); } /* Reset our signals -- our parent may have done any number of * weird things to them. */ _vte_pty_reset_signal_handlers(); /* Now set the TERM environment variable */ /* FIXME: Setting environment here seems to have no effect, the merged envp2 will override on exec. * By the way, we'd need to set the one from there, if any. */ g_setenv("TERM", VTE_DEFAULT_TERM, TRUE); g_snprintf (version, sizeof (version), "%u", VTE_VERSION_NUMERIC); g_setenv ("VTE_VERSION", version, TRUE); /* Finally call an extra child setup */ if (data->extra_child_setup) { data->extra_child_setup (data->extra_child_setup_data); } } /* TODO: clean up the spawning * - replace current env rather than adding! */ /* * __vte_pty_merge_environ: * @envp: environment vector * @inherit: whether to use the parent environment * * Merges @envp to the parent environment, and returns a new environment vector. * * Returns: a newly allocated string array. Free using g_strfreev() */ static gchar ** __vte_pty_merge_environ (char **envp, gboolean inherit) { GHashTable *table; GHashTableIter iter; char *name, *value; gchar **parent_environ; GPtrArray *array; gint i; table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); if (inherit) { parent_environ = g_listenv (); for (i = 0; parent_environ[i] != NULL; i++) { g_hash_table_replace (table, g_strdup (parent_environ[i]), g_strdup (g_getenv (parent_environ[i]))); } g_strfreev (parent_environ); } /* Make sure the one in envp overrides the default. */ g_hash_table_replace (table, g_strdup ("TERM"), g_strdup (VTE_DEFAULT_TERM)); if (envp != NULL) { for (i = 0; envp[i] != NULL; i++) { name = g_strdup (envp[i]); value = strchr (name, '='); if (value) { *value = '\0'; value = g_strdup (value + 1); } g_hash_table_replace (table, name, value); } } g_hash_table_replace (table, g_strdup ("VTE_VERSION"), g_strdup_printf ("%u", VTE_VERSION_NUMERIC)); array = g_ptr_array_sized_new (g_hash_table_size (table) + 1); g_hash_table_iter_init(&iter, table); while (g_hash_table_iter_next(&iter, (void**) &name, (void**) &value)) { g_ptr_array_add (array, g_strconcat (name, "=", value, NULL)); } g_assert(g_hash_table_size(table) == array->len); g_hash_table_destroy (table); g_ptr_array_add (array, NULL); return (gchar **) g_ptr_array_free (array, FALSE); } /* * __vte_pty_spawn: * @pty: a #VtePty * @directory: the name of a directory the command should start in, or %NULL * to use the cwd * @argv: child's argument vector * @envv: a list of environment variables to be added to the environment before * starting the process, or %NULL * @spawn_flags: flags from #GSpawnFlags * @child_setup: function to run in the child just before exec() * @child_setup_data: user data for @child_setup * @child_pid: a location to store the child PID, or %NULL * @error: return location for a #GError, or %NULL * * Uses g_spawn_async() to spawn the command in @argv. The child's environment will * be the parent environment with the variables in @envv set afterwards. * * Enforces the vte_terminal_watch_child() requirements by adding * %G_SPAWN_DO_NOT_REAP_CHILD to @spawn_flags. * * Note that the %G_SPAWN_LEAVE_DESCRIPTORS_OPEN flag is not supported; * it will be cleared! * * If spawning the command in @working_directory fails because the child * is unable to chdir() to it, falls back trying to spawn the command * in the parent's working directory. * * Returns: %TRUE on success, or %FALSE on failure with @error filled in */ gboolean __vte_pty_spawn (VtePty *pty, const char *directory, char **argv, char **envv, GSpawnFlags spawn_flags_, GSpawnChildSetupFunc child_setup, gpointer child_setup_data, GPid *child_pid /* out */, GError **error) { VtePtyPrivate *priv = pty->priv; VtePtyChildSetupData *data = &priv->child_setup_data; guint spawn_flags = (guint) spawn_flags_; gboolean ret = TRUE; gboolean inherit_envv; char **envp2; gint i; GError *err = NULL; spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD; /* FIXMEchpe: Enforce this until I've checked our code to make sure * it doesn't leak out internal FDs into the child this way. */ spawn_flags &= ~G_SPAWN_LEAVE_DESCRIPTORS_OPEN; inherit_envv = (spawn_flags & VTE_SPAWN_NO_PARENT_ENVV) == 0; spawn_flags &= ~VTE_SPAWN_NO_PARENT_ENVV; /* add the given environment to the childs */ envp2 = __vte_pty_merge_environ (envv, inherit_envv); _VTE_DEBUG_IF (VTE_DEBUG_MISC) { g_printerr ("Spawing command:\n"); for (i = 0; argv[i] != NULL; i++) { g_printerr (" argv[%d] = %s\n", i, argv[i]); } for (i = 0; envp2[i] != NULL; i++) { g_printerr (" env[%d] = %s\n", i, envp2[i]); } g_printerr (" directory: %s\n", directory ? directory : "(none)"); } data->extra_child_setup = child_setup; data->extra_child_setup_data = child_setup_data; ret = g_spawn_async_with_pipes(directory, argv, envp2, (GSpawnFlags) spawn_flags, (GSpawnChildSetupFunc) vte_pty_child_setup, pty, child_pid, NULL, NULL, NULL, &err); if (!ret && directory != NULL && g_error_matches(err, G_SPAWN_ERROR, G_SPAWN_ERROR_CHDIR)) { /* try spawning in our working directory */ g_clear_error(&err); ret = g_spawn_async_with_pipes(NULL, argv, envp2, (GSpawnFlags) spawn_flags, (GSpawnChildSetupFunc) vte_pty_child_setup, pty, child_pid, NULL, NULL, NULL, &err); } g_strfreev (envp2); data->extra_child_setup = NULL; data->extra_child_setup_data = NULL; if (ret) return TRUE; g_propagate_error (error, err); return FALSE; } /** * vte_pty_set_size: * @pty: a #VtePty * @rows: the desired number of rows * @columns: the desired number of columns * @error: (allow-none): return location to store a #GError, or %NULL * * Attempts to resize the pseudo terminal's window size. If successful, the * OS kernel will send #SIGWINCH to the child process group. * * If setting the window size failed, @error will be set to a #GIOError. * * Returns: %TRUE on success, %FALSE on failure with @error filled in */ gboolean vte_pty_set_size(VtePty *pty, int rows, int columns, GError **error) { struct winsize size; int master; int ret; g_return_val_if_fail(VTE_IS_PTY(pty), FALSE); master = vte_pty_get_fd(pty); memset(&size, 0, sizeof(size)); size.ws_row = rows > 0 ? rows : 24; size.ws_col = columns > 0 ? columns : 80; _vte_debug_print(VTE_DEBUG_PTY, "Setting size on fd %d to (%d,%d).\n", master, columns, rows); ret = ioctl(master, TIOCSWINSZ, &size); if (ret != 0) { int errsv = errno; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to set window size: %s", g_strerror(errsv)); _vte_debug_print(VTE_DEBUG_PTY, "Failed to set size on %d: %s.\n", master, g_strerror(errsv)); errno = errsv; return FALSE; } return TRUE; } /** * vte_pty_get_size: * @pty: a #VtePty * @rows: (out) (allow-none): a location to store the number of rows, or %NULL * @columns: (out) (allow-none): a location to store the number of columns, or %NULL * @error: return location to store a #GError, or %NULL * * Reads the pseudo terminal's window size. * * If getting the window size failed, @error will be set to a #GIOError. * * Returns: %TRUE on success, %FALSE on failure with @error filled in */ gboolean vte_pty_get_size(VtePty *pty, int *rows, int *columns, GError **error) { struct winsize size; int master; int ret; g_return_val_if_fail(VTE_IS_PTY(pty), FALSE); master = vte_pty_get_fd(pty); memset(&size, 0, sizeof(size)); ret = ioctl(master, TIOCGWINSZ, &size); if (ret == 0) { if (columns != NULL) { *columns = size.ws_col; } if (rows != NULL) { *rows = size.ws_row; } _vte_debug_print(VTE_DEBUG_PTY, "Size on fd %d is (%d,%d).\n", master, size.ws_col, size.ws_row); return TRUE; } else { int errsv = errno; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "Failed to get window size: %s", g_strerror(errsv)); _vte_debug_print(VTE_DEBUG_PTY, "Failed to read size from fd %d: %s\n", master, g_strerror(errsv)); errno = errsv; return FALSE; } } #if defined(HAVE_UNIX98_PTY) /* * _vte_pty_ptsname: * @master: file descriptor to the PTY master * @error: a location to store a #GError, or %NULL * * Returns: a newly allocated string containing the file name of the * PTY slave device, or %NULL on failure with @error filled in */ static char * _vte_pty_ptsname(int master, GError **error) { #if defined(HAVE_PTSNAME_R) gsize len = 1024; char *buf = NULL; int i, errsv; do { buf = (char *) g_malloc0(len); i = ptsname_r(master, buf, len - 1); switch (i) { case 0: /* Return the allocated buffer with the name in it. */ _vte_debug_print(VTE_DEBUG_PTY, "PTY slave is `%s'.\n", buf); return buf; break; default: errsv = errno; g_free(buf); errno = errsv; buf = NULL; break; } len *= 2; } while ((i != 0) && (errno == ERANGE)); g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "ptsname_r", g_strerror(errno)); return NULL; #elif defined(HAVE_PTSNAME) char *p; if ((p = ptsname(master)) != NULL) { _vte_debug_print(VTE_DEBUG_PTY, "PTY slave is `%s'.\n", p); return g_strdup(p); } g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "ptsname", g_strerror(errno)); return NULL; #elif defined(TIOCGPTN) int pty = 0; if (ioctl(master, TIOCGPTN, &pty) == 0) { _vte_debug_print(VTE_DEBUG_PTY, "PTY slave is `/dev/pts/%d'.\n", pty); return g_strdup_printf("/dev/pts/%d", pty); } g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "ioctl(TIOCGPTN)", g_strerror(errno)); return NULL; #else #error no ptsname implementation for this platform #endif } /* * _vte_pty_getpt: * @error: a location to store a #GError, or %NULL * * Opens a file descriptor for the next available PTY master. * Sets the descriptor to blocking mode! * * Returns: a new file descriptor, or %-1 on failure */ static int _vte_pty_getpt(GError **error) { int fd, flags, rv; #if defined(HAVE_POSIX_OPENPT) fd = posix_openpt(O_RDWR | O_NOCTTY); #elif defined(HAVE_GETPT) /* Call the system's function for allocating a pty. */ fd = getpt(); #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 if (fd == -1) { g_set_error (error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "getpt", g_strerror(errno)); return -1; } rv = fcntl(fd, F_GETFL, 0); if (rv < 0) { int errsv = errno; g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "fcntl(F_GETFL)", g_strerror(errno)); close(fd); errno = errsv; return -1; } /* Set it to blocking. */ /* FIXMEchpe: why?? vte_terminal_set_pty does the inverse... */ flags = rv & ~(O_NONBLOCK); rv = fcntl(fd, F_SETFL, flags); if (rv < 0) { int errsv = errno; g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "fcntl(F_SETFL)", g_strerror(errno)); close(fd); errno = errsv; return -1; } return fd; } static gboolean _vte_pty_grantpt(int master, GError **error) { #ifdef HAVE_GRANTPT int rv; rv = grantpt(master); if (rv != 0) { int errsv = errno; g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "grantpt", g_strerror(errsv)); errno = errsv; return FALSE; } #endif return TRUE; } static gboolean _vte_pty_unlockpt(int fd, GError **error) { int rv; #ifdef HAVE_UNLOCKPT rv = unlockpt(fd); if (rv != 0) { int errsv = errno; g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "unlockpt", g_strerror(errsv)); errno = errsv; return FALSE; } return TRUE; #elif defined(TIOCSPTLCK) int zero = 0; rv = ioctl(fd, TIOCSPTLCK, &zero); if (rv != 0) { int errsv = errno; g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "ioctl(TIOCSPTLCK)", g_strerror(errsv)); errno = errsv; return FALSE; } return TRUE; #else #error no unlockpt implementation for this platform #endif } /* * _vte_pty_open_unix98: * @pty: a #VtePty * @error: a location to store a #GError, or %NULL * * Opens a new file descriptor to a new PTY master. * * Returns: %TRUE on success, %FALSE on failure with @error filled in */ static gboolean _vte_pty_open_unix98(VtePty *pty, GError **error) { VtePtyPrivate *priv = pty->priv; int fd; char *buf; /* Attempt to open the master. */ fd = _vte_pty_getpt(error); if (fd == -1) return FALSE; _vte_debug_print(VTE_DEBUG_PTY, "Allocated pty on fd %d.\n", fd); /* Read the slave number and unlock it. */ if ((buf = _vte_pty_ptsname(fd, error)) == NULL || !_vte_pty_grantpt(fd, error) || !_vte_pty_unlockpt(fd, error)) { int errsv = errno; _vte_debug_print(VTE_DEBUG_PTY, "PTY setup failed, bailing.\n"); close(fd); errno = errsv; return FALSE; } priv->pty_fd = fd; priv->child_setup_data.mode = TTY_OPEN_BY_NAME; priv->child_setup_data.tty.name = buf; return TRUE; } #elif defined(HAVE_OPENPTY) /* * _vte_pty_open_bsd: * @pty: a #VtePty * @error: a location to store a #GError, or %NULL * * Opens new file descriptors to a new PTY master and slave. * * Returns: %TRUE on success, %FALSE on failure with @error filled in */ static gboolean _vte_pty_open_bsd(VtePty *pty, GError **error) { VtePtyPrivate *priv = pty->priv; int parentfd, childfd; if (openpty(&parentfd, &childfd, NULL, NULL, NULL) != 0) { int errsv = errno; g_set_error(error, VTE_PTY_ERROR, VTE_PTY_ERROR_PTY98_FAILED, "%s failed: %s", "openpty", g_strerror(errsv)); errno = errsv; return FALSE; } priv->pty_fd = parentfd; priv->child_setup_data.mode = TTY_OPEN_BY_FD; priv->child_setup_data.tty.fd = childfd; return TRUE; } #else #error Have neither UNIX98 PTY nor BSD openpty! #endif /* HAVE_UNIX98_PTY */ /** * vte_pty_set_utf8: * @pty: a #VtePty * @utf8: whether or not the pty is in UTF-8 mode * @error: (allow-none): return location to store a #GError, or %NULL * * Tells the kernel whether the terminal is UTF-8 or not, in case it can make * use of the info. Linux 2.6.5 or so defines IUTF8 to make the line * discipline do multibyte backspace correctly. * * Returns: %TRUE on success, %FALSE on failure with @error filled in */ gboolean vte_pty_set_utf8(VtePty *pty, gboolean utf8, GError **error) { #if defined(HAVE_TCSETATTR) && defined(IUTF8) VtePtyPrivate *priv; struct termios tio; tcflag_t saved_cflag; g_return_val_if_fail(VTE_IS_PTY(pty), FALSE); priv = pty->priv; g_return_val_if_fail (priv->pty_fd != -1, FALSE); if (tcgetattr(priv->pty_fd, &tio) == -1) { int errsv = errno; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "%s failed: %s", "tcgetattr", g_strerror(errsv)); errno = errsv; return FALSE; } saved_cflag = tio.c_iflag; if (utf8) { tio.c_iflag |= IUTF8; } else { tio.c_iflag &= ~IUTF8; } /* Only set the flag if it changes */ if (saved_cflag != tio.c_iflag && tcsetattr(priv->pty_fd, TCSANOW, &tio) == -1) { int errsv = errno; g_set_error(error, G_IO_ERROR, g_io_error_from_errno(errsv), "%s failed: %s", "tcgetattr", g_strerror(errsv)); errno = errsv; return FALSE; } #endif return TRUE; } /** * vte_pty_close: * @pty: a #VtePty * * Since 0.42 this is a no-op. * * Deprecated: 0.42 */ void vte_pty_close (VtePty *pty) { } /* VTE PTY class */ enum { PROP_0, PROP_FLAGS, PROP_FD, }; /* GInitable impl */ static gboolean vte_pty_initable_init (GInitable *initable, GCancellable *cancellable, GError **error) { VtePty *pty = VTE_PTY (initable); VtePtyPrivate *priv = pty->priv; gboolean ret = FALSE; if (cancellable != NULL) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Cancellable initialisation not supported"); return FALSE; } /* If we already have a (foreign) FD, we're done. */ if (priv->foreign) { g_assert(priv->pty_fd != -1); return TRUE; } #if defined(HAVE_UNIX98_PTY) ret = _vte_pty_open_unix98(pty, error); #elif defined(HAVE_OPENPTY) ret = _vte_pty_open_bsd(pty, error); #else #error Have neither UNIX98 PTY nor BSD openpty! #endif _vte_debug_print(VTE_DEBUG_PTY, "vte_pty_initable_init returning %s with ptyfd = %d\n", ret ? "TRUE" : "FALSE", priv->pty_fd); return ret; } static void vte_pty_initable_iface_init (GInitableIface *iface) { iface->init = vte_pty_initable_init; } /* GObjectClass impl */ G_DEFINE_TYPE_WITH_CODE (VtePty, vte_pty, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, vte_pty_initable_iface_init)) static void vte_pty_init (VtePty *pty) { VtePtyPrivate *priv; priv = pty->priv = G_TYPE_INSTANCE_GET_PRIVATE (pty, VTE_TYPE_PTY, VtePtyPrivate); priv->flags = VTE_PTY_DEFAULT; priv->pty_fd = -1; priv->foreign = FALSE; } static void vte_pty_finalize (GObject *object) { VtePty *pty = VTE_PTY (object); VtePtyPrivate *priv = pty->priv; if (priv->child_setup_data.mode == TTY_OPEN_BY_FD && priv->child_setup_data.tty.fd != -1) { /* Close the child FD */ close(priv->child_setup_data.tty.fd); } /* Close the master FD */ if (priv->pty_fd != -1) { close(priv->pty_fd); } G_OBJECT_CLASS (vte_pty_parent_class)->finalize (object); } static void vte_pty_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { VtePty *pty = VTE_PTY (object); VtePtyPrivate *priv = pty->priv; switch (property_id) { case PROP_FLAGS: g_value_set_flags(value, priv->flags); break; case PROP_FD: g_value_set_int(value, vte_pty_get_fd(pty)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } } static void vte_pty_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { VtePty *pty = VTE_PTY (object); VtePtyPrivate *priv = pty->priv; switch (property_id) { case PROP_FLAGS: priv->flags = (VtePtyFlags) g_value_get_flags(value); break; case PROP_FD: priv->pty_fd = g_value_get_int(value); priv->foreign = (priv->pty_fd != -1); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void vte_pty_class_init (VtePtyClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); g_type_class_add_private(object_class, sizeof(VtePtyPrivate)); object_class->set_property = vte_pty_set_property; object_class->get_property = vte_pty_get_property; object_class->finalize = vte_pty_finalize; /** * VtePty:flags: * * Flags. */ g_object_class_install_property (object_class, PROP_FLAGS, g_param_spec_flags ("flags", NULL, NULL, VTE_TYPE_PTY_FLAGS, VTE_PTY_DEFAULT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY))); /** * VtePty:fd: * * The file descriptor of the PTY master. */ g_object_class_install_property (object_class, PROP_FD, g_param_spec_int ("fd", NULL, NULL, -1, G_MAXINT, -1, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY))); } /* public API */ /** * vte_pty_error_quark: * * Error domain for VTE PTY errors. Errors in this domain will be from the #VtePtyError * enumeration. See #GError for more information on error domains. * * Returns: the error domain for VTE PTY errors */ GQuark vte_pty_error_quark(void) { static GQuark quark = 0; if (G_UNLIKELY (quark == 0)) quark = g_quark_from_static_string("vte-pty-error"); return quark; } /** * vte_pty_new_sync: (constructor) * @flags: flags from #VtePtyFlags * @cancellable: (allow-none): a #GCancellable, or %NULL * @error: (allow-none): return location for a #GError, or %NULL * * Allocates a new pseudo-terminal. * * You can later use fork() or the g_spawn_async() family of functions * to start a process on the PTY. * * If using fork(), you MUST call vte_pty_child_setup() in the child. * * If using g_spawn_async() and friends, you MUST either use * vte_pty_child_setup() directly as the child setup function, or call * vte_pty_child_setup() from your own child setup function supplied. * * When using vte_terminal_spawn_sync() with a custom child setup * function, vte_pty_child_setup() will be called before the supplied * function; you must not call it again. * * Also, you MUST pass the %G_SPAWN_DO_NOT_REAP_CHILD flag. * * Returns: (transfer full): a new #VtePty, or %NULL on error with @error filled in */ VtePty * vte_pty_new_sync (VtePtyFlags flags, GCancellable *cancellable, GError **error) { return (VtePty *) g_initable_new (VTE_TYPE_PTY, cancellable, error, "flags", flags, NULL); } /** * vte_pty_new_foreign_sync: (constructor) * @fd: (transfer full): a file descriptor to the PTY * @cancellable: (allow-none): a #GCancellable, or %NULL * @error: (allow-none): return location for a #GError, or %NULL * * Creates a new #VtePty for the PTY master @fd. * * No entry will be made in the lastlog, utmp or wtmp system files. * * Note that the newly created #VtePty will take ownership of @fd * and close it on finalize. * * Returns: (transfer full): a new #VtePty for @fd, or %NULL on error with @error filled in */ VtePty * vte_pty_new_foreign_sync (int fd, GCancellable *cancellable, GError **error) { g_return_val_if_fail(fd >= 0, NULL); return (VtePty *) g_initable_new (VTE_TYPE_PTY, cancellable, error, "fd", fd, NULL); } /** * vte_pty_get_fd: * @pty: a #VtePty * * Returns: (transfer none): the file descriptor of the PTY master in @pty. The * file descriptor belongs to @pty and must not be closed */ int vte_pty_get_fd (VtePty *pty) { VtePtyPrivate *priv; g_return_val_if_fail(VTE_IS_PTY(pty), -1); priv = pty->priv; g_return_val_if_fail(priv->pty_fd != -1, -1); return priv->pty_fd; }