+void fork_and_exec_self(struct gui_data *inst, int fd_to_close, ...)
+{
+ /*
+ * Re-execing ourself is not an exact science under Unix. I do
+ * the best I can by using /proc/self/exe if available and by
+ * assuming argv[0] can be found on $PATH if not.
+ *
+ * Note that we also have to reconstruct the elements of the
+ * original argv which gtk swallowed, since the user wants the
+ * new session to appear on the same X display as the old one.
+ */
+ char **args;
+ va_list ap;
+ int i, n;
+ int pid;
+
+ /*
+ * Collect the arguments with which to re-exec ourself.
+ */
+ va_start(ap, fd_to_close);
+ n = 2; /* progname and terminating NULL */
+ n += inst->ngtkargs;
+ while (va_arg(ap, char *) != NULL)
+ n++;
+ va_end(ap);
+
+ args = snewn(n, char *);
+ args[0] = inst->progname;
+ args[n-1] = NULL;
+ for (i = 0; i < inst->ngtkargs; i++)
+ args[i+1] = inst->gtkargvstart[i];
+
+ i++;
+ va_start(ap, fd_to_close);
+ while ((args[i++] = va_arg(ap, char *)) != NULL);
+ va_end(ap);
+
+ assert(i == n);
+
+ /*
+ * Do the double fork.
+ */
+ pid = fork();
+ if (pid < 0) {
+ perror("fork");
+ return;
+ }
+
+ if (pid == 0) {
+ int pid2 = fork();
+ if (pid2 < 0) {
+ perror("fork");
+ _exit(1);
+ } else if (pid2 > 0) {
+ /*
+ * First child has successfully forked second child. My
+ * Work Here Is Done. Note the use of _exit rather than
+ * exit: the latter appears to cause destroy messages
+ * to be sent to the X server. I suspect gtk uses
+ * atexit.
+ */
+ _exit(0);
+ }
+
+ /*
+ * If we reach here, we are the second child, so we now
+ * actually perform the exec.
+ */
+ if (fd_to_close >= 0)
+ close(fd_to_close);
+
+ execv("/proc/self/exe", args);
+ execvp(inst->progname, args);
+ perror("exec");
+ _exit(127);
+
+ } else {
+ int status;
+ waitpid(pid, &status, 0);
+ }
+
+}
+
+void dup_session_menuitem(GtkMenuItem *item, gpointer gdata)
+{
+ struct gui_data *inst = (struct gui_data *)gdata;
+ /*
+ * For this feature we must marshal cfg and (possibly) pty_argv
+ * into a byte stream, create a pipe, and send this byte stream
+ * to the child through the pipe.
+ */
+ int i, ret, size;
+ char *data;
+ char option[80];
+ int pipefd[2];
+
+ if (pipe(pipefd) < 0) {
+ perror("pipe");
+ return;
+ }
+
+ size = sizeof(inst->cfg);
+ if (use_pty_argv && pty_argv) {
+ for (i = 0; pty_argv[i]; i++)
+ size += strlen(pty_argv[i]) + 1;
+ }
+
+ data = snewn(size, char);
+ memcpy(data, &inst->cfg, sizeof(inst->cfg));
+ if (use_pty_argv && pty_argv) {
+ int p = sizeof(inst->cfg);
+ for (i = 0; pty_argv[i]; i++) {
+ strcpy(data + p, pty_argv[i]);
+ p += strlen(pty_argv[i]) + 1;
+ }
+ assert(p == size);
+ }
+
+ sprintf(option, "---[%d,%d]", pipefd[0], size);
+ fcntl(pipefd[0], F_SETFD, 0);
+ fork_and_exec_self(inst, pipefd[1], option, NULL);
+ close(pipefd[0]);
+
+ i = ret = 0;
+ while (i < size && (ret = write(pipefd[1], data + i, size - i)) > 0)
+ i += ret;
+ if (ret < 0)
+ perror("write to pipe");
+ close(pipefd[1]);
+ sfree(data);
+}
+
+int read_dupsession_data(struct gui_data *inst, Config *cfg, char *arg)
+{
+ int fd, i, ret, size;
+ char *data;
+
+ if (sscanf(arg, "---[%d,%d]", &fd, &size) != 2) {
+ fprintf(stderr, "%s: malformed magic argument `%s'\n", appname, arg);
+ exit(1);
+ }
+
+ data = snewn(size, char);
+ i = ret = 0;
+ while (i < size && (ret = read(fd, data + i, size - i)) > 0)
+ i += ret;
+ if (ret < 0) {
+ perror("read from pipe");
+ exit(1);
+ } else if (i < size) {
+ fprintf(stderr, "%s: unexpected EOF in Duplicate Session data\n",
+ appname);
+ exit(1);
+ }
+
+ memcpy(cfg, data, sizeof(Config));
+ if (use_pty_argv && size > sizeof(Config)) {
+ int n = 0;
+ i = sizeof(Config);
+ while (i < size) {
+ while (i < size && data[i]) i++;
+ if (i >= size) {
+ fprintf(stderr, "%s: malformed Duplicate Session data\n",
+ appname);
+ exit(1);
+ }
+ i++;
+ n++;
+ }
+ pty_argv = snewn(n+1, char *);
+ pty_argv[n] = NULL;
+ n = 0;
+ i = sizeof(Config);
+ while (i < size) {
+ char *p = data + i;
+ while (i < size && data[i]) i++;
+ assert(i < size);
+ i++;
+ pty_argv[n++] = dupstr(p);
+ }
+ }
+
+ return 0;
+}
+
+void new_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ fork_and_exec_self(inst, -1, NULL);
+}
+
+void restart_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+
+ if (!inst->back) {
+ logevent(inst, "----- Session restarted -----");
+ start_backend(inst);
+ inst->exited = FALSE;
+ }
+}
+
+void saved_session_menuitem(GtkMenuItem *item, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+ fork_and_exec_self(inst, -1, "-load", str, NULL);
+}
+
+void saved_session_freedata(GtkMenuItem *item, gpointer data)
+{
+ char *str = (char *)gtk_object_get_data(GTK_OBJECT(item), "user-data");
+
+ sfree(str);
+}
+