/process.c
C | 6877 lines | 4638 code | 699 blank | 1540 comment | 1142 complexity | 669245bc685590d49ec357675d92d3c9 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, AGPL-3.0, 0BSD
Large files files are truncated, but you can click here to view the full file
- /**********************************************************************
- process.c -
- $Author$
- created at: Tue Aug 10 14:30:50 JST 1993
- Copyright (C) 1993-2007 Yukihiro Matsumoto
- Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
- Copyright (C) 2000 Information-technology Promotion Agency, Japan
- **********************************************************************/
- #include "ruby/ruby.h"
- #include "ruby/io.h"
- #include "ruby/thread.h"
- #include "ruby/util.h"
- #include "internal.h"
- #include "vm_core.h"
- #include <stdio.h>
- #include <errno.h>
- #include <signal.h>
- #ifdef HAVE_STDLIB_H
- #include <stdlib.h>
- #endif
- #ifdef HAVE_UNISTD_H
- #include <unistd.h>
- #endif
- #ifdef HAVE_FCNTL_H
- #include <fcntl.h>
- #endif
- #ifdef HAVE_PROCESS_H
- #include <process.h>
- #endif
- #include <time.h>
- #include <ctype.h>
- #ifndef EXIT_SUCCESS
- #define EXIT_SUCCESS 0
- #endif
- #ifndef EXIT_FAILURE
- #define EXIT_FAILURE 1
- #endif
- #ifdef HAVE_SYS_WAIT_H
- # include <sys/wait.h>
- #endif
- #ifdef HAVE_SYS_RESOURCE_H
- # include <sys/resource.h>
- #endif
- #ifdef HAVE_SYS_PARAM_H
- # include <sys/param.h>
- #endif
- #ifndef MAXPATHLEN
- # define MAXPATHLEN 1024
- #endif
- #include "ruby/st.h"
- #ifdef __EMX__
- #undef HAVE_GETPGRP
- #endif
- #include <sys/stat.h>
- #if defined(__native_client__) && defined(NACL_NEWLIB)
- # include "nacl/stat.h"
- # include "nacl/unistd.h"
- #endif
- #ifdef HAVE_SYS_TIMES_H
- #include <sys/times.h>
- #endif
- #ifdef HAVE_PWD_H
- #include <pwd.h>
- #endif
- #ifdef HAVE_GRP_H
- #include <grp.h>
- #endif
- #define numberof(array) (int)(sizeof(array)/sizeof((array)[0]))
- #if defined(HAVE_TIMES) || defined(_WIN32)
- static VALUE rb_cProcessTms;
- #endif
- #ifndef WIFEXITED
- #define WIFEXITED(w) (((w) & 0xff) == 0)
- #endif
- #ifndef WIFSIGNALED
- #define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f))
- #endif
- #ifndef WIFSTOPPED
- #define WIFSTOPPED(w) (((w) & 0xff) == 0x7f)
- #endif
- #ifndef WEXITSTATUS
- #define WEXITSTATUS(w) (((w) >> 8) & 0xff)
- #endif
- #ifndef WTERMSIG
- #define WTERMSIG(w) ((w) & 0x7f)
- #endif
- #ifndef WSTOPSIG
- #define WSTOPSIG WEXITSTATUS
- #endif
- #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__)
- #define HAVE_44BSD_SETUID 1
- #define HAVE_44BSD_SETGID 1
- #endif
- #ifdef __NetBSD__
- #undef HAVE_SETRUID
- #undef HAVE_SETRGID
- #endif
- #ifdef BROKEN_SETREUID
- #define setreuid ruby_setreuid
- int setreuid(rb_uid_t ruid, rb_uid_t euid);
- #endif
- #ifdef BROKEN_SETREGID
- #define setregid ruby_setregid
- int setregid(rb_gid_t rgid, rb_gid_t egid);
- #endif
- #if defined(HAVE_44BSD_SETUID) || defined(__APPLE__)
- #if !defined(USE_SETREUID) && !defined(BROKEN_SETREUID)
- #define OBSOLETE_SETREUID 1
- #endif
- #if !defined(USE_SETREGID) && !defined(BROKEN_SETREGID)
- #define OBSOLETE_SETREGID 1
- #endif
- #endif
- #define preserving_errno(stmts) \
- do {int saved_errno = errno; stmts; errno = saved_errno;} while (0)
- static void check_uid_switch(void);
- static void check_gid_switch(void);
- #if 1
- #define p_uid_from_name p_uid_from_name
- #define p_gid_from_name p_gid_from_name
- #endif
- #if defined(HAVE_PWD_H)
- # ifdef HAVE_GETPWNAM_R
- # define PREPARE_GETPWNAM \
- long getpw_buf_len = sysconf(_SC_GETPW_R_SIZE_MAX); \
- char *getpw_buf = ALLOCA_N(char, (getpw_buf_len < 0 ? (getpw_buf_len = 4096) : getpw_buf_len));
- # define OBJ2UID(id) obj2uid((id), getpw_buf, getpw_buf_len)
- static rb_uid_t obj2uid(VALUE id, char *getpw_buf, size_t getpw_buf_len);
- # else
- # define PREPARE_GETPWNAM /* do nothing */
- # define OBJ2UID(id) obj2uid((id))
- static rb_uid_t obj2uid(VALUE id);
- # endif
- #else
- # define PREPARE_GETPWNAM /* do nothing */
- # define OBJ2UID(id) NUM2UIDT(id)
- # ifdef p_uid_from_name
- # undef p_uid_from_name
- # define p_uid_from_name rb_f_notimplement
- # endif
- #endif
- #if defined(HAVE_GRP_H)
- # ifdef HAVE_GETGRNAM_R
- # define PREPARE_GETGRNAM \
- long getgr_buf_len = sysconf(_SC_GETGR_R_SIZE_MAX); \
- char *getgr_buf = ALLOCA_N(char, (getgr_buf_len < 0 ? (getgr_buf_len = 4096) : getgr_buf_len));
- # define OBJ2GID(id) obj2gid((id), getgr_buf, getgr_buf_len)
- static rb_gid_t obj2gid(VALUE id, char *getgr_buf, size_t getgr_buf_len);
- # else
- # define PREPARE_GETGRNAM /* do nothing */
- # define OBJ2GID(id) obj2gid((id))
- static rb_gid_t obj2gid(VALUE id);
- # endif
- #else
- # define PREPARE_GETGRNAM /* do nothing */
- # define OBJ2GID(id) NUM2GIDT(id)
- # ifdef p_gid_from_name
- # undef p_gid_from_name
- # define p_gid_from_name rb_f_notimplement
- # endif
- #endif
- /*
- * call-seq:
- * Process.pid -> fixnum
- *
- * Returns the process id of this process. Not available on all
- * platforms.
- *
- * Process.pid #=> 27415
- */
- static VALUE
- get_pid(void)
- {
- rb_secure(2);
- return PIDT2NUM(getpid());
- }
- /*
- * call-seq:
- * Process.ppid -> fixnum
- *
- * Returns the process id of the parent of this process. Returns
- * untrustworthy value on Win32/64. Not available on all platforms.
- *
- * puts "I am #{Process.pid}"
- * Process.fork { puts "Dad is #{Process.ppid}" }
- *
- * <em>produces:</em>
- *
- * I am 27417
- * Dad is 27417
- */
- static VALUE
- get_ppid(void)
- {
- rb_secure(2);
- return PIDT2NUM(getppid());
- }
- /*********************************************************************
- *
- * Document-class: Process::Status
- *
- * <code>Process::Status</code> encapsulates the information on the
- * status of a running or terminated system process. The built-in
- * variable <code>$?</code> is either +nil+ or a
- * <code>Process::Status</code> object.
- *
- * fork { exit 99 } #=> 26557
- * Process.wait #=> 26557
- * $?.class #=> Process::Status
- * $?.to_i #=> 25344
- * $? >> 8 #=> 99
- * $?.stopped? #=> false
- * $?.exited? #=> true
- * $?.exitstatus #=> 99
- *
- * Posix systems record information on processes using a 16-bit
- * integer. The lower bits record the process status (stopped,
- * exited, signaled) and the upper bits possibly contain additional
- * information (for example the program's return code in the case of
- * exited processes). Pre Ruby 1.8, these bits were exposed directly
- * to the Ruby program. Ruby now encapsulates these in a
- * <code>Process::Status</code> object. To maximize compatibility,
- * however, these objects retain a bit-oriented interface. In the
- * descriptions that follow, when we talk about the integer value of
- * _stat_, we're referring to this 16 bit value.
- */
- static VALUE rb_cProcessStatus;
- VALUE
- rb_last_status_get(void)
- {
- return GET_THREAD()->last_status;
- }
- void
- rb_last_status_set(int status, rb_pid_t pid)
- {
- rb_thread_t *th = GET_THREAD();
- th->last_status = rb_obj_alloc(rb_cProcessStatus);
- rb_iv_set(th->last_status, "status", INT2FIX(status));
- rb_iv_set(th->last_status, "pid", PIDT2NUM(pid));
- }
- void
- rb_last_status_clear(void)
- {
- GET_THREAD()->last_status = Qnil;
- }
- /*
- * call-seq:
- * stat.to_i -> fixnum
- * stat.to_int -> fixnum
- *
- * Returns the bits in _stat_ as a <code>Fixnum</code>. Poking
- * around in these bits is platform dependent.
- *
- * fork { exit 0xab } #=> 26566
- * Process.wait #=> 26566
- * sprintf('%04x', $?.to_i) #=> "ab00"
- */
- static VALUE
- pst_to_i(VALUE st)
- {
- return rb_iv_get(st, "status");
- }
- #define PST2INT(st) NUM2INT(pst_to_i(st))
- /*
- * call-seq:
- * stat.pid -> fixnum
- *
- * Returns the process ID that this status object represents.
- *
- * fork { exit } #=> 26569
- * Process.wait #=> 26569
- * $?.pid #=> 26569
- */
- static VALUE
- pst_pid(VALUE st)
- {
- return rb_attr_get(st, rb_intern("pid"));
- }
- static void
- pst_message(VALUE str, rb_pid_t pid, int status)
- {
- rb_str_catf(str, "pid %ld", (long)pid);
- if (WIFSTOPPED(status)) {
- int stopsig = WSTOPSIG(status);
- const char *signame = ruby_signal_name(stopsig);
- if (signame) {
- rb_str_catf(str, " stopped SIG%s (signal %d)", signame, stopsig);
- }
- else {
- rb_str_catf(str, " stopped signal %d", stopsig);
- }
- }
- if (WIFSIGNALED(status)) {
- int termsig = WTERMSIG(status);
- const char *signame = ruby_signal_name(termsig);
- if (signame) {
- rb_str_catf(str, " SIG%s (signal %d)", signame, termsig);
- }
- else {
- rb_str_catf(str, " signal %d", termsig);
- }
- }
- if (WIFEXITED(status)) {
- rb_str_catf(str, " exit %d", WEXITSTATUS(status));
- }
- #ifdef WCOREDUMP
- if (WCOREDUMP(status)) {
- rb_str_cat2(str, " (core dumped)");
- }
- #endif
- }
- /*
- * call-seq:
- * stat.to_s -> string
- *
- * Show pid and exit status as a string.
- *
- * system("false")
- * p $?.to_s #=> "pid 12766 exit 1"
- *
- */
- static VALUE
- pst_to_s(VALUE st)
- {
- rb_pid_t pid;
- int status;
- VALUE str;
- pid = NUM2PIDT(pst_pid(st));
- status = PST2INT(st);
- str = rb_str_buf_new(0);
- pst_message(str, pid, status);
- return str;
- }
- /*
- * call-seq:
- * stat.inspect -> string
- *
- * Override the inspection method.
- *
- * system("false")
- * p $?.inspect #=> "#<Process::Status: pid 12861 exit 1>"
- *
- */
- static VALUE
- pst_inspect(VALUE st)
- {
- rb_pid_t pid;
- int status;
- VALUE vpid, str;
- vpid = pst_pid(st);
- if (NIL_P(vpid)) {
- return rb_sprintf("#<%s: uninitialized>", rb_class2name(CLASS_OF(st)));
- }
- pid = NUM2PIDT(vpid);
- status = PST2INT(st);
- str = rb_sprintf("#<%s: ", rb_class2name(CLASS_OF(st)));
- pst_message(str, pid, status);
- rb_str_cat2(str, ">");
- return str;
- }
- /*
- * call-seq:
- * stat == other -> true or false
- *
- * Returns +true+ if the integer value of _stat_
- * equals <em>other</em>.
- */
- static VALUE
- pst_equal(VALUE st1, VALUE st2)
- {
- if (st1 == st2) return Qtrue;
- return rb_equal(pst_to_i(st1), st2);
- }
- /*
- * call-seq:
- * stat & num -> fixnum
- *
- * Logical AND of the bits in _stat_ with <em>num</em>.
- *
- * fork { exit 0x37 }
- * Process.wait
- * sprintf('%04x', $?.to_i) #=> "3700"
- * sprintf('%04x', $? & 0x1e00) #=> "1600"
- */
- static VALUE
- pst_bitand(VALUE st1, VALUE st2)
- {
- int status = PST2INT(st1) & NUM2INT(st2);
- return INT2NUM(status);
- }
- /*
- * call-seq:
- * stat >> num -> fixnum
- *
- * Shift the bits in _stat_ right <em>num</em> places.
- *
- * fork { exit 99 } #=> 26563
- * Process.wait #=> 26563
- * $?.to_i #=> 25344
- * $? >> 8 #=> 99
- */
- static VALUE
- pst_rshift(VALUE st1, VALUE st2)
- {
- int status = PST2INT(st1) >> NUM2INT(st2);
- return INT2NUM(status);
- }
- /*
- * call-seq:
- * stat.stopped? -> true or false
- *
- * Returns +true+ if this process is stopped. This is only
- * returned if the corresponding <code>wait</code> call had the
- * <code>WUNTRACED</code> flag set.
- */
- static VALUE
- pst_wifstopped(VALUE st)
- {
- int status = PST2INT(st);
- if (WIFSTOPPED(status))
- return Qtrue;
- else
- return Qfalse;
- }
- /*
- * call-seq:
- * stat.stopsig -> fixnum or nil
- *
- * Returns the number of the signal that caused _stat_ to stop
- * (or +nil+ if self is not stopped).
- */
- static VALUE
- pst_wstopsig(VALUE st)
- {
- int status = PST2INT(st);
- if (WIFSTOPPED(status))
- return INT2NUM(WSTOPSIG(status));
- return Qnil;
- }
- /*
- * call-seq:
- * stat.signaled? -> true or false
- *
- * Returns +true+ if _stat_ terminated because of
- * an uncaught signal.
- */
- static VALUE
- pst_wifsignaled(VALUE st)
- {
- int status = PST2INT(st);
- if (WIFSIGNALED(status))
- return Qtrue;
- else
- return Qfalse;
- }
- /*
- * call-seq:
- * stat.termsig -> fixnum or nil
- *
- * Returns the number of the signal that caused _stat_ to
- * terminate (or +nil+ if self was not terminated by an
- * uncaught signal).
- */
- static VALUE
- pst_wtermsig(VALUE st)
- {
- int status = PST2INT(st);
- if (WIFSIGNALED(status))
- return INT2NUM(WTERMSIG(status));
- return Qnil;
- }
- /*
- * call-seq:
- * stat.exited? -> true or false
- *
- * Returns +true+ if _stat_ exited normally (for
- * example using an <code>exit()</code> call or finishing the
- * program).
- */
- static VALUE
- pst_wifexited(VALUE st)
- {
- int status = PST2INT(st);
- if (WIFEXITED(status))
- return Qtrue;
- else
- return Qfalse;
- }
- /*
- * call-seq:
- * stat.exitstatus -> fixnum or nil
- *
- * Returns the least significant eight bits of the return code of
- * _stat_. Only available if <code>exited?</code> is
- * +true+.
- *
- * fork { } #=> 26572
- * Process.wait #=> 26572
- * $?.exited? #=> true
- * $?.exitstatus #=> 0
- *
- * fork { exit 99 } #=> 26573
- * Process.wait #=> 26573
- * $?.exited? #=> true
- * $?.exitstatus #=> 99
- */
- static VALUE
- pst_wexitstatus(VALUE st)
- {
- int status = PST2INT(st);
- if (WIFEXITED(status))
- return INT2NUM(WEXITSTATUS(status));
- return Qnil;
- }
- /*
- * call-seq:
- * stat.success? -> true, false or nil
- *
- * Returns +true+ if _stat_ is successful, +false+ if not.
- * Returns +nil+ if <code>exited?</code> is not +true+.
- */
- static VALUE
- pst_success_p(VALUE st)
- {
- int status = PST2INT(st);
- if (!WIFEXITED(status))
- return Qnil;
- return WEXITSTATUS(status) == EXIT_SUCCESS ? Qtrue : Qfalse;
- }
- /*
- * call-seq:
- * stat.coredump? -> true or false
- *
- * Returns +true+ if _stat_ generated a coredump
- * when it terminated. Not available on all platforms.
- */
- static VALUE
- pst_wcoredump(VALUE st)
- {
- #ifdef WCOREDUMP
- int status = PST2INT(st);
- if (WCOREDUMP(status))
- return Qtrue;
- else
- return Qfalse;
- #else
- return Qfalse;
- #endif
- }
- #if !defined(HAVE_WAITPID) && !defined(HAVE_WAIT4)
- #define NO_WAITPID
- static st_table *pid_tbl;
- struct wait_data {
- rb_pid_t pid;
- int status;
- };
- static int
- wait_each(rb_pid_t pid, int status, struct wait_data *data)
- {
- if (data->status != -1) return ST_STOP;
- data->pid = pid;
- data->status = status;
- return ST_DELETE;
- }
- static int
- waitall_each(rb_pid_t pid, int status, VALUE ary)
- {
- rb_last_status_set(status, pid);
- rb_ary_push(ary, rb_assoc_new(PIDT2NUM(pid), rb_last_status_get()));
- return ST_DELETE;
- }
- #else
- struct waitpid_arg {
- rb_pid_t pid;
- int *st;
- int flags;
- };
- #endif
- static void *
- rb_waitpid_blocking(void *data)
- {
- rb_pid_t result;
- #ifndef NO_WAITPID
- struct waitpid_arg *arg = data;
- #endif
- #if defined NO_WAITPID
- result = wait(data);
- #elif defined HAVE_WAITPID
- result = waitpid(arg->pid, arg->st, arg->flags);
- #else /* HAVE_WAIT4 */
- result = wait4(arg->pid, arg->st, arg->flags, NULL);
- #endif
- return (void *)(VALUE)result;
- }
- rb_pid_t
- rb_waitpid(rb_pid_t pid, int *st, int flags)
- {
- rb_pid_t result;
- #ifndef NO_WAITPID
- struct waitpid_arg arg;
- retry:
- arg.pid = pid;
- arg.st = st;
- arg.flags = flags;
- result = (rb_pid_t)(VALUE)rb_thread_call_without_gvl(rb_waitpid_blocking, &arg,
- RUBY_UBF_PROCESS, 0);
- if (result < 0) {
- if (errno == EINTR) {
- RUBY_VM_CHECK_INTS(GET_THREAD());
- goto retry;
- }
- return (rb_pid_t)-1;
- }
- #else /* NO_WAITPID */
- if (pid_tbl) {
- st_data_t status, piddata = (st_data_t)pid;
- if (pid == (rb_pid_t)-1) {
- struct wait_data data;
- data.pid = (rb_pid_t)-1;
- data.status = -1;
- st_foreach(pid_tbl, wait_each, (st_data_t)&data);
- if (data.status != -1) {
- rb_last_status_set(data.status, data.pid);
- return data.pid;
- }
- }
- else if (st_delete(pid_tbl, &piddata, &status)) {
- rb_last_status_set(*st = (int)status, pid);
- return pid;
- }
- }
- if (flags) {
- rb_raise(rb_eArgError, "can't do waitpid with flags");
- }
- for (;;) {
- result = (rb_pid_t)(VALUE)rb_thread_blocking_region(rb_waitpid_blocking,
- st, RUBY_UBF_PROCESS, 0);
- if (result < 0) {
- if (errno == EINTR) {
- rb_thread_schedule();
- continue;
- }
- return (rb_pid_t)-1;
- }
- if (result == pid || pid == (rb_pid_t)-1) {
- break;
- }
- if (!pid_tbl)
- pid_tbl = st_init_numtable();
- st_insert(pid_tbl, pid, (st_data_t)st);
- if (!rb_thread_alone()) rb_thread_schedule();
- }
- #endif
- if (result > 0) {
- rb_last_status_set(*st, result);
- }
- return result;
- }
- /* [MG]:FIXME: I wasn't sure how this should be done, since ::wait()
- has historically been documented as if it didn't take any arguments
- despite the fact that it's just an alias for ::waitpid(). The way I
- have it below is more truthful, but a little confusing.
- I also took the liberty of putting in the pid values, as they're
- pretty useful, and it looked as if the original 'ri' output was
- supposed to contain them after "[...]depending on the value of
- aPid:".
- The 'ansi' and 'bs' formats of the ri output don't display the
- definition list for some reason, but the plain text one does.
- */
- /*
- * call-seq:
- * Process.wait() -> fixnum
- * Process.wait(pid=-1, flags=0) -> fixnum
- * Process.waitpid(pid=-1, flags=0) -> fixnum
- *
- * Waits for a child process to exit, returns its process id, and
- * sets <code>$?</code> to a <code>Process::Status</code> object
- * containing information on that process. Which child it waits on
- * depends on the value of _pid_:
- *
- * > 0:: Waits for the child whose process ID equals _pid_.
- *
- * 0:: Waits for any child whose process group ID equals that of the
- * calling process.
- *
- * -1:: Waits for any child process (the default if no _pid_ is
- * given).
- *
- * < -1:: Waits for any child whose process group ID equals the absolute
- * value of _pid_.
- *
- * The _flags_ argument may be a logical or of the flag values
- * <code>Process::WNOHANG</code> (do not block if no child available)
- * or <code>Process::WUNTRACED</code> (return stopped children that
- * haven't been reported). Not all flags are available on all
- * platforms, but a flag value of zero will work on all platforms.
- *
- * Calling this method raises a SystemCallError if there are no child
- * processes. Not available on all platforms.
- *
- * include Process
- * fork { exit 99 } #=> 27429
- * wait #=> 27429
- * $?.exitstatus #=> 99
- *
- * pid = fork { sleep 3 } #=> 27440
- * Time.now #=> 2008-03-08 19:56:16 +0900
- * waitpid(pid, Process::WNOHANG) #=> nil
- * Time.now #=> 2008-03-08 19:56:16 +0900
- * waitpid(pid, 0) #=> 27440
- * Time.now #=> 2008-03-08 19:56:19 +0900
- */
- static VALUE
- proc_wait(int argc, VALUE *argv)
- {
- VALUE vpid, vflags;
- rb_pid_t pid;
- int flags, status;
- rb_secure(2);
- flags = 0;
- if (argc == 0) {
- pid = -1;
- }
- else {
- rb_scan_args(argc, argv, "02", &vpid, &vflags);
- pid = NUM2PIDT(vpid);
- if (argc == 2 && !NIL_P(vflags)) {
- flags = NUM2UINT(vflags);
- }
- }
- if ((pid = rb_waitpid(pid, &status, flags)) < 0)
- rb_sys_fail(0);
- if (pid == 0) {
- rb_last_status_clear();
- return Qnil;
- }
- return PIDT2NUM(pid);
- }
- /*
- * call-seq:
- * Process.wait2(pid=-1, flags=0) -> [pid, status]
- * Process.waitpid2(pid=-1, flags=0) -> [pid, status]
- *
- * Waits for a child process to exit (see Process::waitpid for exact
- * semantics) and returns an array containing the process id and the
- * exit status (a <code>Process::Status</code> object) of that
- * child. Raises a SystemCallError if there are no child processes.
- *
- * Process.fork { exit 99 } #=> 27437
- * pid, status = Process.wait2
- * pid #=> 27437
- * status.exitstatus #=> 99
- */
- static VALUE
- proc_wait2(int argc, VALUE *argv)
- {
- VALUE pid = proc_wait(argc, argv);
- if (NIL_P(pid)) return Qnil;
- return rb_assoc_new(pid, rb_last_status_get());
- }
- /*
- * call-seq:
- * Process.waitall -> [ [pid1,status1], ...]
- *
- * Waits for all children, returning an array of
- * _pid_/_status_ pairs (where _status_ is a
- * <code>Process::Status</code> object).
- *
- * fork { sleep 0.2; exit 2 } #=> 27432
- * fork { sleep 0.1; exit 1 } #=> 27433
- * fork { exit 0 } #=> 27434
- * p Process.waitall
- *
- * <em>produces</em>:
- *
- * [[30982, #<Process::Status: pid 30982 exit 0>],
- * [30979, #<Process::Status: pid 30979 exit 1>],
- * [30976, #<Process::Status: pid 30976 exit 2>]]
- */
- static VALUE
- proc_waitall(void)
- {
- VALUE result;
- rb_pid_t pid;
- int status;
- rb_secure(2);
- result = rb_ary_new();
- #ifdef NO_WAITPID
- if (pid_tbl) {
- st_foreach(pid_tbl, waitall_each, result);
- }
- #else
- rb_last_status_clear();
- #endif
- for (pid = -1;;) {
- #ifdef NO_WAITPID
- pid = wait(&status);
- #else
- pid = rb_waitpid(-1, &status, 0);
- #endif
- if (pid == -1) {
- if (errno == ECHILD)
- break;
- #ifdef NO_WAITPID
- if (errno == EINTR) {
- rb_thread_schedule();
- continue;
- }
- #endif
- rb_sys_fail(0);
- }
- #ifdef NO_WAITPID
- rb_last_status_set(status, pid);
- #endif
- rb_ary_push(result, rb_assoc_new(PIDT2NUM(pid), rb_last_status_get()));
- }
- return result;
- }
- static inline ID
- id_pid(void)
- {
- ID pid;
- CONST_ID(pid, "pid");
- return pid;
- }
- static VALUE
- detach_process_pid(VALUE thread)
- {
- return rb_thread_local_aref(thread, id_pid());
- }
- static VALUE
- detach_process_watcher(void *arg)
- {
- rb_pid_t cpid, pid = (rb_pid_t)(VALUE)arg;
- int status;
- while ((cpid = rb_waitpid(pid, &status, 0)) == 0) {
- /* wait while alive */
- }
- return rb_last_status_get();
- }
- VALUE
- rb_detach_process(rb_pid_t pid)
- {
- VALUE watcher = rb_thread_create(detach_process_watcher, (void*)(VALUE)pid);
- rb_thread_local_aset(watcher, id_pid(), PIDT2NUM(pid));
- rb_define_singleton_method(watcher, "pid", detach_process_pid, 0);
- return watcher;
- }
- /*
- * call-seq:
- * Process.detach(pid) -> thread
- *
- * Some operating systems retain the status of terminated child
- * processes until the parent collects that status (normally using
- * some variant of <code>wait()</code>. If the parent never collects
- * this status, the child stays around as a <em>zombie</em> process.
- * <code>Process::detach</code> prevents this by setting up a
- * separate Ruby thread whose sole job is to reap the status of the
- * process _pid_ when it terminates. Use <code>detach</code>
- * only when you do not intent to explicitly wait for the child to
- * terminate.
- *
- * The waiting thread returns the exit status of the detached process
- * when it terminates, so you can use <code>Thread#join</code> to
- * know the result. If specified _pid_ is not a valid child process
- * ID, the thread returns +nil+ immediately.
- *
- * The waiting thread has <code>pid</code> method which returns the pid.
- *
- * In this first example, we don't reap the first child process, so
- * it appears as a zombie in the process status display.
- *
- * p1 = fork { sleep 0.1 }
- * p2 = fork { sleep 0.2 }
- * Process.waitpid(p2)
- * sleep 2
- * system("ps -ho pid,state -p #{p1}")
- *
- * <em>produces:</em>
- *
- * 27389 Z
- *
- * In the next example, <code>Process::detach</code> is used to reap
- * the child automatically.
- *
- * p1 = fork { sleep 0.1 }
- * p2 = fork { sleep 0.2 }
- * Process.detach(p1)
- * Process.waitpid(p2)
- * sleep 2
- * system("ps -ho pid,state -p #{p1}")
- *
- * <em>(produces no output)</em>
- */
- static VALUE
- proc_detach(VALUE obj, VALUE pid)
- {
- rb_secure(2);
- return rb_detach_process(NUM2PIDT(pid));
- }
- static int forked_child = 0;
- #ifdef SIGPIPE
- static RETSIGTYPE (*saved_sigpipe_handler)(int) = 0;
- #endif
- #ifdef SIGPIPE
- static RETSIGTYPE
- sig_do_nothing(int sig)
- {
- }
- #endif
- /* This function should be async-signal-safe. Actually it is. */
- static void
- before_exec_async_signal_safe(void)
- {
- #ifdef SIGPIPE
- /*
- * Some OS commands don't initialize signal handler properly. Thus we have
- * to reset signal handler before exec(). Otherwise, system() and similar
- * child process interaction might fail. (e.g. ruby -e "system 'yes | ls'")
- * [ruby-dev:12261]
- */
- saved_sigpipe_handler = signal(SIGPIPE, sig_do_nothing); /* async-signal-safe */
- #endif
- }
- static void
- before_exec_non_async_signal_safe(void)
- {
- if (!forked_child) {
- /*
- * On Mac OS X 10.5.x (Leopard) or earlier, exec() may return ENOTSUPP
- * if the process have multiple threads. Therefore we have to kill
- * internal threads temporary. [ruby-core:10583]
- * This is also true on Haiku. It returns Errno::EPERM against exec()
- * in multiple threads.
- */
- rb_thread_stop_timer_thread(0);
- }
- }
- static void
- before_exec(void)
- {
- before_exec_non_async_signal_safe();
- before_exec_async_signal_safe();
- }
- /* This function should be async-signal-safe. Actually it is. */
- static void
- after_exec_async_signal_safe(void)
- {
- #ifdef SIGPIPE
- signal(SIGPIPE, saved_sigpipe_handler); /* async-signal-safe */
- #endif
- }
- static void
- after_exec_non_async_signal_safe(void)
- {
- rb_thread_reset_timer_thread();
- rb_thread_start_timer_thread();
- forked_child = 0;
- }
- static void
- after_exec(void)
- {
- after_exec_async_signal_safe();
- after_exec_non_async_signal_safe();
- }
- #define before_fork() before_exec()
- #define after_fork() (rb_threadptr_pending_interrupt_clear(GET_THREAD()), after_exec())
- #include "dln.h"
- static void
- security(const char *str)
- {
- if (rb_env_path_tainted()) {
- if (rb_safe_level() > 0) {
- rb_raise(rb_eSecurityError, "Insecure PATH - %s", str);
- }
- }
- }
- #if defined(HAVE_FORK) && !defined(__native_client__)
- /* try_with_sh and exec_with_sh should be async-signal-safe. Actually it is.*/
- #define try_with_sh(prog, argv, envp) ((saved_errno == ENOEXEC) ? exec_with_sh((prog), (argv), (envp)) : (void)0)
- static void
- exec_with_sh(const char *prog, char **argv, char **envp)
- {
- *argv = (char *)prog;
- *--argv = (char *)"sh";
- if (envp)
- execve("/bin/sh", argv, envp); /* async-signal-safe */
- else
- execv("/bin/sh", argv); /* async-signal-safe */
- }
- #else
- #define try_with_sh(prog, argv, envp) (void)0
- #endif
- /* This function should be async-signal-safe. Actually it is. */
- static int
- proc_exec_cmd(const char *prog, VALUE argv_str, VALUE envp_str)
- {
- #ifdef __native_client__
- rb_notimplement();
- UNREACHABLE;
- #else
- char **argv;
- char **envp;
- # if defined(__EMX__) || defined(OS2)
- char **new_argv = NULL;
- # endif
- argv = ARGVSTR2ARGV(argv_str);
- if (!prog) {
- errno = ENOENT;
- return -1;
- }
- # if defined(__EMX__) || defined(OS2)
- {
- # define COMMAND "cmd.exe"
- char *extension;
- if ((extension = strrchr(prog, '.')) != NULL && STRCASECMP(extension, ".bat") == 0) {
- char *p;
- int n;
- for (n = 0; argv[n]; n++)
- /* no-op */;
- new_argv = ALLOC_N(char*, n + 2);
- for (; n > 0; n--)
- new_argv[n + 1] = argv[n];
- new_argv[1] = strcpy(ALLOC_N(char, strlen(argv[0]) + 1), argv[0]);
- for (p = new_argv[1]; *p != '\0'; p++)
- if (*p == '/')
- *p = '\\';
- new_argv[0] = COMMAND;
- argv = new_argv;
- prog = dln_find_exe_r(argv[0], 0, fbuf, sizeof(fbuf));
- if (!prog) {
- errno = ENOENT;
- return -1;
- }
- }
- }
- # endif /* __EMX__ */
- envp = envp_str ? (char **)RSTRING_PTR(envp_str) : NULL;
- if (envp_str)
- execve(prog, argv, envp); /* async-signal-safe */
- else
- execv(prog, argv); /* async-signal-safe */
- preserving_errno(try_with_sh(prog, argv, envp)); /* try_with_sh() is async-signal-safe. */
- # if defined(__EMX__) || defined(OS2)
- if (new_argv) {
- xfree(new_argv[0]);
- xfree(new_argv);
- }
- # endif
- return -1;
- #endif
- }
- /* deprecated */
- static int
- proc_exec_v(char **argv, const char *prog)
- {
- char fbuf[MAXPATHLEN];
- if (!prog)
- prog = argv[0];
- prog = dln_find_exe_r(prog, 0, fbuf, sizeof(fbuf));
- if (!prog) {
- errno = ENOENT;
- return -1;
- }
- before_exec();
- execv(prog, argv);
- preserving_errno(try_with_sh(prog, argv, 0); after_exec());
- return -1;
- }
- /* deprecated */
- int
- rb_proc_exec_n(int argc, VALUE *argv, const char *prog)
- {
- #define ARGV_COUNT(n) ((n)+1)
- #define ARGV_SIZE(n) (sizeof(char*) * ARGV_COUNT(n))
- #define ALLOC_ARGV(n, v) ALLOCV_N(char*, (v), ARGV_COUNT(n))
- char **args;
- int i;
- int ret = -1;
- VALUE v;
- args = ALLOC_ARGV(argc+1, v);
- for (i=0; i<argc; i++) {
- args[i] = RSTRING_PTR(argv[i]);
- }
- args[i] = 0;
- if (args[0]) {
- ret = proc_exec_v(args, prog);
- }
- ALLOCV_END(v);
- return ret;
- #undef ARGV_COUNT
- #undef ARGV_SIZE
- #undef ALLOC_ARGV
- }
- /* This function should be async-signal-safe. Actually it is. */
- static int
- proc_exec_sh(const char *str, VALUE envp_str)
- {
- #ifdef __native_client__
- rb_notimplement();
- UNREACHABLE;
- #else
- const char *s;
- s = str;
- while (*s == ' ' || *s == '\t' || *s == '\n')
- s++;
- if (!*s) {
- errno = ENOENT;
- return -1;
- }
- #ifdef _WIN32
- rb_w32_spawn(P_OVERLAY, (char *)str, 0);
- return -1;
- #else
- #if defined(__CYGWIN32__) || defined(__EMX__)
- {
- char fbuf[MAXPATHLEN];
- char *shell = dln_find_exe_r("sh", 0, fbuf, sizeof(fbuf));
- int status = -1;
- if (shell)
- execl(shell, "sh", "-c", str, (char *) NULL);
- else
- status = system(str);
- if (status != -1)
- exit(status);
- }
- #else
- if (envp_str)
- execle("/bin/sh", "sh", "-c", str, (char *)NULL, (char **)RSTRING_PTR(envp_str)); /* async-signal-safe */
- else
- execl("/bin/sh", "sh", "-c", str, (char *)NULL); /* async-signal-safe */
- #endif
- return -1;
- #endif /* _WIN32 */
- #endif
- }
- int
- rb_proc_exec(const char *str)
- {
- int ret;
- before_exec();
- ret = proc_exec_sh(str, Qfalse);
- preserving_errno(after_exec());
- return ret;
- }
- static void
- mark_exec_arg(void *ptr)
- {
- struct rb_execarg *eargp = ptr;
- if (eargp->use_shell)
- rb_gc_mark(eargp->invoke.sh.shell_script);
- else {
- rb_gc_mark(eargp->invoke.cmd.command_name);
- rb_gc_mark(eargp->invoke.cmd.command_abspath);
- rb_gc_mark(eargp->invoke.cmd.argv_str);
- rb_gc_mark(eargp->invoke.cmd.argv_buf);
- }
- rb_gc_mark(eargp->redirect_fds);
- rb_gc_mark(eargp->envp_str);
- rb_gc_mark(eargp->envp_buf);
- rb_gc_mark(eargp->dup2_tmpbuf);
- rb_gc_mark(eargp->rlimit_limits);
- rb_gc_mark(eargp->fd_dup2);
- rb_gc_mark(eargp->fd_close);
- rb_gc_mark(eargp->fd_open);
- rb_gc_mark(eargp->fd_dup2_child);
- rb_gc_mark(eargp->env_modification);
- rb_gc_mark(eargp->chdir_dir);
- }
- static void
- free_exec_arg(void *ptr)
- {
- xfree(ptr);
- }
- static size_t
- memsize_exec_arg(const void *ptr)
- {
- return ptr ? sizeof(struct rb_execarg) : 0;
- }
- static const rb_data_type_t exec_arg_data_type = {
- "exec_arg",
- {mark_exec_arg, free_exec_arg, memsize_exec_arg},
- };
- #if defined(_WIN32)
- #define HAVE_SPAWNV 1
- #endif
- #if !defined(HAVE_FORK) && defined(HAVE_SPAWNV)
- # define USE_SPAWNV 1
- #else
- # define USE_SPAWNV 0
- #endif
- #ifndef P_NOWAIT
- # define P_NOWAIT _P_NOWAIT
- #endif
- #if USE_SPAWNV
- #if defined(_WIN32)
- #define proc_spawn_cmd_internal(argv, prog) rb_w32_aspawn(P_NOWAIT, (prog), (argv))
- #else
- static rb_pid_t
- proc_spawn_cmd_internal(char **argv, char *prog)
- {
- char fbuf[MAXPATHLEN];
- rb_pid_t status;
- if (!prog)
- prog = argv[0];
- security(prog);
- prog = dln_find_exe_r(prog, 0, fbuf, sizeof(fbuf));
- if (!prog)
- return -1;
- before_exec();
- status = spawnv(P_NOWAIT, prog, (const char **)argv);
- if (status == -1 && errno == ENOEXEC) {
- *argv = (char *)prog;
- *--argv = (char *)"sh";
- status = spawnv(P_NOWAIT, "/bin/sh", (const char **)argv);
- after_exec();
- if (status == -1) errno = ENOEXEC;
- }
- rb_last_status_set(status == -1 ? 127 : status, 0);
- return status;
- }
- #endif
- static rb_pid_t
- proc_spawn_cmd(char **argv, VALUE prog, struct rb_execarg *eargp)
- {
- rb_pid_t pid = -1;
- if (argv[0]) {
- #if defined(_WIN32)
- DWORD flags = 0;
- if (eargp->new_pgroup_given && eargp->new_pgroup_flag) {
- flags = CREATE_NEW_PROCESS_GROUP;
- }
- pid = rb_w32_aspawn_flags(P_NOWAIT, prog ? RSTRING_PTR(prog) : 0, argv, flags);
- #else
- pid = proc_spawn_cmd_internal(argv, prog ? RSTRING_PTR(prog) : 0);
- #endif
- }
- return pid;
- }
- #if defined(_WIN32)
- #define proc_spawn_sh(str) rb_w32_spawn(P_NOWAIT, (str), 0)
- #else
- static rb_pid_t
- proc_spawn_sh(char *str)
- {
- char fbuf[MAXPATHLEN];
- rb_pid_t status;
- char *shell = dln_find_exe_r("sh", 0, fbuf, sizeof(fbuf));
- before_exec();
- status = spawnl(P_NOWAIT, (shell ? shell : "/bin/sh"), "sh", "-c", str, (char*)NULL);
- rb_last_status_set(status == -1 ? 127 : status, 0);
- after_exec();
- return status;
- }
- #endif
- #endif
- static VALUE
- hide_obj(VALUE obj)
- {
- RBASIC(obj)->klass = 0;
- return obj;
- }
- static VALUE
- check_exec_redirect_fd(VALUE v, int iskey)
- {
- VALUE tmp;
- int fd;
- if (FIXNUM_P(v)) {
- fd = FIX2INT(v);
- }
- else if (SYMBOL_P(v)) {
- ID id = SYM2ID(v);
- if (id == rb_intern("in"))
- fd = 0;
- else if (id == rb_intern("out"))
- fd = 1;
- else if (id == rb_intern("err"))
- fd = 2;
- else
- goto wrong;
- }
- else if (!NIL_P(tmp = rb_check_convert_type(v, T_FILE, "IO", "to_io"))) {
- rb_io_t *fptr;
- GetOpenFile(tmp, fptr);
- if (fptr->tied_io_for_writing)
- rb_raise(rb_eArgError, "duplex IO redirection");
- fd = fptr->fd;
- }
- else {
- rb_raise(rb_eArgError, "wrong exec redirect");
- }
- if (fd < 0) {
- wrong:
- rb_raise(rb_eArgError, "negative file descriptor");
- }
- #ifdef _WIN32
- else if (fd >= 3 && iskey) {
- rb_raise(rb_eArgError, "wrong file descriptor (%d)", fd);
- }
- #endif
- return INT2FIX(fd);
- }
- static VALUE
- check_exec_redirect1(VALUE ary, VALUE key, VALUE param)
- {
- if (ary == Qfalse) {
- ary = hide_obj(rb_ary_new());
- }
- if (!RB_TYPE_P(key, T_ARRAY)) {
- VALUE fd = check_exec_redirect_fd(key, !NIL_P(param));
- rb_ary_push(ary, hide_obj(rb_assoc_new(fd, param)));
- }
- else {
- int i, n=0;
- for (i = 0 ; i < RARRAY_LEN(key); i++) {
- VALUE v = RARRAY_PTR(key)[i];
- VALUE fd = check_exec_redirect_fd(v, !NIL_P(param));
- rb_ary_push(ary, hide_obj(rb_assoc_new(fd, param)));
- n++;
- }
- }
- return ary;
- }
- static void
- check_exec_redirect(VALUE key, VALUE val, struct rb_execarg *eargp)
- {
- VALUE param;
- VALUE path, flags, perm;
- VALUE tmp;
- ID id;
- switch (TYPE(val)) {
- case T_SYMBOL:
- id = SYM2ID(val);
- if (id == rb_intern("close")) {
- param = Qnil;
- eargp->fd_close = check_exec_redirect1(eargp->fd_close, key, param);
- }
- else if (id == rb_intern("in")) {
- param = INT2FIX(0);
- eargp->fd_dup2 = check_exec_redirect1(eargp->fd_dup2, key, param);
- }
- else if (id == rb_intern("out")) {
- param = INT2FIX(1);
- eargp->fd_dup2 = check_exec_redirect1(eargp->fd_dup2, key, param);
- }
- else if (id == rb_intern("err")) {
- param = INT2FIX(2);
- eargp->fd_dup2 = check_exec_redirect1(eargp->fd_dup2, key, param);
- }
- else {
- rb_raise(rb_eArgError, "wrong exec redirect symbol: %s",
- rb_id2name(id));
- }
- break;
- case T_FILE:
- io:
- val = check_exec_redirect_fd(val, 0);
- /* fall through */
- case T_FIXNUM:
- param = val;
- eargp->fd_dup2 = check_exec_redirect1(eargp->fd_dup2, key, param);
- break;
- case T_ARRAY:
- path = rb_ary_entry(val, 0);
- if (RARRAY_LEN(val) == 2 && SYMBOL_P(path) &&
- SYM2ID(path) == rb_intern("child")) {
- param = check_exec_redirect_fd(rb_ary_entry(val, 1), 0);
- eargp->fd_dup2_child = check_exec_redirect1(eargp->fd_dup2_child, key, param);
- }
- else {
- FilePathValue(path);
- flags = rb_ary_entry(val, 1);
- if (NIL_P(flags))
- flags = INT2NUM(O_RDONLY);
- else if (RB_TYPE_P(flags, T_STRING))
- flags = INT2NUM(rb_io_modestr_oflags(StringValueCStr(flags)));
- else
- flags = rb_to_int(flags);
- perm = rb_ary_entry(val, 2);
- perm = NIL_P(perm) ? INT2FIX(0644) : rb_to_int(perm);
- param = hide_obj(rb_ary_new3(3, hide_obj(rb_str_dup(path)),
- flags, perm));
- eargp->fd_open = check_exec_redirect1(eargp->fd_open, key, param);
- }
- break;
- case T_STRING:
- path = val;
- FilePathValue(path);
- if (RB_TYPE_P(key, T_FILE))
- key = check_exec_redirect_fd(key, 1);
- if (FIXNUM_P(key) && (FIX2INT(key) == 1 || FIX2INT(key) == 2))
- flags = INT2NUM(O_WRONLY|O_CREAT|O_TRUNC);
- else
- flags = INT2NUM(O_RDONLY);
- perm = INT2FIX(0644);
- param = hide_obj(rb_ary_new3(3, hide_obj(rb_str_dup(path)),
- flags, perm));
- eargp->fd_open = check_exec_redirect1(eargp->fd_open, key, param);
- break;
- default:
- tmp = val;
- val = rb_io_check_io(tmp);
- if (!NIL_P(val)) goto io;
- rb_raise(rb_eArgError, "wrong exec redirect action");
- }
- }
- #if defined(HAVE_SETRLIMIT) && defined(NUM2RLIM)
- static int rlimit_type_by_lname(const char *name);
- #endif
- int
- rb_execarg_addopt(VALUE execarg_obj, VALUE key, VALUE val)
- {
- struct rb_execarg *eargp = rb_execarg_get(execarg_obj);
- ID id;
- #if defined(HAVE_SETRLIMIT) && defined(NUM2RLIM)
- int rtype;
- #endif
- rb_secure(2);
- switch (TYPE(key)) {
- case T_SYMBOL:
- id = SYM2ID(key);
- #ifdef HAVE_SETPGID
- if (id == rb_intern("pgroup")) {
- pid_t pgroup;
- if (eargp->pgroup_given) {
- rb_raise(rb_eArgError, "pgroup option specified twice");
- }
- if (!RTEST(val))
- pgroup = -1; /* asis(-1) means "don't call setpgid()". */
- else if (val == Qtrue)
- pgroup = 0; /* new process group. */
- else {
- pgroup = NUM2PIDT(val);
- if (pgroup < 0) {
- rb_raise(rb_eArgError, "negative process group ID : %ld", (long)pgroup);
- }
- }
- eargp->pgroup_given = 1;
- eargp->pgroup_pgid = pgroup;
- }
- else
- #endif
- #ifdef _WIN32
- if (id == rb_intern("new_pgroup")) {
- if (eargp->new_pgroup_given) {
- rb_raise(rb_eArgError, "new_pgroup option specified twice");
- }
- eargp->new_pgroup_given = 1;
- eargp->new_pgroup_flag = RTEST(val) ? 1 : 0;
- }
- else
- #endif
- #if defined(HAVE_SETRLIMIT) && defined(NUM2RLIM)
- if (strncmp("rlimit_", rb_id2name(id), 7) == 0 &&
- (rtype = rlimit_type_by_lname(rb_id2name(id)+7)) != -1) {
- VALUE ary = eargp->rlimit_limits;
- VALUE tmp, softlim, hardlim;
- if (eargp->rlimit_limits == Qfalse)
- ary = eargp->rlimit_limits = hide_obj(rb_ary_new());
- else
- ary = eargp->rlimit_limits;
- tmp = rb_check_array_type(val);
- if (!NIL_P(tmp)) {
- if (RARRAY_LEN(tmp) == 1)
- softlim = hardlim = rb_to_int(rb_ary_entry(tmp, 0));
- else if (RARRAY_LEN(tmp) == 2) {
- softlim = rb_to_int(rb_ary_entry(tmp, 0));
- hardlim = rb_to_int(rb_ary_entry(tmp, 1));
- }
- else {
- rb_raise(rb_eArgError, "wrong exec rlimit option");
- }
- }
- else {
- softlim = hardlim = rb_to_int(val);
- }
- tmp = hide_obj(rb_ary_new3(3, INT2NUM(rtype), softlim, hardlim));
- rb_ary_push(ary, tmp);
- }
- else
- #endif
- if (id == rb_intern("unsetenv_others")) {
- if (eargp->unsetenv_others_given) {
- rb_raise(rb_eArgError, "unsetenv_others option specified twice");
- }
- eargp->unsetenv_others_given = 1;
- eargp->unsetenv_others_do = RTEST(val) ? 1 : 0;
- }
- else if (id == rb_intern("chdir")) {
- if (eargp->chdir_given) {
- rb_raise(rb_eArgError, "chdir option specified twice");
- }
- FilePathValue(val);
- eargp->chdir_given = 1;
- eargp->chdir_dir = hide_obj(rb_str_dup(val));
- }
- else if (id == rb_intern("umask")) {
- mode_t cmask = NUM2MODET(val);
- if (eargp->umask_given) {
- rb_raise(rb_eArgError, "umask option specified twice");
- }
- eargp->umask_given = 1;
- eargp->umask_mask = cmask;
- }
- else if (id == rb_intern("close_others")) {
- if (eargp->close_others_given) {
- rb_raise(rb_eArgError, "close_others option specified twice");
- }
- eargp->close_others_given = 1;
- eargp->close_others_do = RTEST(val) ? 1 : 0;
- }
- else if (id == rb_intern("in")) {
- key = INT2FIX(0);
- goto redirect;
- }
- else if (id == rb_intern("out")) {
- key = INT2FIX(1);
- goto redirect;
- }
- else if (id == rb_intern("err")) {
- key = INT2FIX(2);
- goto redirect;
- }
- else if (id == rb_intern("uid")) {
- #ifdef HAVE_SETUID
- if (eargp->uid_given) {
- rb_raise(rb_eArgError, "uid option specified twice");
- }
- check_uid_switch();
- {
- PREPARE_GETPWNAM;
- eargp->uid = OBJ2UID(val);
- eargp->uid_given = 1;
- }
- #else
- rb_raise(rb_eNotImpError,
- "uid option is unimplemented on this machine");
- #endif
- }
- else if (id == rb_intern("gid")) {
- #ifdef HAVE_SETGID
- if (eargp->gid_given) {
- rb_raise(rb_eArgError, "gid option specified twice");
- }
- check_gid_switch();
- {
- PREPARE_GETGRNAM;
- eargp->gid = OBJ2GID(val);
- eargp->gid_given = 1;
- }
- #else
- rb_raise(rb_eNotImpError,
- "gid option is unimplemented on this machine");
- #endif
- }
- else {
- return ST_STOP;
- }
- break;
- case T_FIXNUM:
- case T_FILE:
- case T_ARRAY:
- redirect:
- check_exec_redirect(key, val, eargp);
- break;
- default:
- return ST_STOP;
- }
- RB_GC_GUARD(execarg_obj);
- return ST_CONTINUE;
- }
- int
- rb_exec_arg_addopt(struct rb_exec_arg *e, VALUE key, VALUE val)
- {
- return rb_execarg_addopt(e->execarg_obj, key, val);
- }
- static int
- check_exec_options_i(st_data_t st_key, st_data_t st_val, st_data_t arg)
- {
- VALUE key = (VALUE)st_key;
- VALUE val = (VALUE)st_val;
- VALUE execarg_obj = (VALUE)arg;
- if (rb_execarg_addopt(execarg_obj, key, val) != ST_CONTINUE) {
- if (SYMBOL_P(key))
- rb_raise(rb_eArgError, "wrong exec option symbol: %"PRIsVALUE,
- key);
- rb_raise(rb_eArgError, "wrong exec option");
- }
- return ST_CONTINUE;
- }
- static int
- check_exec_options_i_extract(st_data_t st_key, st_data_t st_val, st_data_t arg)
- {
- VALUE key = (VALUE)st_key;
- VALUE val = (VALUE)st_val;
- VALUE *args = (VALUE *)arg;
- VALUE execarg_obj = args[0];
- if (rb_execarg_addopt(execarg_obj, key, val) != ST_CONTINUE) {
- VALUE nonopts = args[1];
- if (NIL_P(nonopts)) args[1] = nonopts = rb_hash_new();
- rb_hash_aset(nonopts, key, val);
- }
- return ST_CONTINUE;
- }
- static int
- check_exec_fds_1(struct rb_execarg *eargp, VALUE h, int maxhint, VALUE ary)
- {
- long i;
- if (ary != Qfalse) {
- for (i = 0; i < RARRAY_LEN(ary); i++) {
- VALUE elt = RARRAY_PTR(ary)[i];
- int fd = FIX2INT(RARRAY_PTR(elt)[0]);
- if (RTEST(rb_hash_lookup(h, INT2FIX(fd)))) {
- rb_raise(rb_eArgError, "fd %d specified twice", fd);
- }
- if (ary == eargp->fd_open || ary == eargp->fd_dup2)
- rb_hash_aset(h, INT2FIX(fd), Qtrue);
- else if (ary == eargp->fd_dup2_child)
- rb_hash_aset(h, INT2FIX(fd), RARRAY_PTR(elt)[1]);
- else /* ary == eargp->fd_close */
- rb_hash_aset(h, INT2FIX(fd), INT2FIX(-1));
- if (maxhint < fd)
- maxhint = fd;
- if (ary == eargp->fd_dup2 || ary == eargp->fd_dup2_child) {
- fd = FIX2INT(RARRAY_PTR(elt)[1]);
- if (maxhint < fd)
- maxhint = fd;
- }
- }
- }
- return maxhint;
- }
- static VALUE
- check_exec_fds(struct rb_execarg *eargp)
- {
- VALUE h = rb_hash_new();
- VALUE ary;
- int maxhint = -1;
- long i;
- maxhint = check_exec_fds_1(eargp, h, maxhint, eargp->fd_dup2);
- maxhint = check_exec_fds_1(eargp, h, maxhint, eargp->fd_close);
- maxhint = check_exec_fds_1(eargp, h, maxhint, eargp->fd_open);
- maxhint = check_exec_fds_1(eargp, h, maxhint, eargp->fd_dup2_child);
- if (eargp->fd_dup2_child) {
- ary = eargp->fd_dup2_child;
- for (i = 0; i < RARRAY_LEN(ary); i++) {
- VALUE elt = RARRAY_PTR(ary)[i];
- int newfd = FIX2INT(RARRAY_PTR(elt)[0]);
- int oldfd = FIX2INT(RARRAY_PTR(elt)[1]);
- int lastfd = oldfd;
- VALUE val = rb_hash_lookup(h, INT2FIX(lastfd));
- long depth = 0;
- while (FIXNUM_P(val) && 0 <= FIX2INT(val)) {
- lastfd = FIX2INT(val);
- val = rb_hash_lookup(h, val);
- if (RARRAY_LEN(ary) < depth)
- rb_raise(rb_eArgError, "cyclic child fd redirection from %d", oldfd);
- depth++;
- }
- if (val != Qtrue)
- rb_raise(rb_eArgError, "child fd %d is not redirected", oldfd);
- if (oldfd != lastfd) {
- VALUE val2;
- rb_ary_store(elt, 1, INT2FIX(lastfd));
- rb_hash_aset(h, INT2FIX(newfd), INT2FIX(lastfd));
- val = INT2FIX(oldfd);
- while (FIXNUM_P(val2 = rb_hash_lookup(h, val))) {
- rb_hash_aset(h, val, INT2FIX(lastfd));
- val = val2;
- }
- }
- }
- }
- eargp->close_others_maxhint = maxhint;
- return h;
- }
- static void
- rb_check_exec_options(VALUE opthash, VALUE execarg_obj)
- {
- if (RHASH_EMPTY_P(opthash))
- return;
- st_foreach(RHASH_TBL(opthash), check_exec_options_i, (st_data_t)execarg_obj);
- }
- VALUE
- rb_execarg_extract_options(VALUE execarg_obj, VALUE opthash)
- {
- VALUE args[2];
- if (RHASH_EMPTY_P(opthash))
- return Qnil;
- args[0] = execarg_obj;
- args[1] = Qnil;
- st_foreach(RHASH_TBL(opthash), check_exec_options_i_extract, (st_data_t)args);
- return args[1];
- }
- static int
- check_exec_env_i(st_data_t st_key, st_data_t st_val, st_data_t arg)
- {
- VALUE key = (VALUE)st_key;
- VALUE val = (VALUE)st_val;
- VALUE env = (VALUE)arg;
- char *k;
- k = StringValueCStr(key);
- if (strchr(k, '='))
- rb_raise(rb_eArgError, "environment name contains a equal : %s", k);
- if (!NIL_P(val))
- StringValueCStr(val);
- rb_ary_push(env, hide_obj(rb_assoc_new(key, val)));
- return ST_CONTINUE;
- }
- static VALUE
- rb_check_exec_env(VALUE hash)
- {
- VALUE env;
- env = hide_obj(rb_ary_new());
- st_foreach(RHASH_TBL(hash), check_exec_env_i, (st_data_t)env);
- return env;
- }
- static VALUE
- rb_check_argv(int argc, VALUE *argv)
- {
- VALUE tmp, prog;
- int i;
- const char *name = 0;
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
- prog = 0;
- tmp = rb_check_array_type(argv[0]);
- if (!NIL_P(tmp)) {
- if (RARRAY_LEN(tmp) != 2) {
- rb_raise(rb_eArgError, "wrong first argument");
- }
- prog = RARRAY_PTR(tmp)[0];
- argv[0] = RARRAY_PTR(tmp)[1];
- SafeStringValue(prog);
- StringValueCStr(prog);
- prog = rb_str_new_frozen(prog);
- name = RSTRING_PTR(prog);
- }
- for (i = 0; i < argc; i++) {
- SafeStringValue(argv[i]);
- argv[i] = rb_str_new_frozen(argv[i]);
- StringValueCStr(argv[i]);
- }
- security(name ? name : RSTRING_PTR(argv[0]));
- return prog;
- }
- static VALUE
- rb_exec_getargs(int *argc_p, VALUE **argv_p, int accept_shell, VALUE *env_ret, VALUE *opthash_ret)
- {
- VALUE hash, prog;
- if (0 < *argc_p) {
- hash = rb_check_hash_type((*argv_p)[*argc_p-1]);
- if (!NIL_P(hash)) {
- *opthash_ret = hash;
- (*argc_p)--;
- }
- }
- if (0 < *argc_p) {
- hash = rb_check_hash_type((*argv_p)[0]);
- if (!NIL_P(hash)) {
- *env_ret = hash;
- (*argc_p)--;
- (*argv_p)++;
- }
- }
- prog = rb_check_argv(*argc_p, *argv_p);
- if (!prog) {
- prog = (*argv_p)[0];
- if (accept_shell && *argc_p == 1) {
- *argc_p = 0;
- *argv_p = 0;
- }
- }
- return prog;
- }
- #ifndef _WIN32
- struct string_part {
- const char *ptr;
- size_t len;
- };
- static int
- compare_posix_sh(const void *key, const void *el)
- {
- const struct string_part *word = key;
- int ret = strncmp(word->ptr, el, word->len);
- if (!ret && ((const char *)el)[word->len]) ret = -1;
- return ret;
- }
- #endif
- static void
- rb_exec_fillarg(VALUE prog, int argc, VALUE *argv, VALUE env, VALUE opthash, VALUE execarg_obj)
- {
- struct rb_execarg *eargp = rb_execarg_get(execarg_obj);
- char fbuf[MAXPATHLEN];
- MEMZERO(eargp, struct rb_execarg, 1);
- if (!NIL_P(opthash)) {
- rb_check_exec_options(opthash, execarg_obj);
- }
- if (!NIL_P(env)) {
- env = rb_check_exec_env(env);
- eargp->env_modification = env;
- }
- eargp->use_shell = argc == 0;
- if (eargp->use_shell)
- eargp->invoke.sh.shell_script = prog;
- else
- eargp->invoke.cmd.command_name = prog;
- #ifndef _WIN32
- if (eargp->use_shell) {
- static const char posix_sh_cmds[][9] = {
- "!", /* reserved */
- ".", /* special built-in */
- ":", /* special built-in */
- "break", /* special built-in */
- "case", /* reserved */
- "continue", /* special …
Large files files are truncated, but you can click here to view the full file