diff --git a/doc/reference/tmpl/vte.sgml b/doc/reference/tmpl/vte.sgml
index 8a3cc31..b5bba14 100644
--- a/doc/reference/tmpl/vte.sgml
+++ b/doc/reference/tmpl/vte.sgml
@@ -450,6 +450,24 @@ for the terminal.
 @Returns: 
 
 
+<!-- ##### FUNCTION vte_terminal_fork_command_full ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@command: 
+@argv: 
+@envv: 
+@directory: 
+@fork_flags: 
+@spawn_flags: 
+@child_setup: 
+@child_setup_data: 
+@error: 
+@Returns: 
+
+
 <!-- ##### FUNCTION vte_terminal_forkpty ##### -->
 <para>
 
@@ -464,6 +482,20 @@ for the terminal.
 @Returns: 
 
 
+<!-- ##### FUNCTION vte_terminal_forkpty_full ##### -->
+<para>
+
+</para>
+
+@terminal: 
+@envv: 
+@directory: 
+@fork_flags: 
+@spawn_flags: 
+@error: 
+@Returns: 
+
+
 <!-- ##### FUNCTION vte_terminal_set_pty ##### -->
 <para>
 
diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt
index edfa51b..400a9a6 100644
--- a/doc/reference/vte-sections.txt
+++ b/doc/reference/vte-sections.txt
@@ -8,7 +8,9 @@ VteTerminalCursorBlinkMode
 vte_terminal_new
 vte_terminal_im_append_menuitems
 vte_terminal_fork_command
+vte_terminal_fork_command_full
 vte_terminal_forkpty
+vte_terminal_forkpty_full
 vte_terminal_set_pty
 vte_terminal_get_pty
 vte_terminal_feed
diff --git a/src/pty.c b/src/pty.c
index 4d90bb9..9945411 100644
--- a/src/pty.c
+++ b/src/pty.c
@@ -17,6 +17,7 @@
  */
 
 #include <config.h>
+
 #include <sys/types.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -153,7 +154,11 @@ struct vte_pty_child_setup_data {
 		const char *name;
 		int fd;
 	} tty;
+        GSpawnFlags spawn_flags;
+        GSpawnChildSetupFunc child_setup;
+        gpointer child_setup_data;
 };
+
 static void
 vte_pty_child_setup (gpointer arg)
 {
@@ -246,10 +251,14 @@ vte_pty_child_setup (gpointer arg)
 		close(fd);
 	}
 
-
 	/* Reset our signals -- our parent may have done any number of
 	 * weird things to them. */
 	_vte_pty_reset_signal_handlers();
+
+        /* Now call the extra child setup */
+        if (data->child_setup) {
+                data->child_setup (data->child_setup_data);
+        }
 }
 
 /* TODO: clean up the spawning
@@ -307,14 +316,16 @@ static gboolean
 _vte_pty_run_on_pty (struct vte_pty_child_setup_data *data,
 		     const char *command, char **argv, char **envp,
 		     const char *directory,
-		     GPid *pid, GError **error)
+		     GPid *pid,
+                     GError **error)
 {
 	gboolean ret = TRUE;
-	GError *local_error = NULL;
 
 	if (command != NULL) {
 		gchar **arg2, **envp2;
 		gint i, argc;
+                GSpawnFlags spawn_flags;
+                GError *local_error = NULL;
 
 		/* push the command into argv[0] */
 		argc = argv ? g_strv_length (argv) : 0;
@@ -340,12 +351,14 @@ _vte_pty_run_on_pty (struct vte_pty_child_setup_data *data,
 					directory ? directory : "(none)");
 		}
 
+                spawn_flags = data->spawn_flags;
+                if (argv) {
+                        spawn_flags |= G_SPAWN_FILE_AND_ARGV_ZERO;
+                }
+
 		ret = g_spawn_async_with_pipes (directory,
 				arg2, envp2,
-				G_SPAWN_CHILD_INHERITS_STDIN |
-				G_SPAWN_SEARCH_PATH |
-				G_SPAWN_DO_NOT_REAP_CHILD |
-				(argv ? G_SPAWN_FILE_AND_ARGV_ZERO : 0),
+                                spawn_flags,
 				vte_pty_child_setup, data,
 				pid,
 				NULL, NULL, NULL,
@@ -358,10 +371,7 @@ _vte_pty_run_on_pty (struct vte_pty_child_setup_data *data,
 				g_clear_error (&local_error);
 				ret = g_spawn_async_with_pipes (NULL,
 						arg2, envp2,
-						G_SPAWN_CHILD_INHERITS_STDIN |
-						G_SPAWN_SEARCH_PATH |
-						G_SPAWN_DO_NOT_REAP_CHILD |
-						(argv ? G_SPAWN_FILE_AND_ARGV_ZERO : 0),
+                                                spawn_flags,
 						vte_pty_child_setup, data,
 						pid,
 						NULL, NULL, NULL,
@@ -377,17 +387,16 @@ _vte_pty_run_on_pty (struct vte_pty_child_setup_data *data,
 				ret?"":local_error->message);
 		if (local_error)
 			g_propagate_error (error, local_error);
-	}
+	} else {
 #ifdef HAVE_FORK
-	else {
 		*pid = fork();
 		switch (*pid) {
 			case -1:
 				g_set_error (error,
-						G_SPAWN_ERROR,
-						G_SPAWN_ERROR_FAILED,
-						"Unable to fork: %s",
-						g_strerror (errno));
+                                             G_SPAWN_ERROR,
+                                             G_SPAWN_ERROR_FAILED,
+                                             "Unable to fork: %s",
+                                             g_strerror (errno));
 				ret = FALSE;
 			case 0: /* child */
 				vte_pty_child_setup (data);
@@ -395,8 +404,12 @@ _vte_pty_run_on_pty (struct vte_pty_child_setup_data *data,
 			default: /* parent */
 				break;
 		}
-	}
+#else
+                g_set_error_literal (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED,
+                                    "No fork implementation");
+                ret = FALSE;
 #endif
+	}
 
 	return ret;
 }
@@ -407,17 +420,23 @@ static gboolean
 _vte_pty_fork_on_pty_name (const char *path, int parent_fd, char **envp,
 			   const char *command, char **argv,
 			   const char *directory,
-			   int columns, int rows, GPid *child)
+			   int columns, int rows,
+                           GSpawnFlags flags,
+                           GSpawnChildSetupFunc child_setup, gpointer child_setup_data,
+                           GPid *child,
+                           GError **error)
 {
 	struct vte_pty_child_setup_data data;
 
 	data.mode = TTY_OPEN_BY_NAME;
 	data.tty.name = path;
+        data.spawn_flags = flags;
+        data.child_setup = child_setup;
+        data.child_setup_data = child_setup_data;
 
 	if (!_vte_pty_run_on_pty(&data,
 			command, argv, envp, directory,
-			child, NULL)) {
-		/* XXX propagate the error */
+			child, error)) {
 		return FALSE;
 	}
 
@@ -431,17 +450,23 @@ static gboolean
 _vte_pty_fork_on_pty_fd (int fd, char **envp,
 			 const char *command, char **argv,
 			 const char *directory,
-			 int columns, int rows, GPid *child)
+			 int columns, int rows,
+                         GSpawnFlags flags,
+                         GSpawnChildSetupFunc child_setup, gpointer child_setup_data,
+                         GPid *child,
+                         GError **error)
 {
 	struct vte_pty_child_setup_data data;
 
 	data.mode = TTY_OPEN_BY_FD;
 	data.tty.fd = fd;
+        data.spawn_flags = flags;
+        data.child_setup = child_setup;
+        data.child_setup_data = child_setup_data;
 
 	if (!_vte_pty_run_on_pty(&data,
 				command, argv, envp, directory,
-				child, NULL)) {
-		/* XXX propagate the error */
+				child, error)) {
 		return FALSE;
 	}
 
@@ -603,7 +628,10 @@ _vte_pty_unlockpt(int fd)
 static int
 _vte_pty_open_unix98(GPid *child, char **env_add,
 		     const char *command, char **argv,
-		     const char *directory, int columns, int rows)
+		     const char *directory, int columns, int rows,
+                     GSpawnFlags flags,
+                     GSpawnChildSetupFunc child_setup, gpointer child_setup_data,
+                     GError **error)
 {
 	int fd;
 	char *buf;
@@ -616,6 +644,10 @@ _vte_pty_open_unix98(GPid *child, char **env_add,
 		if (((buf = _vte_pty_ptsname(fd)) == NULL) ||
 		    (_vte_pty_grantpt(fd) != 0) ||
 		    (_vte_pty_unlockpt(fd) != 0)) {
+                        g_set_error_literal (error, VTE_TERMINAL_FORK_ERROR,
+                                             VTE_TERMINAL_FORK_ERROR_PTY98_FAILED,
+                                             "PTY setup failed");
+                                     
 			_vte_debug_print(VTE_DEBUG_PTY,
 					"PTY setup failed, bailing.\n");
 			close(fd);
@@ -626,13 +658,20 @@ _vte_pty_open_unix98(GPid *child, char **env_add,
 						      env_add, command,
 						      argv, directory,
 						      columns, rows,
-						      child)) {
+                                                      flags,
+                                                      child_setup, child_setup_data,
+						      child,
+                                                      error)) {
 				close(fd);
 				fd = -1;
 			}
 			g_free(buf);
 		}
-	}
+	} else {
+                g_set_error_literal (error, VTE_TERMINAL_FORK_ERROR,
+                                     VTE_TERMINAL_FORK_ERROR_PTY98_FAILED,
+                                     "Getpty failed");
+        }
 	return fd;
 }
 
@@ -820,7 +859,7 @@ _vte_direct_compare(gconstpointer a, gconstpointer b)
 }
 
 static gboolean
-_vte_pty_start_helper(void)
+_vte_pty_start_helper(GError **error)
 {
 	int i, tmp[2], tunnel;
 	/* Sanity check. */
@@ -829,27 +868,31 @@ _vte_pty_start_helper(void)
 		 * going to work (assuming we can open a pty using some other
 		 * method). */
 		g_warning(_("can not run %s"), LIBEXECDIR "/gnome-pty-helper");
+		g_set_error(error, VTE_TERMINAL_FORK_ERROR,
+                            VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                            _("can not run %s"), LIBEXECDIR "/gnome-pty-helper");
 		return FALSE;
 	}
 	/* Create a communication link for use with the helper. */
 	tmp[0] = open("/dev/null", O_RDONLY);
 	if (tmp[0] == -1) {
-		return FALSE;
+		goto failure;
 	}
 	tmp[1] = open("/dev/null", O_RDONLY);
 	if (tmp[1] == -1) {
 		close(tmp[0]);
-		return FALSE;
+		goto failure;
 	}
 	if (_vte_pty_pipe_open(&_vte_pty_helper_tunnel, &tunnel) != 0) {
-		return FALSE;
+                // FIXME doesn't this leak FDs tmp[0] and tmp[1] ?
+		goto failure;
 	}
 	close(tmp[0]);
 	close(tmp[1]);
 	/* Now fork and start the helper. */
 	_vte_pty_helper_pid = fork();
 	if (_vte_pty_helper_pid == -1) {
-		return FALSE;
+		goto failure;
 	}
 	if (_vte_pty_helper_pid == 0) {
 		/* Child.  Close descriptors.  No need to close all,
@@ -872,13 +915,42 @@ _vte_pty_start_helper(void)
 	_vte_pty_helper_map = g_tree_new(_vte_direct_compare);
 	atexit(_vte_pty_stop_helper);
 	return TRUE;
+
+failure:
+        g_set_error(error, VTE_TERMINAL_FORK_ERROR,
+                    VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                    "Failed to start gnome-pty-helper: %s",
+                    g_strerror (errno));
+        return FALSE;
 }
 
+/*
+ * _vte_pty_open_with_helper:
+ * @child: location to store the child's PID
+ * @env_add:
+ * @argv:
+ * @directory:
+ * @columns:
+ * @rows:
+ * @op:
+ * @flags:
+ * @child_setup:
+ * @child_setup_data:
+ * @error: location to store a #GError
+ *
+ * Opens a PTY using gnome-pty-helper, and spawns the command.
+ *
+ * Returns: the PTY's FD; or -1 if opening the PTY failed, or
+ *   -2 if spawning the command failed
+ */
 static int
 _vte_pty_open_with_helper(GPid *child, char **env_add,
 			  const char *command, char **argv,
 			  const char *directory,
-			  int columns, int rows, int op)
+			  int columns, int rows, int op,
+                          GSpawnFlags flags,
+                          GSpawnChildSetupFunc child_setup, gpointer child_setup_data,
+                          GError **error)
 {
 	GnomePtyOps ops;
 	int ret;
@@ -886,7 +958,7 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 	gpointer tag;
 	/* We have to use the pty helper here. */
 	if (!_vte_pty_helper_started) {
-		_vte_pty_helper_started = _vte_pty_start_helper();
+		_vte_pty_helper_started = _vte_pty_start_helper(error);
 	}
 	/* Try to open a new descriptor. */
 	if (_vte_pty_helper_started) {
@@ -894,23 +966,38 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 		/* Send our request. */
 		if (n_write(_vte_pty_helper_tunnel,
 			    &ops, sizeof(ops)) != sizeof(ops)) {
+                        g_set_error (error, VTE_TERMINAL_FORK_ERROR,
+                                     VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to send request to gnome-pty-helper: %s",
+                                     g_strerror (errno));
 			return -1;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY, "Sent request to helper.\n");
 		/* Read back the response. */
 		if (n_read(_vte_pty_helper_tunnel,
 			   &ret, sizeof(ret)) != sizeof(ret)) {
+                        g_set_error (error, VTE_TERMINAL_FORK_ERROR,
+                                     VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read response from gnome-pty-helper: %s",
+                                     g_strerror (errno));
 			return -1;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY,
 				"Received response from helper.\n");
 		if (ret == 0) {
+                        g_set_error_literal (error, VTE_TERMINAL_FORK_ERROR,
+                                             VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                                             "gnome-pty-helper failed to open pty");
 			return -1;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY, "Helper returns success.\n");
 		/* Read back a tag. */
 		if (n_read(_vte_pty_helper_tunnel,
 			   &tag, sizeof(tag)) != sizeof(tag)) {
+                        g_set_error (error, VTE_TERMINAL_FORK_ERROR,
+                                     VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read tag from gnome-pty-helper: %s",
+                                     g_strerror (errno));
 			return -1;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY, "Tag = %p.\n", tag);
@@ -919,8 +1006,15 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 				      &parentfd, &childfd);
 
 		if ((parentfd == -1) || (childfd == -1)) {
+                        int errsv = errno;
+
 			close(parentfd);
 			close(childfd);
+
+                        g_set_error (error, VTE_TERMINAL_FORK_ERROR,
+                                     VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED,
+                                     "Failed to read master or slave pty from gnome-pty-helper: %s",
+                                     g_strerror (errsv));
 			return -1;
 		}
 		_vte_debug_print(VTE_DEBUG_PTY,
@@ -934,10 +1028,17 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
 		/* Start up a child process with the given command. */
 		if (!_vte_pty_fork_on_pty_fd(childfd, env_add, command,
 					    argv, directory,
-					    columns, rows, child)) {
+					    columns, rows,
+                                            flags,
+                                            child_setup, child_setup_data,
+                                            child,
+                                            error)) {
+			g_tree_remove(_vte_pty_helper_map,
+				      GINT_TO_POINTER(parentfd));
+
 			close(parentfd);
 			close(childfd);
-			return -1;
+			return -2;
 		}
 		close(childfd);
 		return parentfd;
@@ -955,29 +1056,37 @@ _vte_pty_open_with_helper(GPid *child, char **env_add,
  * @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
+ * @fork_flags: flags from #VteTerminalForkFlags
+ * @spawn_flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @child_setup_data: user data for child_setup
+ * @error: return location for a #GError
  *
  * 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
+ * and variables in @env_add added to its environment.  FIXME: 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
+ *   with @error filled in
  */
 int
 _vte_pty_open(pid_t *child_pid, char **env_add,
 	      const char *command, char **argv, const char *directory,
 	      int columns, int rows,
-	      gboolean lastlog, gboolean utmp, gboolean wtmp)
+              VteTerminalForkFlags fork_flags,
+              GSpawnFlags spawn_flags,
+              GSpawnChildSetupFunc child_setup, gpointer child_setup_data,
+              GError **error)
+
 {
+        GError *err = NULL;
 	GPid child;
 	int ret = -1;
 #ifdef VTE_USE_GNOME_PTY_HELPER
 	int op = 0;
-	int opmap[8] = {
+	static const int opmap[8] = {
 		GNOME_PTY_OPEN_NO_DB_UPDATE,		/* 0 0 0 */
 		GNOME_PTY_OPEN_PTY_LASTLOG,		/* 0 0 1 */
 		GNOME_PTY_OPEN_PTY_UTMP,		/* 0 1 0 */
@@ -987,30 +1096,43 @@ _vte_pty_open(pid_t *child_pid, char **env_add,
 		GNOME_PTY_OPEN_PTY_UWTMP,		/* 1 1 0 */
 		GNOME_PTY_OPEN_PTY_LASTLOGUWTMP,	/* 1 1 1 */
 	};
-	if (lastlog) {
+	if ((fork_flags & VTE_TERMINAL_FORK_NO_LASTLOG) == 0) {
 		op += 1;
 	}
-	if (utmp) {
+	if ((fork_flags & VTE_TERMINAL_FORK_NO_UTMP) == 0) {
 		op += 2;
 	}
-	if (wtmp) {
+	if ((fork_flags & VTE_TERMINAL_FORK_NO_WTMP) == 0) {
 		op += 4;
 	}
 	g_assert(op >= 0);
 	g_assert(op < G_N_ELEMENTS(opmap));
-	if (ret == -1 && op != 0) {
+
+	if (op != 0) {
 		ret = _vte_pty_open_with_helper(&child, env_add, command, argv,
 						directory,
-						columns, rows, opmap[op]);
-	}
+						columns, rows, opmap[op],
+                                                spawn_flags,
+                                                child_setup, child_setup_data,
+                                                &err);
+        }
+        if (ret == -2) {
+                g_propagate_error (error, err);
+                goto out;
+        }
 #endif
 	if (ret == -1) {
+                g_clear_error (error);
 		ret = _vte_pty_open_unix98(&child, env_add, command, argv,
-					   directory, columns, rows);
+					   directory, columns, rows,
+                                           spawn_flags,
+                                           child_setup, child_setup_data,
+                                           error);
 	}
 	if (ret != -1) {
 		*child_pid = (pid_t) child;
 	}
+out:
 	_vte_debug_print(VTE_DEBUG_PTY,
 			"Returning ptyfd = %d, child = %ld.\n",
 			ret, (long) child);
@@ -1115,7 +1237,9 @@ main(int argc, char **argv)
 			   (argc > 1) ? argv + 1 : NULL,
 			   NULL,
 			   0, 0,
-			   TRUE, TRUE, TRUE);
+			   0,
+                           NULL, NULL,
+                           NULL);
 	if (child == 0) {
 		int i;
 		for (i = 0; ; i++) {
diff --git a/src/pty.h b/src/pty.h
index 063f92d..e25626e 100644
--- a/src/pty.h
+++ b/src/pty.h
@@ -21,6 +21,9 @@
 
 #include <sys/types.h>
 
+#include <glib.h>
+#include "vte.h"
+
 G_BEGIN_DECLS
 
 /* Start up the given binary (exact path, not interpreted at all) in a
@@ -30,7 +33,10 @@ G_BEGIN_DECLS
 int _vte_pty_open(pid_t *child, char **env_add,
 		  const char *command, char **argv, const char *directory,
 		  int columns, int rows,
-		  gboolean lastlog, gboolean utmp, gboolean wtmp);
+                  VteTerminalForkFlags fork_flags,
+                  GSpawnFlags spawn_flags,
+                  GSpawnChildSetupFunc child_setup, gpointer child_setup_data,
+                  GError **error);
 
 /* Set or read the size of a terminal.  Returns 0 on success, -1 on failure,
  * with errno set to defined return codes from ioctl(). */
diff --git a/src/vte-private.h b/src/vte-private.h
index d43cba4..d0332bb 100644
--- a/src/vte-private.h
+++ b/src/vte-private.h
@@ -214,7 +214,6 @@ struct _VteTerminalPrivate {
 	int default_column_count, default_row_count;	/* default sizes */
 
 	/* PTY handling data. */
-	const char *shell;		/* shell we started */
 	int pty_master;			/* pty master descriptor */
 	GIOChannel *pty_channel;	/* master channel */
 	guint pty_input_source;
diff --git a/src/vte.c b/src/vte.c
index b0971c7..7041df7 100644
--- a/src/vte.c
+++ b/src/vte.c
@@ -3439,10 +3439,16 @@ _vte_terminal_disconnect_pty_write(VteTerminal *terminal)
 
 /* Basic wrapper around _vte_pty_open(), which handles the pipefitting. */
 static pid_t
-_vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
-			 char **argv, char **envv,
+_vte_terminal_fork_basic(VteTerminal *terminal,
+                         const char *command,
+			 char **argv,
+                         char **envv,
 			 const char *directory,
-			 gboolean lastlog, gboolean utmp, gboolean wtmp)
+                         VteTerminalForkFlags fork_flags,
+                         GSpawnFlags spawn_flags,
+                         GSpawnChildSetupFunc child_setup,
+                         gpointer child_setup_data,
+                         GError **error)
 {
         GObject *object = G_OBJECT(terminal);
 	char **env_add;
@@ -3450,6 +3456,14 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
 	pid_t pid;
 	VteReaper *reaper;
 
+        /* We need this so the reaper works */
+        spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
+
+        /* FIXMEchpe: Enforce this until I've checked our code to make sure
+         * it doesn't leak its internal FDs into the child this way.
+         */
+        spawn_flags &= ~G_SPAWN_LEAVE_DESCRIPTORS_OPEN;
+        
         g_object_freeze_notify(object);
 
 	/* Duplicate the environment, and add one more variable. */
@@ -3481,7 +3495,9 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
 	pid = -1;
 	fd = _vte_pty_open(&pid, env_add, command, argv, directory,
 			  terminal->column_count, terminal->row_count,
-			  lastlog, utmp, wtmp);
+			  fork_flags,
+                          spawn_flags, child_setup, child_setup_data,
+                          error);
 	if (pid == -1 || fd == -1) {
 		g_strfreev(env_add);
 
@@ -3492,7 +3508,7 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
 
 	/* If we successfully started the process, set up to listen for its
 	 * output. */
-	if (pid != 0) {
+	if (pid > 0) {
 		/* Set this as the child's pid. */
 		terminal->pvt->pty_pid = pid;
 
@@ -3525,17 +3541,56 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
 	return pid;
 }
 
+static VteTerminalForkFlags
+_vte_terminal_get_fork_flags(gboolean lastlog,
+                             gboolean utmp,
+                             gboolean wtmp)
+{
+  VteTerminalForkFlags flags = 0;
+
+  if (!lastlog)
+    flags |= VTE_TERMINAL_FORK_NO_LASTLOG;
+  if (!utmp)
+    flags |= VTE_TERMINAL_FORK_NO_UTMP;
+  if (!wtmp)
+    flags |= VTE_TERMINAL_FORK_NO_WTMP;
+
+  return flags;
+}
+
 /**
- * vte_terminal_fork_command:
+ * vte_terminal_fork_error_quark:
+ *
+ * The VTE fork error domain.
+ * 
+ * Returns: the error domain for VTE fork errors
+ *
+ * Since: 0.21.0
+ */
+GQuark
+vte_terminal_fork_error_quark(void)
+{
+  static GQuark quark = 0;
+
+  if (G_UNLIKELY (quark == 0))
+    quark = g_quark_from_static_string("vte-fork-error");
+
+  return quark;
+}
+
+/**
+ * vte_terminal_fork_command_full:
  * @terminal: a #VteTerminal
  * @command: the name of a binary to run, or %NULL to get user's shell
  * @argv: the argument list to be passed to @command, or %NULL
  * @envv: a list of environment variables to be added to the environment before
  * starting @command, or %NULL
  * @directory: the name of a directory the command should start in, or %NULL
- * @lastlog: %TRUE if the session should be logged to the lastlog
- * @utmp: %TRUE if the session should be logged to the utmp/utmpx log
- * @wtmp: %TRUE if the session should be logged to the wtmp/wtmpx log
+ * @fork_flags: flags from #VteTerminalForkFlags
+ * @spawn_flags: flags from #GSpawnFlags
+ * @child_setup: function to run in the child just before exec()
+ * @child_setup_data: user data for @child_setup
+ * @error: return location for a #GError
  *
  * Starts the specified command under a newly-allocated controlling
  * pseudo-terminal.  The @argv and @envv lists should be %NULL-terminated, and
@@ -3544,51 +3599,151 @@ _vte_terminal_fork_basic(VteTerminal *terminal, const char *command,
  * terminal widget's emulation setting.  If @lastlog, @utmp, or @wtmp are %TRUE,
  * logs the session to the specified system log files.
  *
- * Note that all file descriptors except stdin/stdout/stderr will be closed
- * before calling exec() in the child.
+ * Note that #G_SPAWN_DO_NOT_REAP_CHILD will always be added to @spawn_flags.
+ *
+ * Note that all unless @flags contains #G_SPAWN_LEAVE_DESCRIPTORS_OPEN, all file
+ * descriptors except stdin/stdout/stderr will be closed before calling exec()
+ * in the child.
  * 
- * Returns: the ID of the new process
+ * Returns: the ID of the new process, or -1 on error with @error filled in
+ *
+ * Since: 0.21
  */
 pid_t
-vte_terminal_fork_command(VteTerminal *terminal,
-			  const char *command, char **argv, char **envv,
-			  const char *directory,
-			  gboolean lastlog, gboolean utmp, gboolean wtmp)
+vte_terminal_fork_command_full(VteTerminal *terminal,
+                               const char *command,
+                               char **argv,
+                               char **envv,
+                               const char *directory,
+                               VteTerminalForkFlags fork_flags,
+                               GSpawnFlags spawn_flags,
+                               GSpawnChildSetupFunc child_setup,
+                               gpointer child_setup_data,
+                               GError **error)
 {
 	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
 
 	/* Make the user's shell the default command. */
 	if (command == NULL) {
-		if (terminal->pvt->shell == NULL) {
-			struct passwd *pwd;
-
-			pwd = getpwuid(getuid());
-			if (pwd != NULL) {
-				terminal->pvt->shell = pwd->pw_shell;
-				_vte_debug_print(VTE_DEBUG_MISC,
-						"Using user's shell (%s).\n",
-						terminal->pvt->shell);
-			}
-		}
-		if (terminal->pvt->shell == NULL) {
+                struct passwd *pwd;
+
+                pwd = getpwuid(getuid());
+                if (pwd != NULL) {
+                        command = pwd->pw_shell;
+                        _vte_debug_print(VTE_DEBUG_MISC,
+                                        "Using user's shell (%s).\n",
+                                        command);
+                }
+		if (command == NULL) {
 			if (getenv ("SHELL")) {
-				terminal->pvt->shell = getenv ("SHELL");
+				command = getenv ("SHELL");
 				_vte_debug_print(VTE_DEBUG_MISC,
 					"Using $SHELL shell (%s).\n",
-						terminal->pvt->shell);
+						command);
 			} else {
-				terminal->pvt->shell = "/bin/sh";
+				command = "/bin/sh";
 				_vte_debug_print(VTE_DEBUG_MISC,
 						"Using default shell (%s).\n",
-						terminal->pvt->shell);
+						command);
 			}
 		}
-		command = terminal->pvt->shell;
 	}
 
 	/* Start up the command and get the PTY of the master. */
 	return _vte_terminal_fork_basic(terminal, command, argv, envv,
-				        directory, lastlog, utmp, wtmp);
+				        directory,
+                                        fork_flags, spawn_flags,
+                                        child_setup, child_setup_data,
+                                        error);
+}
+
+/**
+ * vte_terminal_fork_command:
+ * @terminal: a #VteTerminal
+ * @command: the name of a binary to run, or %NULL to get user's shell
+ * @argv: the argument list to be passed to @command, or %NULL
+ * @envv: a list of environment variables to be added to the environment before
+ * starting @command, or %NULL
+ * @directory: the name of a directory the command should start in, or %NULL
+ * @lastlog: %TRUE if the session should be logged to the lastlog
+ * @utmp: %TRUE if the session should be logged to the utmp/utmpx log
+ * @wtmp: %TRUE if the session should be logged to the wtmp/wtmpx log
+ *
+ * Starts the specified command under a newly-allocated controlling
+ * pseudo-terminal.  The @argv and @envv lists should be %NULL-terminated, and
+ * argv[0] is expected to be the name of the file being run, as it would be if
+ * execve() were being called.  TERM is automatically set to reflect the
+ * terminal widget's emulation setting.  If @lastlog, @utmp, or @wtmp are %TRUE,
+ * logs the session to the specified system log files.
+ *
+ * Note that all file descriptors except stdin/stdout/stderr will be closed
+ * before calling exec() in the child.
+ *
+ * This is equivalent to calling vte_terminal_fork_command_full() with 
+ * flags #G_SPAWN_CHILD_INHERITS_STDIN | #G_SPAWN_SEARCH_PATH
+ * and NULL child setup function and data.
+ * 
+ * Returns: the ID of the new process
+ */
+pid_t
+vte_terminal_fork_command(VteTerminal *terminal,
+			  const char *command,
+                          char **argv,
+                          char **envv,
+			  const char *directory,
+			  gboolean lastlog,
+                          gboolean utmp,
+                          gboolean wtmp)
+{
+        return vte_terminal_fork_command_full(terminal, command, argv, envv, directory,
+                                              _vte_terminal_get_fork_flags (lastlog, utmp, wtmp),
+                                              G_SPAWN_CHILD_INHERITS_STDIN |
+                                              G_SPAWN_SEARCH_PATH,
+                                              NULL, NULL,
+                                              NULL);
+}
+
+/**
+ * vte_terminal_forkpty_full:
+ * @terminal: a #VteTerminal
+ * @envv: a list of environment variables to be added to the environment before
+ * starting returning in the child process, or %NULL
+ * @directory: the name of a directory the child process should change to, or
+ * %NULL
+ * @fork_flags: flags from #VteTerminalForkFlags
+ * @spawn_flags: flags from #GSpawnFlags
+ * @error: return location for a #GError
+ *
+ * Starts a new child process under a newly-allocated controlling
+ * pseudo-terminal.  TERM is automatically set to reflect the terminal widget's
+ * emulation setting.  If @lastlog, @utmp, or @wtmp are %TRUE, logs the session
+ * to the specified system log files.
+ *
+ * Note that #G_SPAWN_DO_NOT_REAP_CHILD will always be added to @spawn_flags.
+ *
+ * Note that unless @flags contains #G_SPAWN_LEAVE_DESCRIPTORS_OPEN, all file
+ * descriptors except stdin/stdout/stderr will be closed in the child.
+ * 
+ * Returns: the ID of the new process in the parent, 0 in the child, or -1
+ * on error with @error filled in
+ *
+ * Since: 0.21
+ */
+pid_t
+vte_terminal_forkpty_full(VteTerminal *terminal,
+                          char **envv, const char *directory,
+                          VteTerminalForkFlags fork_flags,
+                          GSpawnFlags spawn_flags,
+                          GError **error)
+{
+	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
+
+	return _vte_terminal_fork_basic(terminal, NULL, NULL, envv,
+                                        directory,
+                                        fork_flags,
+                                        spawn_flags,
+                                        NULL, NULL,
+                                        error);
 }
 
 /**
@@ -3607,6 +3762,13 @@ vte_terminal_fork_command(VteTerminal *terminal,
  * emulation setting.  If @lastlog, @utmp, or @wtmp are %TRUE, logs the session
  * to the specified system log files.
  *
+ * Note that all file descriptors except stdin/stdout/stderr will be closed
+ * in the child.
+ *
+ * This is equivalent to calling vte_terminal_forkpty_full() with
+ * flags #G_SPAWN_CHILD_INHERITS_STDIN | #G_SPAWN_SEARCH_PATH
+ * and NULL child setup function and data.
+ * 
  * Returns: the ID of the new process in the parent, 0 in the child, and -1 if
  * there was an error
  *
@@ -3617,10 +3779,11 @@ vte_terminal_forkpty(VteTerminal *terminal,
 		     char **envv, const char *directory,
 		     gboolean lastlog, gboolean utmp, gboolean wtmp)
 {
-	g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
-
-	return _vte_terminal_fork_basic(terminal, NULL, NULL, envv,
-				       directory, lastlog, utmp, wtmp);
+        return vte_terminal_forkpty_full(terminal, envv, directory,
+                                         _vte_terminal_get_fork_flags (lastlog, utmp, wtmp),
+                                         G_SPAWN_CHILD_INHERITS_STDIN |
+                                         G_SPAWN_SEARCH_PATH,
+                                         NULL);
 }
 
 /* Handle an EOF from the client. */
@@ -13825,10 +13988,16 @@ vte_terminal_set_pty(VteTerminal *terminal, int pty_master)
                close (terminal->pvt->pty_master);
        }
        terminal->pvt->pty_master = pty_master;
+
+       if (pty_master == -1) {
+                g_object_notify(object, "pty");
+                g_object_thaw_notify(object);
+                return;
+       }
+
        terminal->pvt->pty_channel = g_io_channel_unix_new (pty_master);
        g_io_channel_set_close_on_unref (terminal->pvt->pty_channel, FALSE);
 
-
        /* Set the pty to be non-blocking. */
        flags = fcntl (terminal->pvt->pty_master, F_GETFL);
        if ((flags & O_NONBLOCK) == 0) {
diff --git a/src/vte.h b/src/vte.h
index 80e9411..f7a7a56 100644
--- a/src/vte.h
+++ b/src/vte.h
@@ -213,6 +213,24 @@ GType vte_terminal_get_type(void);
 #define VTE_TERMINAL_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), VTE_TYPE_TERMINAL, VteTerminalClass))
 
 /* You can get by with just these two functions. */
+
+typedef enum {
+  VTE_TERMINAL_FORK_NO_LASTLOG  = 1 << 0,
+  VTE_TERMINAL_FORK_NO_UTMP     = 1 << 1,
+  VTE_TERMINAL_FORK_NO_WTMP     = 1 << 2
+} VteTerminalForkFlags;
+
+#define VTE_TERMINAL_FORK_FLAGS_DEFAULT (0)
+
+typedef enum {
+  VTE_TERMINAL_FORK_ERROR_PTY_HELPER_FAILED = 0,
+  VTE_TERMINAL_FORK_ERROR_PTY98_FAILED
+} VteTerminalForkError;
+
+GQuark vte_terminal_fork_error_quark (void);
+
+#define VTE_TERMINAL_FORK_ERROR (vte_terminal_fork_error_quark ())
+
 GtkWidget *vte_terminal_new(void);
 pid_t vte_terminal_fork_command(VteTerminal *terminal,
 				const char *command, char **argv,
@@ -221,12 +239,29 @@ pid_t vte_terminal_fork_command(VteTerminal *terminal,
 				gboolean utmp,
 				gboolean wtmp);
 
+pid_t vte_terminal_fork_command_full(VteTerminal *terminal,
+                                     const char *command,
+                                     char **argv,
+                                     char **envv,
+                                     const char *directory,
+                                     VteTerminalForkFlags fork_flags,
+                                     GSpawnFlags spawn_flags,
+                                     GSpawnChildSetupFunc child_setup,
+                                     gpointer child_setup_data,
+                                     GError **error);
+
 /* Users of libzvt may find this useful. */
 pid_t vte_terminal_forkpty(VteTerminal *terminal,
 			   char **envv, const char *directory,
 			   gboolean lastlog,
 			   gboolean utmp,
 			   gboolean wtmp);
+pid_t vte_terminal_forkpty_full(VteTerminal *terminal,
+                                char **envv,
+                                const char *directory,
+                                VteTerminalForkFlags fork_flags,
+                                GSpawnFlags spawn_flags,
+                                GError **error);
 
 /* Send data to the terminal to display, or to the terminal's forked command
  * to handle in some way.  If it's 'cat', they should be the same. */
diff --git a/src/vteapp.c b/src/vteapp.c
index 9302d30..bda3e53 100644
--- a/src/vteapp.c
+++ b/src/vteapp.c
@@ -854,10 +854,16 @@ main(int argc, char **argv)
 			/* Launch a shell. */
 			_VTE_DEBUG_IF(VTE_DEBUG_MISC)
 				vte_terminal_feed(terminal, message, -1);
-			vte_terminal_fork_command(terminal,
-						  command, NULL, env_add,
-						  working_directory,
-						  TRUE, TRUE, TRUE);
+			if (vte_terminal_fork_command_full(terminal,
+                                                           command, NULL, env_add,
+                                                           working_directory,
+                                                           VTE_TERMINAL_FORK_FLAGS_DEFAULT,
+                                                           0, NULL, NULL,
+                                                           &error) == -1) {
+                                g_printerr ("Failed to spawn shell: %s\n", error->message);
+                                g_error_free (error);
+                                exit (1);
+                        }
 	#ifdef VTE_DEBUG
 			if (command == NULL) {
 				vte_terminal_feed_child(terminal,
@@ -865,15 +871,17 @@ main(int argc, char **argv)
 			}
 	#endif
 		} else {
-			long i;
-			i = vte_terminal_forkpty(terminal,
-						 env_add, working_directory,
-						 TRUE, TRUE, TRUE);
+			pid_t i;
+			i = vte_terminal_forkpty_full(terminal,
+                                                      env_add, working_directory,
+                                                      VTE_TERMINAL_FORK_FLAGS_DEFAULT,
+                                                      0,
+                                                      &error);
 			switch (i) {
 			case -1:
 				/* abnormal */
-				g_warning("Error in vte_terminal_forkpty(): %s",
-					  strerror(errno));
+				g_warning("Error forking: %s\n", error->message);
+                                g_error_free (error);
 				break;
 			case 0:
 				/* child */
@@ -881,10 +889,10 @@ main(int argc, char **argv)
 					switch (i % 3) {
 					case 0:
 					case 1:
-						g_print("%ld\n", i);
+						g_print("%ld\n", (long) i);
 						break;
 					case 2:
-						g_printerr("%ld\n", i);
+						g_printerr("%ld\n", (long) i);
 						break;
 					}
 					sleep(1);
