]> asedeno.scripts.mit.edu Git - PuTTY.git/blobdiff - unix/uxpty.c
Fix buffer management in strbuf_catfv.
[PuTTY.git] / unix / uxpty.c
index afd64afb8385b49bf56215dab084cbc0b52ee0ea..39f96f126afd9a347f3c46caf2bd7c8fed914015 100644 (file)
@@ -180,6 +180,8 @@ static struct utmpx utmp_entry;
  */
 char **pty_argv;
 
+char *pty_osx_envrestore_prefix;
+
 static void pty_close(Pty pty);
 static void pty_try_write(Pty pty);
 
@@ -259,18 +261,19 @@ static void cleanup_utmp(void)
 }
 #endif
 
+#ifndef NO_PTY_PRE_INIT
 static void sigchld_handler(int signum)
 {
     if (write(pty_signal_pipe[1], "x", 1) <= 0)
        /* not much we can do about it */;
 }
+#endif
 
 #ifndef OMIT_UTMP
 static void fatal_sig_handler(int signum)
 {
     putty_signal(signum, SIG_DFL);
     cleanup_utmp();
-    setuid(getuid());
     raise(signum);
 }
 #endif
@@ -335,12 +338,39 @@ static void pty_open_master(Pty pty)
     chown(pty->name, getuid(), gp ? gp->gr_gid : -1);
     chmod(pty->name, 0600);
 #else
-    pty->master_fd = open("/dev/ptmx", O_RDWR);
+
+    const int flags = O_RDWR
+#ifdef O_NOCTTY
+        | O_NOCTTY
+#endif
+        ;
+
+#ifdef HAVE_POSIX_OPENPT
+#ifdef SET_NONBLOCK_VIA_OPENPT
+    /*
+     * OS X, as of 10.10 at least, doesn't permit me to set O_NONBLOCK
+     * on pty master fds via the usual fcntl mechanism. Fortunately,
+     * it does let me work around this by adding O_NONBLOCK to the
+     * posix_openpt flags parameter, which isn't a documented use of
+     * the API but seems to work. So we'll do that for now.
+     */
+    pty->master_fd = posix_openpt(flags | O_NONBLOCK);
+#else
+    pty->master_fd = posix_openpt(flags);
+#endif
+
+    if (pty->master_fd < 0) {
+       perror("posix_openpt");
+       exit(1);
+    }
+#else
+    pty->master_fd = open("/dev/ptmx", flags);
 
     if (pty->master_fd < 0) {
        perror("/dev/ptmx: open");
        exit(1);
     }
+#endif
 
     if (grantpt(pty->master_fd) < 0) {
        perror("grantpt");
@@ -358,21 +388,23 @@ static void pty_open_master(Pty pty)
     strncpy(pty->name, ptsname(pty->master_fd), FILENAME_MAX-1);
 #endif
 
-    {
-        /*
-         * Set the pty master into non-blocking mode.
-         */
-        int fl;
-       fl = fcntl(pty->master_fd, F_GETFL);
-       if (fl != -1 && !(fl & O_NONBLOCK))
-           fcntl(pty->master_fd, F_SETFL, fl | O_NONBLOCK);
-    }
+#ifndef SET_NONBLOCK_VIA_OPENPT
+    nonblock(pty->master_fd);
+#endif
 
     if (!ptys_by_fd)
        ptys_by_fd = newtree234(pty_compare_by_fd);
     add234(ptys_by_fd, pty);
 }
 
+static Pty new_pty_struct(void)
+{
+    Pty pty = snew(struct pty_tag);
+    pty->conf = NULL;
+    bufchain_init(&pty->output_data);
+    return pty;
+}
+
 /*
  * Pre-initialisation. This is here to get around the fact that GTK
  * doesn't like being run in setuid/setgid programs (probably
@@ -388,6 +420,8 @@ static void pty_open_master(Pty pty)
  */
 void pty_pre_init(void)
 {
+#ifndef NO_PTY_PRE_INIT
+
     Pty pty;
 
 #ifndef OMIT_UTMP
@@ -395,8 +429,7 @@ void pty_pre_init(void)
     int pipefd[2];
 #endif
 
-    pty = single_pty = snew(struct pty_tag);
-    bufchain_init(&pty->output_data);
+    pty = single_pty = new_pty_struct();
 
     /* set the child signal handler straight away; it needs to be set
      * before we ever fork. */
@@ -515,13 +548,28 @@ void pty_pre_init(void)
        int gid = getgid(), uid = getuid();
        int setresgid(gid_t, gid_t, gid_t);
        int setresuid(uid_t, uid_t, uid_t);
-       setresgid(gid, gid, gid);
-       setresuid(uid, uid, uid);
+       if (setresgid(gid, gid, gid) < 0) {
+            perror("setresgid");
+            exit(1);
+        }
+       if (setresuid(uid, uid, uid) < 0) {
+            perror("setresuid");
+            exit(1);
+        }
 #else
-       setgid(getgid());
-       setuid(getuid());
+       if (setgid(getgid()) < 0) {
+            perror("setgid");
+            exit(1);
+        }
+       if (setuid(getuid()) < 0) {
+            perror("setuid");
+            exit(1);
+        }
 #endif
     }
+
+#endif /* NO_PTY_PRE_INIT */
+
 }
 
 int pty_real_select_result(Pty pty, int event, int status)
@@ -606,6 +654,7 @@ int pty_real_select_result(Pty pty, int event, int status)
        if (close_on_exit == FORCE_OFF ||
            (close_on_exit == AUTO && pty->exit_code != 0)) {
            char message[512];
+            message[0] = '\0';
            if (WIFEXITED(pty->exit_code))
                sprintf(message, "\r\n[pterm: process terminated with exit"
                        " code %d]\r\n", WEXITSTATUS(pty->exit_code));
@@ -685,8 +734,8 @@ static void pty_uxsel_setup(Pty pty)
  * freed by the caller.
  */
 static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
-                           char *host, int port, char **realhost, int nodelay,
-                           int keepalive)
+                           const char *host, int port, char **realhost,
+                            int nodelay, int keepalive)
 {
     int slavefd;
     pid_t pid, pgrp;
@@ -697,8 +746,9 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
 
     if (single_pty) {
        pty = single_pty;
+        assert(pty->conf == NULL);
     } else {
-       pty = snew(struct pty_tag);
+       pty = new_pty_struct();
        pty->master_fd = pty->slave_fd = -1;
 #ifndef OMIT_UTMP
        pty_stamped_utmp = FALSE;
@@ -715,38 +765,28 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
     if (pty->master_fd < 0)
        pty_open_master(pty);
 
-    /*
-     * Set the backspace character to be whichever of ^H and ^? is
-     * specified by bksp_is_delete.
-     */
-    {
-       struct termios attrs;
-       tcgetattr(pty->master_fd, &attrs);
-       attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete)
-           ? '\177' : '\010';
-       tcsetattr(pty->master_fd, TCSANOW, &attrs);
-    }
-
 #ifndef OMIT_UTMP
     /*
      * Stamp utmp (that is, tell the utmp helper process to do so),
      * or not.
      */
-    if (!conf_get_int(conf, CONF_stamp_utmp)) {
-       close(pty_utmp_helper_pipe);   /* just let the child process die */
-       pty_utmp_helper_pipe = -1;
-    } else if (pty_utmp_helper_pipe >= 0) {
-       char *location = get_x_display(pty->frontend);
-       int len = strlen(location)+1, pos = 0;   /* +1 to include NUL */
-       while (pos < len) {
-           int ret = write(pty_utmp_helper_pipe, location+pos, len - pos);
-           if (ret < 0) {
-               perror("pterm: writing to utmp helper process");
-               close(pty_utmp_helper_pipe);   /* arrgh, just give up */
-               pty_utmp_helper_pipe = -1;
-               break;
-           }
-           pos += ret;
+    if (pty_utmp_helper_pipe >= 0) {   /* if it's < 0, we can't anyway */
+        if (!conf_get_int(conf, CONF_stamp_utmp)) {
+            close(pty_utmp_helper_pipe);   /* just let the child process die */
+            pty_utmp_helper_pipe = -1;
+        } else {
+            const char *location = get_x_display(pty->frontend);
+            int len = strlen(location)+1, pos = 0;   /* +1 to include NUL */
+            while (pos < len) {
+                int ret = write(pty_utmp_helper_pipe, location+pos, len - pos);
+                if (ret < 0) {
+                    perror("pterm: writing to utmp helper process");
+                    close(pty_utmp_helper_pipe);   /* arrgh, just give up */
+                    pty_utmp_helper_pipe = -1;
+                    break;
+                }
+                pos += ret;
+            }
        }
     }
 #endif
@@ -765,10 +805,40 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
     }
 
     if (pid == 0) {
+        struct termios attrs;
+
        /*
         * We are the child.
         */
 
+        if (pty_osx_envrestore_prefix) {
+            int plen = strlen(pty_osx_envrestore_prefix);
+            extern char **environ;
+            char **ep;
+
+          restart_osx_env_restore:
+            for (ep = environ; *ep; ep++) {
+                char *e = *ep;
+
+                if (!strncmp(e, pty_osx_envrestore_prefix, plen)) {
+                    int unset = (e[plen] == 'u');
+                    char *pname = dupprintf("%.*s", (int)strcspn(e, "="), e);
+                    char *name = pname + plen + 1;
+                    char *value = e + strcspn(e, "=");
+                    if (*value) value++;
+                    value = dupstr(value);
+                    if (unset)
+                        unsetenv(name);
+                    else
+                        setenv(name, value, 1);
+                    unsetenv(pname);
+                    sfree(pname);
+                    sfree(value);
+                    goto restart_osx_env_restore;
+                }
+            }
+        }
+
        slavefd = pty_open_slave(pty);
        if (slavefd < 0) {
            perror("slave pty: open");
@@ -776,7 +846,7 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
        }
 
        close(pty->master_fd);
-       fcntl(slavefd, F_SETFD, 0);    /* don't close on exec */
+       noncloexec(slavefd);
        dup2(slavefd, 0);
        dup2(slavefd, 1);
        dup2(slavefd, 2);
@@ -787,8 +857,40 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
 #endif
        pgrp = getpid();
        tcsetpgrp(0, pgrp);
+
+        /*
+         * Set up configuration-dependent termios settings on the new
+         * pty. Linux would have let us do this on the pty master
+         * before we forked, but that fails on OS X, so we do it here
+         * instead.
+         */
+       if (tcgetattr(0, &attrs) == 0) {
+            /*
+             * Set the backspace character to be whichever of ^H and
+             * ^? is specified by bksp_is_delete.
+             */
+            attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete)
+                ? '\177' : '\010';
+
+            /*
+             * Set the IUTF8 bit iff the character set is UTF-8.
+             */
+#ifdef IUTF8
+            if (frontend_is_utf8(frontend))
+                attrs.c_iflag |= IUTF8;
+            else
+                attrs.c_iflag &= ~IUTF8;
+#endif
+
+            tcsetattr(0, TCSANOW, &attrs);
+        }
+
        setpgid(pgrp, pgrp);
-       close(open(pty->name, O_WRONLY, 0));
+        {
+            int ptyfd = open(pty->name, O_WRONLY, 0);
+            if (ptyfd >= 0)
+                close(ptyfd);
+        }
        setpgid(pgrp, pgrp);
        {
            char *term_env_var = dupprintf("TERM=%s",
@@ -806,6 +908,19 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
             * environment in place.
             */
        }
+        {
+            /*
+             * In case we were invoked with a --display argument that
+             * doesn't match DISPLAY in our actual environment, we
+             * should set DISPLAY for processes running inside the
+             * terminal to match the display the terminal itself is
+             * on.
+             */
+            const char *x_display = get_x_display(pty->frontend);
+            char *x_display_env_var = dupprintf("DISPLAY=%s", x_display);
+            putenv(x_display_env_var);
+            /* As above, we don't free this. */
+        }
 #endif
        {
            char *key, *val;
@@ -827,17 +942,49 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
        /*
         * SIGINT, SIGQUIT and SIGPIPE may have been set to ignored by
         * our parent, particularly by things like sh -c 'pterm &' and
-        * some window or session managers. SIGCHLD, meanwhile, was
-        * blocked during pt_main() startup. Reverse all this for our
-        * child process.
+        * some window or session managers. SIGPIPE was also
+        * (potentially) blocked by us during startup. Reverse all
+        * this for our child process.
         */
        putty_signal(SIGINT, SIG_DFL);
        putty_signal(SIGQUIT, SIG_DFL);
        putty_signal(SIGPIPE, SIG_DFL);
-       block_signal(SIGCHLD, 0);
-       if (pty_argv)
+       block_signal(SIGPIPE, 0);
+       if (pty_argv) {
+            /*
+             * Exec the exact argument list we were given.
+             */
            execvp(pty_argv[0], pty_argv);
-       else {
+            /*
+             * If that fails, and if we had exactly one argument, pass
+             * that argument to $SHELL -c.
+             *
+             * This arranges that we can _either_ follow 'pterm -e'
+             * with a list of argv elements to be fed directly to
+             * exec, _or_ with a single argument containing a command
+             * to be parsed by a shell (but, in cases of doubt, the
+             * former is more reliable).
+             *
+             * A quick survey of other terminal emulators' -e options
+             * (as of Debian squeeze) suggests that:
+             *
+             *  - xterm supports both modes, more or less like this
+             *  - gnome-terminal will only accept a one-string shell command
+             *  - Eterm, kterm and rxvt will only accept a list of
+             *    argv elements (as did older versions of pterm).
+             *
+             * It therefore seems important to support both usage
+             * modes in order to be a drop-in replacement for either
+             * xterm or gnome-terminal, and hence for anyone's
+             * plausible uses of the Debian-style alias
+             * 'x-terminal-emulator'...
+             */
+            if (pty_argv[1] == NULL) {
+                char *shell = getenv("SHELL");
+                if (shell)
+                    execl(shell, shell, "-c", pty_argv[0], (void *)NULL);
+            }
+        } else {
            char *shell = getenv("SHELL");
            char *shellname;
            if (conf_get_int(conf, CONF_login_shell)) {
@@ -878,7 +1025,7 @@ static const char *pty_init(void *frontend, void **backend_handle, Conf *conf,
 
     *backend_handle = pty;
 
-    *realhost = dupprintf("\0");
+    *realhost = dupstr("");
 
     return NULL;
 }
@@ -905,7 +1052,19 @@ static void pty_free(void *handle)
     del234(ptys_by_pid, pty);
     del234(ptys_by_fd, pty);
 
-    sfree(pty);
+    bufchain_clear(&pty->output_data);
+
+    conf_free(pty->conf);
+    pty->conf = NULL;
+
+    if (pty == single_pty) {
+        /*
+         * Leave this structure around in case we need to Restart
+         * Session.
+         */
+    } else {
+        sfree(pty);
+    }
 }
 
 static void pty_try_write(Pty pty)
@@ -938,7 +1097,7 @@ static void pty_try_write(Pty pty)
 /*
  * Called to send data down the pty.
  */
-static int pty_send(void *handle, char *buf, int len)
+static int pty_send(void *handle, const char *buf, int len)
 {
     Pty pty = (Pty)handle;
 
@@ -1089,6 +1248,7 @@ Backend pty_backend = {
     pty_provide_logctx,
     pty_unthrottle,
     pty_cfg_info,
+    NULL /* test_for_upstream */,
     "pty",
     -1,
     0