diff options
Diffstat (limited to 'ext/pty')
| -rw-r--r-- | ext/pty/depend | 3 | ||||
| -rw-r--r-- | ext/pty/extconf.rb | 9 | ||||
| -rw-r--r-- | ext/pty/pty.c | 166 |
3 files changed, 139 insertions, 39 deletions
diff --git a/ext/pty/depend b/ext/pty/depend index d4d0d558ef..8fa018d084 100644 --- a/ext/pty/depend +++ b/ext/pty/depend @@ -138,6 +138,7 @@ pty.o: $(hdrdir)/ruby/internal/intern/re.h pty.o: $(hdrdir)/ruby/internal/intern/ruby.h pty.o: $(hdrdir)/ruby/internal/intern/select.h pty.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +pty.o: $(hdrdir)/ruby/internal/intern/set.h pty.o: $(hdrdir)/ruby/internal/intern/signal.h pty.o: $(hdrdir)/ruby/internal/intern/sprintf.h pty.o: $(hdrdir)/ruby/internal/intern/string.h @@ -157,6 +158,7 @@ pty.o: $(hdrdir)/ruby/internal/special_consts.h pty.o: $(hdrdir)/ruby/internal/static_assert.h pty.o: $(hdrdir)/ruby/internal/stdalign.h pty.o: $(hdrdir)/ruby/internal/stdbool.h +pty.o: $(hdrdir)/ruby/internal/stdckdint.h pty.o: $(hdrdir)/ruby/internal/symbol.h pty.o: $(hdrdir)/ruby/internal/value.h pty.o: $(hdrdir)/ruby/internal/value_type.h @@ -171,6 +173,7 @@ pty.o: $(hdrdir)/ruby/ruby.h pty.o: $(hdrdir)/ruby/st.h pty.o: $(hdrdir)/ruby/subst.h pty.o: $(hdrdir)/ruby/util.h +pty.o: $(top_srcdir)/id_table.h pty.o: $(top_srcdir)/internal.h pty.o: $(top_srcdir)/internal/array.h pty.o: $(top_srcdir)/internal/compilers.h diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index ba0c4286fd..ae2cb45d3c 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -13,11 +13,16 @@ if /mswin|mingw|bccwin/ !~ RUBY_PLATFORM have_header("util.h") # OpenBSD openpty util = have_library("util", "openpty") end - if have_func("posix_openpt") or + openpt = have_func("posix_openpt") + if openpt + have_func("ptsname_r") or have_func("ptsname") + end + if openpt or (util or have_func("openpty")) or have_func("_getpty") or - have_func("ptsname") or have_func("ioctl") + have_macro("HAVE_FCHMOD") or have_func("fchmod") + have_macro("HAVE_FCHOWN") or have_func("fchown") create_makefile('pty') end end diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 8dca8ba281..3d5977707f 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -92,9 +92,13 @@ struct pty_info { static void getDevice(int*, int*, char [DEVICELEN], int); +static int start_new_session(char *errbuf, size_t errbuf_len); +static int obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len); +static int drop_privilege(char *errbuf, size_t errbuf_len); + struct child_info { int master, slave; - char *slavename; + const char *slavename; VALUE execarg_obj; struct rb_execarg *eargp; }; @@ -102,26 +106,42 @@ struct child_info { static int chfunc(void *data, char *errbuf, size_t errbuf_len) { - struct child_info *carg = data; + const struct child_info *carg = data; int master = carg->master; int slave = carg->slave; + const char *slavename = carg->slavename; + + if (start_new_session(errbuf, errbuf_len)) + return -1; + + if (obtain_ctty(master, slave, slavename, errbuf, errbuf_len)) + return -1; + + if (drop_privilege(errbuf, errbuf_len)) + return -1; + + return rb_exec_async_signal_safe(carg->eargp, errbuf, errbuf_len); +} #define ERROR_EXIT(str) do { \ strlcpy(errbuf, (str), errbuf_len); \ return -1; \ } while (0) - /* - * Set free from process group and controlling terminal - */ +/* + * Set free from process group and controlling terminal + */ +static int +start_new_session(char *errbuf, size_t errbuf_len) +{ #ifdef HAVE_SETSID (void) setsid(); #else /* HAS_SETSID */ # ifdef HAVE_SETPGRP -# ifdef SETGRP_VOID +# ifdef SETPGRP_VOID if (setpgrp() == -1) ERROR_EXIT("setpgrp()"); -# else /* SETGRP_VOID */ +# else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == -1) ERROR_EXIT("setpgrp()"); { @@ -132,20 +152,25 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) ERROR_EXIT("ioctl(TIOCNOTTY)"); close(i); } -# endif /* SETGRP_VOID */ +# endif /* SETPGRP_VOID */ # endif /* HAVE_SETPGRP */ #endif /* HAS_SETSID */ + return 0; +} - /* - * obtain new controlling terminal - */ +/* + * obtain new controlling terminal + */ +static int +obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len) +{ #if defined(TIOCSCTTY) close(master); (void) ioctl(slave, TIOCSCTTY, (char *)0); /* errors ignored for sun */ #else close(slave); - slave = rb_cloexec_open(carg->slavename, O_RDWR, 0); + slave = rb_cloexec_open(slavename, O_RDWR, 0); if (slave < 0) { ERROR_EXIT("open: pty slave"); } @@ -155,14 +180,20 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) dup2(slave,0); dup2(slave,1); dup2(slave,2); - if (slave < 0 || slave > 2) (void)!close(slave); + if (slave > 2) (void)!close(slave); + return 0; +} + +static int +drop_privilege(char *errbuf, size_t errbuf_len) +{ #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID) if (seteuid(getuid())) ERROR_EXIT("seteuid()"); #endif + return 0; +} - return rb_exec_async_signal_safe(carg->eargp, errbuf, sizeof(errbuf_len)); #undef ERROR_EXIT -} static void establishShell(int argc, VALUE *argv, struct pty_info *info, @@ -184,9 +215,13 @@ establishShell(int argc, VALUE *argv, struct pty_info *info, else { #if defined HAVE_PWD_H const char *username = getenv("USER"); - struct passwd *pwent = getpwnam(username ? username : getlogin()); - if (pwent && pwent->pw_shell) - shellname = pwent->pw_shell; + if (username == NULL) + username = getlogin(); + if (username != NULL) { + struct passwd *pwent = getpwnam(username); + if (pwent && pwent->pw_shell) + shellname = pwent->pw_shell; + } #endif } v = rb_str_new2(shellname); @@ -225,12 +260,37 @@ establishShell(int argc, VALUE *argv, struct pty_info *info, RB_GC_GUARD(carg.execarg_obj); } -#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME) +#if (defined(HAVE_POSIX_OPENPT) || defined(HAVE_PTSNAME)) && !defined(HAVE_PTSNAME_R) +/* glibc only, not obsolete interface on Tru64 or HP-UX */ static int -no_mesg(char *slavedevice, int nomesg) +ptsname_r(int fd, char *buf, size_t buflen) +{ + extern char *ptsname(int); + char *name = ptsname(fd); + if (!name) return -1; + strlcpy(buf, name, buflen); + return 0; +} +# define HAVE_PTSNAME_R 1 +#endif + +#ifdef HAVE_FCHMOD +# define change_mode(name, fd, mode) fchmod(fd, mode) +#else +# define change_mode(name, fd, mode) chmod(name, mode) +#endif +#ifdef HAVE_FCHOWN +# define change_owner(name, fd, uid, gid) fchown(fd, uid, gid) +#else +# define change_owner(name, fd, uid, gid) chown(name, uid, gid) +#endif + +#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME_R) +static inline int +prevent_messages(const char *slavedevice, int fd, int nomesg) { if (nomesg) - return chmod(slavedevice, 0600); + return change_mode(slavedevice, fd, 0600); else return 0; } @@ -258,13 +318,19 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, /* Unix98 PTY */ int masterfd = -1, slavefd = -1; char *slavedevice; + struct sigaction dfl, old; + + dfl.sa_handler = SIG_DFL; + dfl.sa_flags = 0; + sigemptyset(&dfl.sa_mask); #if defined(__sun) || defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version < 902000) /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ /* FreeBSD 9.2 or later supports O_CLOEXEC * http://www.freebsd.org/cgi/query-pr.cgi?pr=162374 */ if ((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1) goto error; - if (rb_grantpt(masterfd) == -1) goto error; + if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; + if (grantpt(masterfd) == -1) goto grantpt_error; rb_fd_fix_cloexec(masterfd); #else { @@ -278,12 +344,15 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if ((masterfd = posix_openpt(flags)) == -1) goto error; } rb_fd_fix_cloexec(masterfd); - if (rb_grantpt(masterfd) == -1) goto error; + if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; + if (grantpt(masterfd) == -1) goto grantpt_error; #endif + if (sigaction(SIGCHLD, &old, NULL) == -1) goto error; if (unlockpt(masterfd) == -1) goto error; - if ((slavedevice = ptsname(masterfd)) == NULL) goto error; - if (no_mesg(slavedevice, nomesg) == -1) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; + if (prevent_messages(slavedevice, slavefd, nomesg) == -1) goto error; rb_update_max_fd(slavefd); #if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) @@ -294,9 +363,10 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; + grantpt_error: + sigaction(SIGCHLD, &old, NULL); error: if (slavefd != -1) close(slavefd); if (masterfd != -1) close(masterfd); @@ -316,7 +386,9 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, } rb_fd_fix_cloexec(*master); rb_fd_fix_cloexec(*slave); - if (no_mesg(SlaveName, nomesg) == -1) { + if (prevent_messages(SlaveName, *slave, nomesg) == -1) { + close(*master); + close(*slave); if (!fail) return -1; rb_raise(rb_eRuntimeError, "can't chmod slave pty"); } @@ -346,23 +418,27 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, char *slavedevice; void (*s)(); - extern char *ptsname(int); extern int unlockpt(int); + extern int grantpt(int); #if defined(__sun) /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ if((masterfd = open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; - if(rb_grantpt(masterfd) == -1) goto error; + s = signal(SIGCHLD, SIG_DFL); + if(grantpt(masterfd) == -1) goto error; rb_fd_fix_cloexec(masterfd); #else if((masterfd = rb_cloexec_open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; rb_update_max_fd(masterfd); - if(rb_grantpt(masterfd) == -1) goto error; + s = signal(SIGCHLD, SIG_DFL); + if(grantpt(masterfd) == -1) goto error; #endif + signal(SIGCHLD, s); if(unlockpt(masterfd) == -1) goto error; - if((slavedevice = ptsname(masterfd)) == NULL) goto error; - if (no_mesg(slavedevice, nomesg) == -1) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error; + if (prevent_messages(slavedevice, slavefd, nomesg) == -1) goto error; rb_update_max_fd(slavefd); #if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) if(ioctl_I_PUSH(slavefd, "ptem") == -1) goto error; @@ -371,7 +447,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; error: @@ -416,8 +491,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if ((slavefd = rb_cloexec_open(SlaveName,O_RDWR,0)) >= 0) { rb_update_max_fd(slavefd); *slave = slavefd; - if (chown(SlaveName, getuid(), getgid()) != 0) goto error; - if (chmod(SlaveName, nomesg ? 0600 : 0622) != 0) goto error; + if (change_owner(SlaveName, slavefd, getuid(), getgid()) != 0) goto error; + if (change_mode(SlaveName, slavefd, nomesg ? 0600 : 0622) != 0) goto error; return 0; } close(masterfd); @@ -548,9 +623,17 @@ pty_detach_process(VALUE v) * +env+ is an optional hash that provides additional environment variables to the spawned pty. * * # sets FOO to "bar" - * PTY.spawn({"FOO"=>"bar"}, "printenv", "FOO") { |r,w,pid| p r.read } #=> "bar\r\n" + * PTY.spawn({"FOO"=>"bar"}, "printenv", "FOO") do |r, w, pid| + * p r.read #=> "bar\r\n" + * ensure + * r.close; w.close; Process.wait(pid) + * end * # unsets FOO - * PTY.spawn({"FOO"=>nil}, "printenv", "FOO") { |r,w,pid| p r.read } #=> "" + * PTY.spawn({"FOO"=>nil}, "printenv", "FOO") do |r, w, pid| + * p r.read #=> "" + * ensure + * r.close; w.close; Process.wait(pid) + * end * * +command+ and +command_line+ are the full commands to run, given a String. * Any additional +arguments+ will be passed to the command. @@ -566,6 +649,15 @@ pty_detach_process(VALUE v) * standard output and standard error * +w+:: A writable IO that is the command's standard input * +pid+:: The process identifier for the command. + * + * === Clean up + * + * This method does not clean up like closing IOs or waiting for child + * process, except that the process is detached in the block form to + * prevent it from becoming a zombie (see Process.detach). Any other + * cleanup is the responsibility of the caller. If waiting for +pid+, + * be sure to close both +r+ and +w+ before doing so; doing it in the + * reverse order may cause deadlock on some OSes. */ static VALUE pty_getpty(int argc, VALUE *argv, VALUE self) |
