X-Git-Url: https://git.distorted.org.uk/u/mdw/putty/blobdiff_plain/0b1df79721bc25a7a5b6281b54af889a9402ec25..6db084b8b35c8dc4b510caef60f52aa02ceebcbf:/unix/uxpty.c diff --git a/unix/uxpty.c b/unix/uxpty.c index b3bda081..5ebd0504 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -75,14 +76,15 @@ typedef struct pty_tag *Pty; static int pty_signal_pipe[2] = { -1, -1 }; /* obviously bogus initial val */ struct pty_tag { - Config cfg; + Conf *conf; int master_fd, slave_fd; void *frontend; char name[FILENAME_MAX]; - int child_pid; + pid_t child_pid; int term_width, term_height; int child_dead, finished; int exit_code; + bufchain output_data; }; /* @@ -135,7 +137,7 @@ static int pty_compare_by_pid(void *av, void *bv) static int pty_find_by_pid(void *av, void *bv) { - int a = *(int *)av; + pid_t a = *(pid_t *)av; Pty b = (Pty)bv; if (a < b->child_pid) @@ -165,7 +167,8 @@ static tree234 *ptys_by_pid = NULL; static Pty single_pty = NULL; #ifndef OMIT_UTMP -static int pty_utmp_helper_pid, pty_utmp_helper_pipe; +static pid_t pty_utmp_helper_pid = -1; +static int pty_utmp_helper_pipe = -1; static int pty_stamped_utmp; static struct utmpx utmp_entry; #endif @@ -178,6 +181,7 @@ static struct utmpx utmp_entry; char **pty_argv; static void pty_close(Pty pty); +static void pty_try_write(Pty pty); #ifndef OMIT_UTMP static void setup_utmp(char *ttyname, char *location) @@ -257,7 +261,8 @@ static void cleanup_utmp(void) static void sigchld_handler(int signum) { - write(pty_signal_pipe[1], "x", 1); + if (write(pty_signal_pipe[1], "x", 1) <= 0) + /* not much we can do about it */; } #ifndef OMIT_UTMP @@ -265,15 +270,16 @@ static void fatal_sig_handler(int signum) { putty_signal(signum, SIG_DFL); cleanup_utmp(); - setuid(getuid()); raise(signum); } #endif static int pty_open_slave(Pty pty) { - if (pty->slave_fd < 0) + if (pty->slave_fd < 0) { pty->slave_fd = open(pty->name, O_RDWR); + cloexec(pty->slave_fd); + } return pty->slave_fd; } @@ -304,6 +310,8 @@ static void pty_open_master(Pty pty) strcpy(pty->name, master_name); pty->name[5] = 't'; /* /dev/ptyXX -> /dev/ttyXX */ + cloexec(pty->master_fd); + if (pty_open_slave(pty) >= 0 && access(pty->name, R_OK | W_OK) == 0) goto got_one; @@ -326,12 +334,28 @@ 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 + pty->master_fd = posix_openpt(flags); + + 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"); @@ -343,10 +367,22 @@ static void pty_open_master(Pty pty) exit(1); } + cloexec(pty->master_fd); + pty->name[FILENAME_MAX-1] = '\0'; 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); + } + if (!ptys_by_fd) ptys_by_fd = newtree234(pty_compare_by_fd); add234(ptys_by_fd, pty); @@ -375,6 +411,8 @@ void pty_pre_init(void) #endif pty = single_pty = snew(struct pty_tag); + pty->conf = NULL; + bufchain_init(&pty->output_data); /* set the child signal handler straight away; it needs to be set * before we ever fork. */ @@ -386,104 +424,106 @@ void pty_pre_init(void) if (geteuid() != getuid() || getegid() != getgid()) { pty_open_master(pty); - } #ifndef OMIT_UTMP - /* - * Fork off the utmp helper. - */ - if (pipe(pipefd) < 0) { - perror("pterm: pipe"); - exit(1); - } - pid = fork(); - if (pid < 0) { - perror("pterm: fork"); - exit(1); - } else if (pid == 0) { - char display[128], buffer[128]; - int dlen, ret; - - close(pipefd[1]); - /* - * Now sit here until we receive a display name from the - * other end of the pipe, and then stamp utmp. Unstamp utmp - * again, and exit, when the pipe closes. - */ - - dlen = 0; - while (1) { + /* + * Fork off the utmp helper. + */ + if (pipe(pipefd) < 0) { + perror("pterm: pipe"); + exit(1); + } + cloexec(pipefd[0]); + cloexec(pipefd[1]); + pid = fork(); + if (pid < 0) { + perror("pterm: fork"); + exit(1); + } else if (pid == 0) { + char display[128], buffer[128]; + int dlen, ret; + + close(pipefd[1]); + /* + * Now sit here until we receive a display name from the + * other end of the pipe, and then stamp utmp. Unstamp utmp + * again, and exit, when the pipe closes. + */ + + dlen = 0; + while (1) { - ret = read(pipefd[0], buffer, lenof(buffer)); - if (ret <= 0) { - cleanup_utmp(); - _exit(0); - } else if (!pty_stamped_utmp) { - if (dlen < lenof(display)) - memcpy(display+dlen, buffer, - min(ret, lenof(display)-dlen)); - if (buffer[ret-1] == '\0') { - /* - * Now we have a display name. NUL-terminate - * it, and stamp utmp. - */ - display[lenof(display)-1] = '\0'; - /* - * Trap as many fatal signals as we can in the - * hope of having the best possible chance to - * clean up utmp before termination. We are - * unfortunately unprotected against SIGKILL, - * but that's life. - */ - putty_signal(SIGHUP, fatal_sig_handler); - putty_signal(SIGINT, fatal_sig_handler); - putty_signal(SIGQUIT, fatal_sig_handler); - putty_signal(SIGILL, fatal_sig_handler); - putty_signal(SIGABRT, fatal_sig_handler); - putty_signal(SIGFPE, fatal_sig_handler); - putty_signal(SIGPIPE, fatal_sig_handler); - putty_signal(SIGALRM, fatal_sig_handler); - putty_signal(SIGTERM, fatal_sig_handler); - putty_signal(SIGSEGV, fatal_sig_handler); - putty_signal(SIGUSR1, fatal_sig_handler); - putty_signal(SIGUSR2, fatal_sig_handler); + ret = read(pipefd[0], buffer, lenof(buffer)); + if (ret <= 0) { + cleanup_utmp(); + _exit(0); + } else if (!pty_stamped_utmp) { + if (dlen < lenof(display)) + memcpy(display+dlen, buffer, + min(ret, lenof(display)-dlen)); + if (buffer[ret-1] == '\0') { + /* + * Now we have a display name. NUL-terminate + * it, and stamp utmp. + */ + display[lenof(display)-1] = '\0'; + /* + * Trap as many fatal signals as we can in the + * hope of having the best possible chance to + * clean up utmp before termination. We are + * unfortunately unprotected against SIGKILL, + * but that's life. + */ + putty_signal(SIGHUP, fatal_sig_handler); + putty_signal(SIGINT, fatal_sig_handler); + putty_signal(SIGQUIT, fatal_sig_handler); + putty_signal(SIGILL, fatal_sig_handler); + putty_signal(SIGABRT, fatal_sig_handler); + putty_signal(SIGFPE, fatal_sig_handler); + putty_signal(SIGPIPE, fatal_sig_handler); + putty_signal(SIGALRM, fatal_sig_handler); + putty_signal(SIGTERM, fatal_sig_handler); + putty_signal(SIGSEGV, fatal_sig_handler); + putty_signal(SIGUSR1, fatal_sig_handler); + putty_signal(SIGUSR2, fatal_sig_handler); #ifdef SIGBUS - putty_signal(SIGBUS, fatal_sig_handler); + putty_signal(SIGBUS, fatal_sig_handler); #endif #ifdef SIGPOLL - putty_signal(SIGPOLL, fatal_sig_handler); + putty_signal(SIGPOLL, fatal_sig_handler); #endif #ifdef SIGPROF - putty_signal(SIGPROF, fatal_sig_handler); + putty_signal(SIGPROF, fatal_sig_handler); #endif #ifdef SIGSYS - putty_signal(SIGSYS, fatal_sig_handler); + putty_signal(SIGSYS, fatal_sig_handler); #endif #ifdef SIGTRAP - putty_signal(SIGTRAP, fatal_sig_handler); + putty_signal(SIGTRAP, fatal_sig_handler); #endif #ifdef SIGVTALRM - putty_signal(SIGVTALRM, fatal_sig_handler); + putty_signal(SIGVTALRM, fatal_sig_handler); #endif #ifdef SIGXCPU - putty_signal(SIGXCPU, fatal_sig_handler); + putty_signal(SIGXCPU, fatal_sig_handler); #endif #ifdef SIGXFSZ - putty_signal(SIGXFSZ, fatal_sig_handler); + putty_signal(SIGXFSZ, fatal_sig_handler); #endif #ifdef SIGIO - putty_signal(SIGIO, fatal_sig_handler); + putty_signal(SIGIO, fatal_sig_handler); #endif - setup_utmp(pty->name, display); - } - } - } - } else { - close(pipefd[0]); - pty_utmp_helper_pid = pid; - pty_utmp_helper_pipe = pipefd[1]; - } + setup_utmp(pty->name, display); + } + } + } + } else { + close(pipefd[0]); + pty_utmp_helper_pid = pid; + pty_utmp_helper_pipe = pipefd[1]; + } #endif + } /* Drop privs. */ { @@ -491,11 +531,23 @@ 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 } } @@ -555,10 +607,17 @@ int pty_real_select_result(Pty pty, int event, int status) } else if (ret > 0) { from_backend(pty->frontend, 0, buf, ret); } - } + } else if (event == 2) { + /* + * Attempt to send data down the pty. + */ + pty_try_write(pty); + } } if (finished && !pty->finished) { + int close_on_exit; + uxsel_del(pty->master_fd); pty_close(pty); pty->master_fd = -1; @@ -571,9 +630,11 @@ int pty_real_select_result(Pty pty, int event, int status) * Only On Clean and it wasn't a clean exit) do we output a * `terminated' message. */ - if (pty->cfg.close_on_exit == FORCE_OFF || - (pty->cfg.close_on_exit == AUTO && pty->exit_code != 0)) { + close_on_exit = conf_get_int(pty->conf, CONF_close_on_exit); + 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)); @@ -602,16 +663,16 @@ int pty_select_result(int fd, int event) if (fd == pty_signal_pipe[0]) { pid_t pid; - int ipid; int status; char c[1]; - read(pty_signal_pipe[0], c, 1); /* ignore its value; it'll be `x' */ + if (read(pty_signal_pipe[0], c, 1) <= 0) + /* ignore error */; + /* ignore its value; it'll be `x' */ do { pid = waitpid(-1, &status, WNOHANG); - ipid = pid; pty = find234(ptys_by_pid, &pid, pty_find_by_pid); if (pty) @@ -629,7 +690,12 @@ int pty_select_result(int fd, int event) static void pty_uxsel_setup(Pty pty) { - uxsel_set(pty->master_fd, 1, pty_select_result); + int rwx; + + rwx = 1; /* always want to read from pty */ + if (bufchain_size(&pty->output_data)) + rwx |= 2; /* might also want to write to it */ + uxsel_set(pty->master_fd, rwx, pty_select_result); /* * In principle this only needs calling once for all pty @@ -647,7 +713,7 @@ static void pty_uxsel_setup(Pty pty) * Also places the canonical host name into `realhost'. It must be * freed by the caller. */ -static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, +static const char *pty_init(void *frontend, void **backend_handle, Conf *conf, char *host, int port, char **realhost, int nodelay, int keepalive) { @@ -660,6 +726,7 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, if (single_pty) { pty = single_pty; + assert(pty->conf == NULL); } else { pty = snew(struct pty_tag); pty->master_fd = pty->slave_fd = -1; @@ -671,9 +738,9 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, pty->frontend = frontend; *backend_handle = NULL; /* we can't sensibly use this, sadly */ - pty->cfg = *cfg; /* structure copy */ - pty->term_width = cfg->width; - pty->term_height = cfg->height; + pty->conf = conf_copy(conf); + pty->term_width = conf_get_int(conf, CONF_width); + pty->term_height = conf_get_int(conf, CONF_height); if (pty->master_fd < 0) pty_open_master(pty); @@ -685,7 +752,8 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, { struct termios attrs; tcgetattr(pty->master_fd, &attrs); - attrs.c_cc[VERASE] = cfg->bksp_is_delete ? '\177' : '\010'; + attrs.c_cc[VERASE] = conf_get_int(conf, CONF_bksp_is_delete) + ? '\177' : '\010'; tcsetattr(pty->master_fd, TCSANOW, &attrs); } @@ -694,21 +762,23 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, * Stamp utmp (that is, tell the utmp helper process to do so), * or not. */ - if (!cfg->stamp_utmp) { - close(pty_utmp_helper_pipe); /* just let the child process die */ - pty_utmp_helper_pipe = -1; - } else { - 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 { + 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 @@ -727,7 +797,6 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, } if (pid == 0) { - int i; /* * We are the child. */ @@ -743,41 +812,40 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, dup2(slavefd, 0); dup2(slavefd, 1); dup2(slavefd, 2); + close(slavefd); setsid(); - ioctl(slavefd, TIOCSCTTY, 1); +#ifdef TIOCSCTTY + ioctl(0, TIOCSCTTY, 1); +#endif pgrp = getpid(); - tcsetpgrp(slavefd, pgrp); + tcsetpgrp(0, pgrp); setpgid(pgrp, pgrp); close(open(pty->name, O_WRONLY, 0)); setpgid(pgrp, pgrp); - /* Close everything _else_, for tidiness. */ - for (i = 3; i < 1024; i++) - close(i); { - char term_env_var[10 + sizeof(cfg->termtype)]; - sprintf(term_env_var, "TERM=%s", cfg->termtype); + char *term_env_var = dupprintf("TERM=%s", + conf_get_str(conf, CONF_termtype)); putenv(term_env_var); + /* We mustn't free term_env_var, as putenv links it into the + * environment in place. + */ } #ifndef NOT_X_WINDOWS /* for Mac OS X native compilation */ { - char windowid_env_var[40]; - sprintf(windowid_env_var, "WINDOWID=%ld", windowid); + char *windowid_env_var = dupprintf("WINDOWID=%ld", windowid); putenv(windowid_env_var); + /* We mustn't free windowid_env_var, as putenv links it into the + * environment in place. + */ } #endif { - char *e = cfg->environmt; - char *var, *varend, *val, *varval; - while (*e) { - var = e; - while (*e && *e != '\t') e++; - varend = e; - if (*e == '\t') e++; - val = e; - while (*e) e++; - e++; - - varval = dupprintf("%.*s=%s", varend-var, var, val); + char *key, *val; + + for (val = conf_get_str_strs(conf, CONF_environmt, NULL, &key); + val != NULL; + val = conf_get_str_strs(conf, CONF_environmt, key, &key)) { + char *varval = dupcat(key, "=", val, NULL); putenv(varval); /* * We must not free varval, since putenv links it @@ -789,21 +857,54 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, } /* - * SIGINT and SIGQUIT may have been set to ignored by our - * parent, particularly by things like sh -c 'pterm &' and - * some window managers. SIGCHLD, meanwhile, was blocked - * during pt_main() startup. Reverse all this for our child - * process. + * 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. */ putty_signal(SIGINT, SIG_DFL); putty_signal(SIGQUIT, SIG_DFL); + putty_signal(SIGPIPE, SIG_DFL); block_signal(SIGCHLD, 0); - if (pty_argv) + 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 (cfg->login_shell) { + if (conf_get_int(conf, CONF_login_shell)) { char *p = strrchr(shell, '/'); shellname = snewn(2+strlen(shell), char); p = p ? p+1 : shell; @@ -829,18 +930,24 @@ static const char *pty_init(void *frontend, void **backend_handle, Config *cfg, add234(ptys_by_pid, pty); } - if (pty_signal_pipe[0] < 0 && pipe(pty_signal_pipe) < 0) { - perror("pipe"); - exit(1); + if (pty_signal_pipe[0] < 0) { + if (pipe(pty_signal_pipe) < 0) { + perror("pipe"); + exit(1); + } + cloexec(pty_signal_pipe[0]); + cloexec(pty_signal_pipe[1]); } pty_uxsel_setup(pty); *backend_handle = pty; + *realhost = dupprintf("\0"); + return NULL; } -static void pty_reconfig(void *handle, Config *cfg) +static void pty_reconfig(void *handle, Conf *conf) { Pty pty = (Pty)handle; /* @@ -848,7 +955,7 @@ static void pty_reconfig(void *handle, Config *cfg) * unfortunately we do need to pick up the setting of Close On * Exit so we know whether to give a `terminated' message. */ - pty->cfg = *cfg; /* structure copy */ + conf_copy_into(pty->conf, conf); } /* @@ -862,7 +969,44 @@ static void pty_free(void *handle) del234(ptys_by_pid, pty); del234(ptys_by_fd, pty); - sfree(pty); + 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) +{ + void *data; + int len, ret; + + assert(pty->master_fd >= 0); + + while (bufchain_size(&pty->output_data) > 0) { + bufchain_prefix(&pty->output_data, &data, &len); + ret = write(pty->master_fd, data, len); + + if (ret < 0 && (errno == EWOULDBLOCK)) { + /* + * We've sent all we can for the moment. + */ + break; + } + if (ret < 0) { + perror("write pty master"); + exit(1); + } + bufchain_consume(&pty->output_data, ret); + } + + pty_uxsel_setup(pty); } /* @@ -873,18 +1017,12 @@ static int pty_send(void *handle, char *buf, int len) Pty pty = (Pty)handle; if (pty->master_fd < 0) - return 0; /* ignore all writes if fd closed */ + return 0; /* ignore all writes if fd closed */ - while (len > 0) { - int ret = write(pty->master_fd, buf, len); - if (ret < 0) { - perror("write pty master"); - exit(1); - } - buf += ret; - len -= ret; - } - return 0; + bufchain_add(&pty->output_data, buf, len); + pty_try_write(pty); + + return bufchain_size(&pty->output_data); } static void pty_close(Pty pty) @@ -957,10 +1095,10 @@ static const struct telnet_special *pty_get_specials(void *handle) return NULL; } -static Socket pty_socket(void *handle) +static int pty_connected(void *handle) { /* Pty pty = (Pty)handle; */ - return NULL; /* shouldn't ever be needed */ + return TRUE; } static int pty_sendok(void *handle) @@ -1017,7 +1155,7 @@ Backend pty_backend = { pty_size, pty_special, pty_get_specials, - pty_socket, + pty_connected, pty_exitcode, pty_sendok, pty_ldisc, @@ -1025,5 +1163,7 @@ Backend pty_backend = { pty_provide_logctx, pty_unthrottle, pty_cfg_info, - 1 + "pty", + -1, + 0 };