summaryrefslogtreecommitdiff
path: root/process.c
diff options
context:
space:
mode:
Diffstat (limited to 'process.c')
-rw-r--r--process.c2225
1 files changed, 1213 insertions, 1012 deletions
diff --git a/process.c b/process.c
index 0de0b1f0a3..e443ccbe49 100644
--- a/process.c
+++ b/process.c
@@ -110,11 +110,11 @@ int initgroups(const char *, rb_gid_t);
#include "internal/thread.h"
#include "internal/variable.h"
#include "internal/warnings.h"
-#include "rjit.h"
#include "ruby/io.h"
#include "ruby/st.h"
#include "ruby/thread.h"
#include "ruby/util.h"
+#include "ractor_core.h"
#include "vm_core.h"
#include "vm_sync.h"
#include "ruby/ractor.h"
@@ -566,30 +566,20 @@ proc_get_ppid(VALUE _)
*
* Document-class: Process::Status
*
- * Process::Status encapsulates the information on the
- * status of a running or terminated system process. The built-in
- * variable <code>$?</code> is either +nil+ or a
- * Process::Status 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
- * Process::Status 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.
+ * A Process::Status contains information about a system process.
+ *
+ * Thread-local variable <tt>$?</tt> is initially +nil+.
+ * Some methods assign to it a Process::Status object
+ * that represents a system process (either running or terminated):
+ *
+ * `ruby -e "exit 99"`
+ * stat = $? # => #<Process::Status: pid 1262862 exit 99>
+ * stat.class # => Process::Status
+ * stat.to_i # => 25344
+ * stat.stopped? # => false
+ * stat.exited? # => true
+ * stat.exitstatus # => 99
+ *
*/
static VALUE rb_cProcessStatus;
@@ -603,17 +593,17 @@ struct rb_process_status {
static const rb_data_type_t rb_process_status_type = {
.wrap_struct_name = "Process::Status",
.function = {
+ .dmark = NULL,
.dfree = RUBY_DEFAULT_FREE,
+ .dsize = NULL,
},
- .data = NULL,
- .flags = RUBY_TYPED_FREE_IMMEDIATELY,
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
};
static VALUE
rb_process_status_allocate(VALUE klass)
{
- struct rb_process_status *data = NULL;
-
+ struct rb_process_status *data;
return TypedData_Make_Struct(klass, struct rb_process_status, &rb_process_status_type, data);
}
@@ -655,8 +645,7 @@ VALUE
rb_process_status_new(rb_pid_t pid, int status, int error)
{
VALUE last_status = rb_process_status_allocate(rb_cProcessStatus);
-
- struct rb_process_status *data = RTYPEDDATA_DATA(last_status);
+ struct rb_process_status *data = RTYPEDDATA_GET_DATA(last_status);
data->pid = pid;
data->status = status;
data->error = error;
@@ -669,7 +658,8 @@ static VALUE
process_status_dump(VALUE status)
{
VALUE dump = rb_class_new_instance(0, 0, rb_cObject);
- struct rb_process_status *data = RTYPEDDATA_DATA(status);
+ struct rb_process_status *data;
+ TypedData_Get_Struct(status, struct rb_process_status, &rb_process_status_type, data);
if (data->pid) {
rb_ivar_set(dump, id_status, INT2NUM(data->status));
rb_ivar_set(dump, id_pid, PIDT2NUM(data->pid));
@@ -694,36 +684,42 @@ rb_last_status_set(int status, rb_pid_t pid)
GET_THREAD()->last_status = rb_process_status_new(pid, status, 0);
}
+static void
+last_status_clear(rb_thread_t *th)
+{
+ th->last_status = Qnil;
+}
+
void
rb_last_status_clear(void)
{
- GET_THREAD()->last_status = Qnil;
+ last_status_clear(GET_THREAD());
}
static rb_pid_t
-pst_pid(VALUE pst)
+pst_pid(VALUE status)
{
- struct rb_process_status *data = RTYPEDDATA_DATA(pst);
+ struct rb_process_status *data;
+ TypedData_Get_Struct(status, struct rb_process_status, &rb_process_status_type, data);
return data->pid;
}
static int
-pst_status(VALUE pst)
+pst_status(VALUE status)
{
- struct rb_process_status *data = RTYPEDDATA_DATA(pst);
+ struct rb_process_status *data;
+ TypedData_Get_Struct(status, struct rb_process_status, &rb_process_status_type, data);
return data->status;
}
/*
* call-seq:
- * stat.to_i -> integer
+ * to_i -> integer
*
- * Returns the bits in _stat_ as an Integer. Poking
- * around in these bits is platform dependent.
+ * Returns the system-dependent integer status of +self+:
*
- * fork { exit 0xab } #=> 26566
- * Process.wait #=> 26566
- * sprintf('%04x', $?.to_i) #=> "ab00"
+ * `cat /nop`
+ * $?.to_i # => 256
*/
static VALUE
@@ -737,13 +733,13 @@ pst_to_i(VALUE self)
/*
* call-seq:
- * stat.pid -> integer
+ * pid -> integer
*
- * Returns the process ID that this status object represents.
+ * Returns the process ID of the process:
+ *
+ * system("false")
+ * $?.pid # => 1247002
*
- * fork { exit } #=> 26569
- * Process.wait #=> 26569
- * $?.pid #=> 26569
*/
static VALUE
@@ -799,12 +795,13 @@ pst_message_status(VALUE str, int status)
/*
* call-seq:
- * stat.to_s -> string
+ * to_s -> string
*
- * Show pid and exit status as a string.
+ * Returns a string representation of +self+:
+ *
+ * `cat /nop`
+ * $?.to_s # => "pid 1262141 exit 1"
*
- * system("false")
- * p $?.to_s #=> "pid 12766 exit 1"
*
*/
@@ -826,12 +823,12 @@ pst_to_s(VALUE st)
/*
* call-seq:
- * stat.inspect -> string
+ * inspect -> string
*
- * Override the inspection method.
+ * Returns a string representation of +self+:
*
* system("false")
- * p $?.inspect #=> "#<Process::Status: pid 12861 exit 1>"
+ * $?.inspect # => "#<Process::Status: pid 1303494 exit 1>"
*
*/
@@ -857,10 +854,15 @@ pst_inspect(VALUE st)
/*
* call-seq:
- * stat == other -> true or false
+ * stat == other -> true or false
+ *
+ * Returns whether the value of #to_i == +other+:
+ *
+ * `cat /nop`
+ * stat = $? # => #<Process::Status: pid 1170366 exit 1>
+ * sprintf('%x', stat.to_i) # => "100"
+ * stat == 0x100 # => true
*
- * Returns +true+ if the integer value of _stat_
- * equals <em>other</em>.
*/
static VALUE
@@ -873,53 +875,11 @@ pst_equal(VALUE st1, VALUE st2)
/*
* call-seq:
- * stat & num -> integer
- *
- * 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 -> integer
- *
- * Shift the bits in _stat_ right <em>num</em> places.
+ * stopped? -> true or false
*
- * 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 #wait call had the Process::WUNTRACED flag
- * set.
+ * Returns +true+ if this process is stopped,
+ * and if the corresponding #wait call had the Process::WUNTRACED flag set,
+ * +false+ otherwise.
*/
static VALUE
@@ -933,10 +893,10 @@ pst_wifstopped(VALUE st)
/*
* call-seq:
- * stat.stopsig -> integer or nil
+ * stopsig -> integer or nil
*
- * Returns the number of the signal that caused _stat_ to stop
- * (or +nil+ if self is not stopped).
+ * Returns the number of the signal that caused the process to stop,
+ * or +nil+ if the process is not stopped.
*/
static VALUE
@@ -952,10 +912,10 @@ pst_wstopsig(VALUE st)
/*
* call-seq:
- * stat.signaled? -> true or false
+ * signaled? -> true or false
*
- * Returns +true+ if _stat_ terminated because of
- * an uncaught signal.
+ * Returns +true+ if the process terminated because of an uncaught signal,
+ * +false+ otherwise.
*/
static VALUE
@@ -969,11 +929,10 @@ pst_wifsignaled(VALUE st)
/*
* call-seq:
- * stat.termsig -> integer or nil
+ * termsig -> integer or nil
*
- * Returns the number of the signal that caused _stat_ to
- * terminate (or +nil+ if self was not terminated by an
- * uncaught signal).
+ * Returns the number of the signal that caused the process to terminate
+ * or +nil+ if the process was not terminated by an uncaught signal.
*/
static VALUE
@@ -989,11 +948,11 @@ pst_wtermsig(VALUE st)
/*
* call-seq:
- * stat.exited? -> true or false
+ * exited? -> true or false
*
- * Returns +true+ if _stat_ exited normally (for
- * example using an <code>exit()</code> call or finishing the
- * program).
+ * Returns +true+ if the process exited normally
+ * (for example using an <code>exit()</code> call or finishing the
+ * program), +false+ if not.
*/
static VALUE
@@ -1007,20 +966,15 @@ pst_wifexited(VALUE st)
/*
* call-seq:
- * stat.exitstatus -> integer or nil
+ * exitstatus -> integer or nil
*
- * Returns the least significant eight bits of the return code of
- * _stat_. Only available if #exited? is +true+.
+ * Returns the least significant eight bits of the return code
+ * of the process if it has exited;
+ * +nil+ otherwise:
*
- * fork { } #=> 26572
- * Process.wait #=> 26572
- * $?.exited? #=> true
- * $?.exitstatus #=> 0
+ * `exit 99`
+ * $?.exitstatus # => 99
*
- * fork { exit 99 } #=> 26573
- * Process.wait #=> 26573
- * $?.exited? #=> true
- * $?.exitstatus #=> 99
*/
static VALUE
@@ -1036,10 +990,14 @@ pst_wexitstatus(VALUE st)
/*
* call-seq:
- * stat.success? -> true, false or nil
+ * success? -> true, false, or nil
+ *
+ * Returns:
+ *
+ * - +true+ if the process has completed successfully and exited.
+ * - +false+ if the process has completed unsuccessfully and exited.
+ * - +nil+ if the process has not exited.
*
- * Returns +true+ if _stat_ is successful, +false+ if not.
- * Returns +nil+ if #exited? is not +true+.
*/
static VALUE
@@ -1055,10 +1013,12 @@ pst_success_p(VALUE st)
/*
* call-seq:
- * stat.coredump? -> true or false
+ * coredump? -> true or false
+ *
+ * Returns +true+ if the process generated a coredump
+ * when it terminated, +false+ if not.
*
- * Returns +true+ if _stat_ generated a coredump
- * when it terminated. Not available on all platforms.
+ * Not available on all platforms.
*/
static VALUE
@@ -1137,8 +1097,10 @@ rb_process_status_wait(rb_pid_t pid, int flags)
// We only enter the scheduler if we are "blocking":
if (!(flags & WNOHANG)) {
VALUE scheduler = rb_fiber_scheduler_current();
- VALUE result = rb_fiber_scheduler_process_wait(scheduler, pid, flags);
- if (!UNDEF_P(result)) return result;
+ if (scheduler != Qnil) {
+ VALUE result = rb_fiber_scheduler_process_wait(scheduler, pid, flags);
+ if (!UNDEF_P(result)) return result;
+ }
}
struct waitpid_state waitpid_state;
@@ -1155,49 +1117,35 @@ rb_process_status_wait(rb_pid_t pid, int flags)
/*
* call-seq:
- * Process::Status.wait(pid=-1, flags=0) -> Process::Status
+ * Process::Status.wait(pid = -1, flags = 0) -> Process::Status
*
- * Waits for a child process to exit and returns a Process::Status object
- * containing information on that process. Which child it waits on
- * depends on the value of _pid_:
+ * Like Process.wait, but returns a Process::Status object
+ * (instead of an integer pid or nil);
+ * see Process.wait for the values of +pid+ and +flags+.
*
- * > 0:: Waits for the child whose process ID equals _pid_.
+ * If there are child processes,
+ * waits for a child process to exit and returns a Process::Status object
+ * containing information on that process;
+ * sets thread-local variable <tt>$?</tt>:
*
- * 0:: Waits for any child whose process group ID equals that of the
- * calling process.
+ * Process.spawn('cat /nop') # => 1155880
+ * Process::Status.wait # => #<Process::Status: pid 1155880 exit 1>
+ * $? # => #<Process::Status: pid 1155508 exit 1>
*
- * -1:: Waits for any child process (the default if no _pid_ is
- * given).
+ * If there is no child process,
+ * returns an "empty" Process::Status object
+ * that does not represent an actual process;
+ * does not set thread-local variable <tt>$?</tt>:
*
- * < -1:: Waits for any child whose process group ID equals the absolute
- * value of _pid_.
+ * Process::Status.wait # => #<Process::Status: pid -1 exit 0>
+ * $? # => #<Process::Status: pid 1155508 exit 1> # Unchanged.
*
- * The _flags_ argument may be a logical or of the flag values
- * Process::WNOHANG (do not block if no child available)
- * or Process::WUNTRACED (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.
+ * May invoke the scheduler hook Fiber::Scheduler#process_wait.
*
- * Returns +nil+ if there are no child processes.
* Not available on all platforms.
- *
- * May invoke the scheduler hook _process_wait_.
- *
- * fork { exit 99 } #=> 27429
- * Process::Status.wait #=> pid 27429 exit 99
- * $? #=> nil
- *
- * pid = fork { sleep 3 } #=> 27440
- * Time.now #=> 2008-03-08 19:56:16 +0900
- * Process::Status.wait(pid, Process::WNOHANG) #=> nil
- * Time.now #=> 2008-03-08 19:56:16 +0900
- * Process::Status.wait(pid, 0) #=> pid 27440 exit 99
- * Time.now #=> 2008-03-08 19:56:19 +0900
- *
- * This is an EXPERIMENTAL FEATURE.
*/
-VALUE
+static VALUE
rb_process_status_waitv(int argc, VALUE *argv, VALUE _)
{
rb_check_arity(argc, 0, 2);
@@ -1222,7 +1170,7 @@ rb_waitpid(rb_pid_t pid, int *st, int flags)
VALUE status = rb_process_status_wait(pid, flags);
if (NIL_P(status)) return 0;
- struct rb_process_status *data = RTYPEDDATA_DATA(status);
+ struct rb_process_status *data = rb_check_typeddata(status, &rb_process_status_type);
pid = data->pid;
if (st) *st = data->status;
@@ -1403,7 +1351,7 @@ proc_wait(int argc, VALUE *argv)
* or as the logical OR of both:
*
* - Process::WNOHANG: Does not block if no child process is available.
- * - Process:WUNTRACED: May return a stopped child process, even if not yet reported.
+ * - Process::WUNTRACED: May return a stopped child process, even if not yet reported.
*
* Not all flags are available on all platforms.
*
@@ -1430,7 +1378,7 @@ proc_m_wait(int c, VALUE *v, VALUE _)
* Process.wait2(pid)
* # => [309581, #<Process::Status: pid 309581 exit 13>]
*
- * Process.waitpid2 is an alias for Process.waitpid.
+ * Process.waitpid2 is an alias for Process.wait2.
*/
static VALUE
@@ -1616,42 +1564,35 @@ before_exec(void)
before_exec_async_signal_safe();
}
-/* This function should be async-signal-safe. Actually it is. */
-static void
-after_exec_async_signal_safe(void)
-{
-}
-
static void
-after_exec_non_async_signal_safe(void)
+after_exec(void)
{
rb_thread_reset_timer_thread();
rb_thread_start_timer_thread();
}
-static void
-after_exec(void)
-{
- after_exec_async_signal_safe();
- after_exec_non_async_signal_safe();
-}
-
#if defined HAVE_WORKING_FORK || defined HAVE_DAEMON
static void
before_fork_ruby(void)
{
before_exec();
+ rb_gc_before_fork();
}
static void
after_fork_ruby(rb_pid_t pid)
{
- rb_threadptr_pending_interrupt_clear(GET_THREAD());
+ rb_gc_after_fork(pid);
+
if (pid == 0) {
+ // child
clear_pid_cache();
rb_thread_atfork();
}
- after_exec();
+ else {
+ // parent
+ after_exec();
+ }
}
#endif
@@ -1799,7 +1740,7 @@ memsize_exec_arg(const void *ptr)
static const rb_data_type_t exec_arg_data_type = {
"exec_arg",
{mark_exec_arg, RUBY_TYPED_DEFAULT_FREE, memsize_exec_arg},
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE
};
#ifdef _WIN32
@@ -2263,7 +2204,7 @@ check_exec_options_i(st_data_t st_key, st_data_t st_val, st_data_t arg)
if (SYMBOL_P(key))
rb_raise(rb_eArgError, "wrong exec option symbol: % "PRIsVALUE,
key);
- rb_raise(rb_eArgError, "wrong exec option");
+ rb_raise(rb_eArgError, "wrong exec option: %"PRIsVALUE, rb_obj_class(key));
}
return ST_CONTINUE;
}
@@ -2442,12 +2383,12 @@ rb_check_argv(int argc, VALUE *argv)
}
prog = RARRAY_AREF(tmp, 0);
argv[0] = RARRAY_AREF(tmp, 1);
- SafeStringValue(prog);
+ StringValue(prog);
StringValueCStr(prog);
prog = rb_str_new_frozen(prog);
}
for (i = 0; i < argc; i++) {
- SafeStringValue(argv[i]);
+ StringValue(argv[i]);
argv[i] = rb_str_new_frozen(argv[i]);
StringValueCStr(argv[i]);
}
@@ -2694,7 +2635,7 @@ rb_exec_fillarg(VALUE prog, int argc, VALUE *argv, VALUE env, VALUE opthash, VAL
}
rb_str_buf_cat(argv_str, (char *)&null, sizeof(null)); /* terminator for execve. */
eargp->invoke.cmd.argv_str =
- rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(argv_str);
+ rb_imemo_tmpbuf_new_from_an_RString(argv_str);
}
RB_GC_GUARD(execarg_obj);
}
@@ -2743,6 +2684,7 @@ rb_execarg_setenv(VALUE execarg_obj, VALUE env)
struct rb_execarg *eargp = rb_execarg_get(execarg_obj);
env = !NIL_P(env) ? rb_check_exec_env(env, &eargp->path_env) : Qfalse;
eargp->env_modification = env;
+ RB_GC_GUARD(execarg_obj);
}
static int
@@ -2784,9 +2726,7 @@ open_func(void *ptr)
static void
rb_execarg_allocate_dup2_tmpbuf(struct rb_execarg *eargp, long len)
{
- VALUE tmpbuf = rb_imemo_tmpbuf_auto_free_pointer();
- rb_imemo_tmpbuf_set_ptr(tmpbuf, ruby_xmalloc(run_exec_dup2_tmpbuf_size(len)));
- eargp->dup2_tmpbuf = tmpbuf;
+ rb_alloc_tmp_buffer(&eargp->dup2_tmpbuf, run_exec_dup2_tmpbuf_size(len));
}
static VALUE
@@ -2888,7 +2828,7 @@ rb_execarg_parent_start1(VALUE execarg_obj)
p = NULL;
rb_str_buf_cat(envp_str, (char *)&p, sizeof(p));
eargp->envp_str =
- rb_imemo_tmpbuf_auto_free_pointer_new_from_an_RString(envp_str);
+ rb_imemo_tmpbuf_new_from_an_RString(envp_str);
eargp->envp_buf = envp_buf;
/*
@@ -2940,6 +2880,7 @@ execarg_parent_end(VALUE execarg_obj)
}
errno = err;
+ RB_GC_GUARD(execarg_obj);
return execarg_obj;
}
@@ -2947,7 +2888,6 @@ void
rb_execarg_parent_end(VALUE execarg_obj)
{
execarg_parent_end(execarg_obj);
- RB_GC_GUARD(execarg_obj);
}
static void
@@ -3014,7 +2954,7 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
* - Invoking the executable at +exe_path+.
*
* This method has potential security vulnerabilities if called with untrusted input;
- * see {Command Injection}[rdoc-ref:command_injection.rdoc].
+ * see {Command Injection}[rdoc-ref:security/command_injection.rdoc].
*
* The new process is created using the
* {exec system call}[https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/execve.html];
@@ -3031,7 +2971,7 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
*
* - +command_line+ if it is a string,
* and if it begins with a shell reserved word or special built-in,
- * or if it contains one or more metacharacters.
+ * or if it contains one or more meta characters.
* - +exe_path+ otherwise.
*
* <b>Argument +command_line+</b>
@@ -3040,8 +2980,8 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
* it must begin with a shell reserved word, begin with a special built-in,
* or contain meta characters:
*
- * exec('echo') # Built-in.
* exec('if true; then echo "Foo"; fi') # Shell reserved word.
+ * exec('exit') # Built-in.
* exec('date > date.tmp') # Contains meta character.
*
* The command line may also contain arguments and options for the command:
@@ -3052,23 +2992,9 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
*
* Foo
*
- * On a Unix-like system, the shell is <tt>/bin/sh</tt>;
- * otherwise the shell is determined by environment variable
- * <tt>ENV['RUBYSHELL']</tt>, if defined, or <tt>ENV['COMSPEC']</tt> otherwise.
+ * See {Execution Shell}[rdoc-ref:Process@Execution+Shell] for details about the shell.
*
- * Except for the +COMSPEC+ case,
- * the entire string +command_line+ is passed as an argument
- * to {shell option -c}[https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/sh.html].
- *
- * The shell performs normal shell expansion on the command line:
- *
- * exec('echo C*')
- *
- * Output:
- *
- * CONTRIBUTING.md COPYING COPYING.ja
- *
- * Raises an exception if the new process fails to execute.
+ * Raises an exception if the new process could not execute.
*
* <b>Argument +exe_path+</b>
*
@@ -3086,7 +3012,9 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
*
* Sat Aug 26 09:38:00 AM CDT 2023
*
- * Ruby invokes the executable directly, with no shell and no shell expansion:
+ * Ruby invokes the executable directly.
+ * This form does not use the shell;
+ * see {Arguments args}[rdoc-ref:Process@Arguments+args] for caveats.
*
* exec('doesnt_exist') # Raises Errno::ENOENT
*
@@ -3101,7 +3029,7 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
* C*
* hello world
*
- * Raises an exception if the new process fails to execute.
+ * Raises an exception if the new process could not execute.
*/
static VALUE
@@ -3111,9 +3039,13 @@ f_exec(int c, const VALUE *a, VALUE _)
UNREACHABLE_RETURN(Qnil);
}
-#define ERRMSG(str) do { if (errmsg && 0 < errmsg_buflen) strlcpy(errmsg, (str), errmsg_buflen); } while (0)
-#define ERRMSG1(str, a) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a)); } while (0)
-#define ERRMSG2(str, a, b) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a), (b)); } while (0)
+#define ERRMSG(str) \
+ ((errmsg && 0 < errmsg_buflen) ? \
+ (void)strlcpy(errmsg, (str), errmsg_buflen) : (void)0)
+
+#define ERRMSG_FMT(...) \
+ ((errmsg && 0 < errmsg_buflen) ? \
+ (void)snprintf(errmsg, errmsg_buflen, __VA_ARGS__) : (void)0)
static int fd_get_cloexec(int fd, char *errmsg, size_t errmsg_buflen);
static int fd_set_cloexec(int fd, char *errmsg, size_t errmsg_buflen);
@@ -3248,8 +3180,7 @@ run_exec_dup2(VALUE ary, VALUE tmpbuf, struct rb_execarg *sargp, char *errmsg, s
long n, i;
int ret;
int extra_fd = -1;
- struct rb_imemo_tmpbuf_struct *buf = (void *)tmpbuf;
- struct run_exec_dup2_fd_pair *pairs = (void *)buf->ptr;
+ struct run_exec_dup2_fd_pair *pairs = RB_IMEMO_TMPBUF_PTR(tmpbuf);
n = RARRAY_LEN(ary);
@@ -3326,6 +3257,14 @@ run_exec_dup2(VALUE ary, VALUE tmpbuf, struct rb_execarg *sargp, char *errmsg, s
ERRMSG("dup");
goto fail;
}
+ // without this, kqueue timer_th.event_fd fails with a reserved FD did not have close-on-exec
+ // in #assert_close_on_exec because the FD_CLOEXEC is not dup'd by default
+ if (fd_get_cloexec(pairs[i].oldfd, errmsg, errmsg_buflen)) {
+ if (fd_set_cloexec(extra_fd, errmsg, errmsg_buflen)) {
+ close(extra_fd);
+ goto fail;
+ }
+ }
rb_update_max_fd(extra_fd);
}
else {
@@ -4068,7 +4007,10 @@ retry_fork_async_signal_safe(struct rb_process_status *status, int *ep,
while (1) {
prefork();
disable_child_handler_before_fork(&old);
-#ifdef HAVE_WORKING_VFORK
+
+ // Older versions of ASAN does not work with vfork
+ // See https://github.com/google/sanitizers/issues/925
+#if defined(HAVE_WORKING_VFORK) && !defined(RUBY_ASAN_ENABLED)
if (!has_privilege())
pid = vfork();
else
@@ -4156,7 +4098,7 @@ fork_check_err(struct rb_process_status *status, int (*chfunc)(void*, char *, si
* The "async_signal_safe" name is a lie, but it is used by pty.c and
* maybe other exts. fork() is not async-signal-safe due to pthread_atfork
* and future POSIX revisions will remove it from a list of signal-safe
- * functions. rb_waitpid is not async-signal-safe since RJIT, either.
+ * functions. rb_waitpid is not async-signal-safe.
* For our purposes, we do not need async-signal-safety, here
*/
rb_pid_t
@@ -4175,47 +4117,47 @@ rb_fork_async_signal_safe(int *status,
return result;
}
-static rb_pid_t
-rb_fork_ruby2(struct rb_process_status *status)
+rb_pid_t
+rb_fork_ruby(int *status)
{
+ if (UNLIKELY(!rb_ractor_main_p())) {
+ rb_raise(rb_eRactorIsolationError, "can not fork from non-main Ractors");
+ }
+
+ struct rb_process_status child = {.status = 0};
rb_pid_t pid;
- int try_gc = 1, err;
+ int try_gc = 1, err = 0;
struct child_handler_disabler_state old;
- if (status) status->status = 0;
-
- while (1) {
+ do {
prefork();
- disable_child_handler_before_fork(&old);
+
before_fork_ruby();
- pid = rb_fork();
- err = errno;
- if (status) {
- status->pid = pid;
- status->error = err;
- }
- after_fork_ruby(pid);
- disable_child_handler_fork_parent(&old); /* yes, bad name */
+ rb_thread_acquire_fork_lock();
+ disable_child_handler_before_fork(&old);
- if (pid >= 0) { /* fork succeed */
- return pid;
+ RB_VM_LOCKING() {
+ child.pid = pid = rb_fork();
+ child.error = err = errno;
}
- /* fork failed */
- if (handle_fork_error(err, status, NULL, &try_gc)) {
- return -1;
+ disable_child_handler_fork_parent(&old); /* yes, bad name */
+ if (
+#if defined(__FreeBSD__)
+ pid != 0 &&
+#endif
+ true) {
+ rb_thread_release_fork_lock();
}
- }
-}
-
-rb_pid_t
-rb_fork_ruby(int *status)
-{
- struct rb_process_status process_status = {0};
+ if (pid == 0) {
+ rb_thread_reset_fork_lock();
+ }
+ after_fork_ruby(pid);
- rb_pid_t pid = rb_fork_ruby2(&process_status);
+ /* repeat while fork failed but retryable */
+ } while (pid < 0 && handle_fork_error(err, &child, NULL, &try_gc) == 0);
- if (status) *status = process_status.status;
+ if (status) *status = child.status;
return pid;
}
@@ -4287,7 +4229,7 @@ rb_proc__fork(VALUE _obj)
* puts "Before the fork: #{Process.pid}"
* fork do
* puts "In the child process: #{Process.pid}"
- * end # => 382141
+ * end # => 420520
* puts "After the fork: #{Process.pid}"
*
* Output:
@@ -4639,11 +4581,16 @@ static VALUE
do_spawn_process(VALUE arg)
{
struct spawn_args *argp = (struct spawn_args *)arg;
+
rb_execarg_parent_start1(argp->execarg);
- return (VALUE)rb_spawn_process(DATA_PTR(argp->execarg),
+
+ return (VALUE)rb_spawn_process(rb_execarg_get(argp->execarg),
argp->errmsg.ptr, argp->errmsg.buflen);
}
+NOINLINE(static rb_pid_t
+ rb_execarg_spawn(VALUE execarg_obj, char *errmsg, size_t errmsg_buflen));
+
static rb_pid_t
rb_execarg_spawn(VALUE execarg_obj, char *errmsg, size_t errmsg_buflen)
{
@@ -4652,8 +4599,10 @@ rb_execarg_spawn(VALUE execarg_obj, char *errmsg, size_t errmsg_buflen)
args.execarg = execarg_obj;
args.errmsg.ptr = errmsg;
args.errmsg.buflen = errmsg_buflen;
- return (rb_pid_t)rb_ensure(do_spawn_process, (VALUE)&args,
- execarg_parent_end, execarg_obj);
+
+ rb_pid_t r = (rb_pid_t)rb_ensure(do_spawn_process, (VALUE)&args,
+ execarg_parent_end, execarg_obj);
+ return r;
}
static rb_pid_t
@@ -4679,68 +4628,133 @@ rb_spawn(int argc, const VALUE *argv)
/*
* call-seq:
- * system([env,] command... [,options], exception: false) -> true, false or nil
+ * system([env, ] command_line, options = {}, exception: false) -> true, false, or nil
+ * system([env, ] exe_path, *args, options = {}, exception: false) -> true, false, or nil
*
- * Executes _command..._ in a subshell.
- * _command..._ is one of following forms.
+ * Creates a new child process by doing one of the following
+ * in that process:
+ *
+ * - Passing string +command_line+ to the shell.
+ * - Invoking the executable at +exe_path+.
*
* This method has potential security vulnerabilities if called with untrusted input;
- * see {Command Injection}[rdoc-ref:command_injection.rdoc].
+ * see {Command Injection}[rdoc-ref:security/command_injection.rdoc].
+ *
+ * Returns:
+ *
+ * - +true+ if the command exits with status zero.
+ * - +false+ if the exit status is a non-zero integer.
+ * - +nil+ if the command could not execute.
*
- * [<code>commandline</code>]
- * command line string which is passed to the standard shell
- * [<code>cmdname, arg1, ...</code>]
- * command name and one or more arguments (no shell)
- * [<code>[cmdname, argv0], arg1, ...</code>]
- * command name, <code>argv[0]</code> and zero or more arguments (no shell)
+ * Raises an exception (instead of returning +false+ or +nil+)
+ * if keyword argument +exception+ is set to +true+.
*
- * system returns +true+ if the command gives zero exit status,
- * +false+ for non zero exit status.
- * Returns +nil+ if command execution fails.
- * An error status is available in <code>$?</code>.
+ * Assigns the command's error status to <tt>$?</tt>.
*
- * If the <code>exception: true</code> argument is passed, the method
- * raises an exception instead of returning +false+ or +nil+.
+ * The new process is created using the
+ * {system system call}[https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/system.html];
+ * it may inherit some of its environment from the calling program
+ * (possibly including open file descriptors).
*
- * The arguments are processed in the same way as
- * for Kernel#spawn.
+ * Argument +env+, if given, is a hash that affects +ENV+ for the new process;
+ * see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
+ *
+ * Argument +options+ is a hash of options for the new process;
+ * see {Execution Options}[rdoc-ref:Process@Execution+Options].
*
- * The hash arguments, env and options, are same as #exec and #spawn.
- * See Kernel#spawn for details.
+ * The first required argument is one of the following:
*
- * system("echo *")
- * system("echo", "*")
+ * - +command_line+ if it is a string,
+ * and if it begins with a shell reserved word or special built-in,
+ * or if it contains one or more meta characters.
+ * - +exe_path+ otherwise.
*
- * <em>produces:</em>
+ * <b>Argument +command_line+</b>
*
- * config.h main.rb
- * *
+ * \String argument +command_line+ is a command line to be passed to a shell;
+ * it must begin with a shell reserved word, begin with a special built-in,
+ * or contain meta characters:
+ *
+ * system('if true; then echo "Foo"; fi') # => true # Shell reserved word.
+ * system('exit') # => true # Built-in.
+ * system('date > /tmp/date.tmp') # => true # Contains meta character.
+ * system('date > /nop/date.tmp') # => false
+ * system('date > /nop/date.tmp', exception: true) # Raises RuntimeError.
+ *
+ * Assigns the command's error status to <tt>$?</tt>:
+ *
+ * system('exit') # => true # Built-in.
+ * $? # => #<Process::Status: pid 640610 exit 0>
+ * system('date > /nop/date.tmp') # => false
+ * $? # => #<Process::Status: pid 640742 exit 2>
+ *
+ * The command line may also contain arguments and options for the command:
+ *
+ * system('echo "Foo"') # => true
+ *
+ * Output:
+ *
+ * Foo
+ *
+ * See {Execution Shell}[rdoc-ref:Process@Execution+Shell] for details about the shell.
+ *
+ * Raises an exception if the new process could not execute.
+ *
+ * <b>Argument +exe_path+</b>
+ *
+ * Argument +exe_path+ is one of the following:
+ *
+ * - The string path to an executable to be called.
+ * - A 2-element array containing the path to an executable
+ * and the string to be used as the name of the executing process.
+ *
+ * Example:
+ *
+ * system('/usr/bin/date') # => true # Path to date on Unix-style system.
+ * system('foo') # => nil # Command failed.
+ *
+ * Output:
*
- * Error handling:
+ * Mon Aug 28 11:43:10 AM CDT 2023
*
- * system("cat nonexistent.txt")
- * # => false
- * system("catt nonexistent.txt")
- * # => nil
+ * Assigns the command's error status to <tt>$?</tt>:
*
- * system("cat nonexistent.txt", exception: true)
- * # RuntimeError (Command failed with exit 1: cat)
- * system("catt nonexistent.txt", exception: true)
- * # Errno::ENOENT (No such file or directory - catt)
+ * system('/usr/bin/date') # => true
+ * $? # => #<Process::Status: pid 645605 exit 0>
+ * system('foo') # => nil
+ * $? # => #<Process::Status: pid 645608 exit 127>
*
- * See Kernel#exec for the standard shell.
+ * Ruby invokes the executable directly.
+ * This form does not use the shell;
+ * see {Arguments args}[rdoc-ref:Process@Arguments+args] for caveats.
+ *
+ * system('doesnt_exist') # => nil
+ *
+ * If one or more +args+ is given, each is an argument or option
+ * to be passed to the executable:
+ *
+ * system('echo', 'C*') # => true
+ * system('echo', 'hello', 'world') # => true
+ *
+ * Output:
+ *
+ * C*
+ * hello world
+ *
+ * Raises an exception if the new process could not execute.
*/
static VALUE
rb_f_system(int argc, VALUE *argv, VALUE _)
{
+ rb_thread_t *th = GET_THREAD();
VALUE execarg_obj = rb_execarg_new(argc, argv, TRUE, TRUE);
struct rb_execarg *eargp = rb_execarg_get(execarg_obj);
struct rb_process_status status = {0};
eargp->status = &status;
- rb_last_status_clear();
+ last_status_clear(th);
// This function can set the thread's last status.
// May be different from waitpid_state.pid on exec failure.
@@ -4748,11 +4762,10 @@ rb_f_system(int argc, VALUE *argv, VALUE _)
if (pid > 0) {
VALUE status = rb_process_status_wait(pid, 0);
- struct rb_process_status *data = RTYPEDDATA_DATA(status);
-
+ struct rb_process_status *data = rb_check_typeddata(status, &rb_process_status_type);
// Set the last status:
rb_obj_freeze(status);
- GET_THREAD()->last_status = status;
+ th->last_status = status;
if (data->status == EXIT_SUCCESS) {
return Qtrue;
@@ -4795,271 +4808,105 @@ rb_f_system(int argc, VALUE *argv, VALUE _)
/*
* call-seq:
- * spawn([env,] command... [,options]) -> pid
- * Process.spawn([env,] command... [,options]) -> pid
- *
- * spawn executes specified command and return its pid.
- *
- * pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
- * Process.wait pid
- *
- * pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
- * Process.wait pid
- *
- * This method is similar to Kernel#system but it doesn't wait for the command
- * to finish.
- *
- * The parent process should
- * use Process.wait to collect
- * the termination status of its child or
- * use Process.detach to register
- * disinterest in their status;
- * otherwise, the operating system may accumulate zombie processes.
- *
- * spawn has bunch of options to specify process attributes:
- *
- * env: hash
- * name => val : set the environment variable
- * name => nil : unset the environment variable
- *
- * the keys and the values except for +nil+ must be strings.
- * command...:
- * commandline : command line string which is passed to the standard shell
- * cmdname, arg1, ... : command name and one or more arguments (This form does not use the shell. See below for caveats.)
- * [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
- * options: hash
- * clearing environment variables:
- * :unsetenv_others => true : clear environment variables except specified by env
- * :unsetenv_others => false : don't clear (default)
- * process group:
- * :pgroup => true or 0 : make a new process group
- * :pgroup => pgid : join the specified process group
- * :pgroup => nil : don't change the process group (default)
- * create new process group: Windows only
- * :new_pgroup => true : the new process is the root process of a new process group
- * :new_pgroup => false : don't create a new process group (default)
- * resource limit: resourcename is core, cpu, data, etc. See Process.setrlimit.
- * :rlimit_resourcename => limit
- * :rlimit_resourcename => [cur_limit, max_limit]
- * umask:
- * :umask => int
- * redirection:
- * key:
- * FD : single file descriptor in child process
- * [FD, FD, ...] : multiple file descriptor in child process
- * value:
- * FD : redirect to the file descriptor in parent process
- * string : redirect to file with open(string, "r" or "w")
- * [string] : redirect to file with open(string, File::RDONLY)
- * [string, open_mode] : redirect to file with open(string, open_mode, 0644)
- * [string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
- * [:child, FD] : redirect to the redirected file descriptor
- * :close : close the file descriptor in child process
- * FD is one of follows
- * :in : the file descriptor 0 which is the standard input
- * :out : the file descriptor 1 which is the standard output
- * :err : the file descriptor 2 which is the standard error
- * integer : the file descriptor of specified the integer
- * io : the file descriptor specified as io.fileno
- * file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
- * :close_others => false : inherit
- * current directory:
- * :chdir => str
- *
- * The <code>cmdname, arg1, ...</code> form does not use the shell.
- * However, on different OSes, different things are provided as
- * built-in commands. An example of this is +'echo'+, which is a
- * built-in on Windows, but is a normal program on Linux and Mac OS X.
- * This means that <code>Process.spawn 'echo', '%Path%'</code> will
- * display the contents of the <tt>%Path%</tt> environment variable
- * on Windows, but <code>Process.spawn 'echo', '$PATH'</code> prints
- * the literal <tt>$PATH</tt>.
- *
- * If a hash is given as +env+, the environment is
- * updated by +env+ before <code>exec(2)</code> in the child process.
- * If a pair in +env+ has nil as the value, the variable is deleted.
- *
- * # set FOO as BAR and unset BAZ.
- * pid = spawn({"FOO"=>"BAR", "BAZ"=>nil}, command)
- *
- * If a hash is given as +options+,
- * it specifies
- * process group,
- * create new process group,
- * resource limit,
- * current directory,
- * umask and
- * redirects for the child process.
- * Also, it can be specified to clear environment variables.
- *
- * The <code>:unsetenv_others</code> key in +options+ specifies
- * to clear environment variables, other than specified by +env+.
- *
- * pid = spawn(command, :unsetenv_others=>true) # no environment variable
- * pid = spawn({"FOO"=>"BAR"}, command, :unsetenv_others=>true) # FOO only
- *
- * The <code>:pgroup</code> key in +options+ specifies a process group.
- * The corresponding value should be true, zero, a positive integer, or nil.
- * true and zero cause the process to be a process leader of a new process group.
- * A non-zero positive integer causes the process to join the provided process group.
- * The default value, nil, causes the process to remain in the same process group.
- *
- * pid = spawn(command, :pgroup=>true) # process leader
- * pid = spawn(command, :pgroup=>10) # belongs to the process group 10
- *
- * The <code>:new_pgroup</code> key in +options+ specifies to pass
- * +CREATE_NEW_PROCESS_GROUP+ flag to <code>CreateProcessW()</code> that is
- * Windows API. This option is only for Windows.
- * true means the new process is the root process of the new process group.
- * The new process has CTRL+C disabled. This flag is necessary for
- * <code>Process.kill(:SIGINT, pid)</code> on the subprocess.
- * :new_pgroup is false by default.
- *
- * pid = spawn(command, :new_pgroup=>true) # new process group
- * pid = spawn(command, :new_pgroup=>false) # same process group
- *
- * The <code>:rlimit_</code><em>foo</em> key specifies a resource limit.
- * <em>foo</em> should be one of resource types such as <code>core</code>.
- * The corresponding value should be an integer or an array which have one or
- * two integers: same as cur_limit and max_limit arguments for
- * Process.setrlimit.
- *
- * cur, max = Process.getrlimit(:CORE)
- * pid = spawn(command, :rlimit_core=>[0,max]) # disable core temporary.
- * pid = spawn(command, :rlimit_core=>max) # enable core dump
- * pid = spawn(command, :rlimit_core=>0) # never dump core.
- *
- * The <code>:umask</code> key in +options+ specifies the umask.
- *
- * pid = spawn(command, :umask=>077)
- *
- * The :in, :out, :err, an integer, an IO and an array key specifies a redirection.
- * The redirection maps a file descriptor in the child process.
- *
- * For example, stderr can be merged into stdout as follows:
- *
- * pid = spawn(command, :err=>:out)
- * pid = spawn(command, 2=>1)
- * pid = spawn(command, STDERR=>:out)
- * pid = spawn(command, STDERR=>STDOUT)
- *
- * The hash keys specifies a file descriptor in the child process
- * started by #spawn.
- * :err, 2 and STDERR specifies the standard error stream (stderr).
- *
- * The hash values specifies a file descriptor in the parent process
- * which invokes #spawn.
- * :out, 1 and STDOUT specifies the standard output stream (stdout).
- *
- * In the above example,
- * the standard output in the child process is not specified.
- * So it is inherited from the parent process.
- *
- * The standard input stream (stdin) can be specified by :in, 0 and STDIN.
- *
- * A filename can be specified as a hash value.
- *
- * pid = spawn(command, :in=>File::NULL) # read mode
- * pid = spawn(command, :out=>File::NULL) # write mode
- * pid = spawn(command, :err=>"log") # write mode
- * pid = spawn(command, [:out, :err]=>File::NULL) # write mode
- * pid = spawn(command, 3=>File::NULL) # read mode
+ * spawn([env, ] command_line, options = {}) -> pid
+ * spawn([env, ] exe_path, *args, options = {}) -> pid
*
- * For stdout and stderr (and combination of them),
- * it is opened in write mode.
- * Otherwise read mode is used.
+ * Creates a new child process by doing one of the following
+ * in that process:
+ *
+ * - Passing string +command_line+ to the shell.
+ * - Invoking the executable at +exe_path+.
+ *
+ * This method has potential security vulnerabilities if called with untrusted input;
+ * see {Command Injection}[rdoc-ref:security/command_injection.rdoc].
+ *
+ * Returns the process ID (pid) of the new process,
+ * without waiting for it to complete.
+ *
+ * To avoid zombie processes, the parent process should call either:
+ *
+ * - Process.wait, to collect the termination statuses of its children.
+ * - Process.detach, to register disinterest in their status.
+ *
+ * The new process is created using the
+ * {exec system call}[https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/execve.html];
+ * it may inherit some of its environment from the calling program
+ * (possibly including open file descriptors).
*
- * For specifying flags and permission of file creation explicitly,
- * an array is used instead.
+ * Argument +env+, if given, is a hash that affects +ENV+ for the new process;
+ * see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
*
- * pid = spawn(command, :in=>["file"]) # read mode is assumed
- * pid = spawn(command, :in=>["file", "r"])
- * pid = spawn(command, :out=>["log", "w"]) # 0644 assumed
- * pid = spawn(command, :out=>["log", "w", 0600])
- * pid = spawn(command, :out=>["log", File::WRONLY|File::EXCL|File::CREAT, 0600])
+ * Argument +options+ is a hash of options for the new process;
+ * see {Execution Options}[rdoc-ref:Process@Execution+Options].
*
- * The array specifies a filename, flags and permission.
- * The flags can be a string or an integer.
- * If the flags is omitted or nil, File::RDONLY is assumed.
- * The permission should be an integer.
- * If the permission is omitted or nil, 0644 is assumed.
+ * The first required argument is one of the following:
*
- * If an array of IOs and integers are specified as a hash key,
- * all the elements are redirected.
+ * - +command_line+ if it is a string,
+ * and if it begins with a shell reserved word or special built-in,
+ * or if it contains one or more meta characters.
+ * - +exe_path+ otherwise.
*
- * # stdout and stderr is redirected to log file.
- * # The file "log" is opened just once.
- * pid = spawn(command, [:out, :err]=>["log", "w"])
+ * <b>Argument +command_line+</b>
*
- * Another way to merge multiple file descriptors is [:child, fd].
- * \[:child, fd] means the file descriptor in the child process.
- * This is different from fd.
- * For example, :err=>:out means redirecting child stderr to parent stdout.
- * But :err=>[:child, :out] means redirecting child stderr to child stdout.
- * They differ if stdout is redirected in the child process as follows.
+ * \String argument +command_line+ is a command line to be passed to a shell;
+ * it must begin with a shell reserved word, begin with a special built-in,
+ * or contain meta characters:
*
- * # stdout and stderr is redirected to log file.
- * # The file "log" is opened just once.
- * pid = spawn(command, :out=>["log", "w"], :err=>[:child, :out])
+ * spawn('if true; then echo "Foo"; fi') # => 798847 # Shell reserved word.
+ * Process.wait # => 798847
+ * spawn('exit') # => 798848 # Built-in.
+ * Process.wait # => 798848
+ * spawn('date > /tmp/date.tmp') # => 798879 # Contains meta character.
+ * Process.wait # => 798849
+ * spawn('date > /nop/date.tmp') # => 798882 # Issues error message.
+ * Process.wait # => 798882
*
- * \[:child, :out] can be used to merge stderr into stdout in IO.popen.
- * In this case, IO.popen redirects stdout to a pipe in the child process
- * and [:child, :out] refers the redirected stdout.
+ * The command line may also contain arguments and options for the command:
*
- * io = IO.popen(["sh", "-c", "echo out; echo err >&2", :err=>[:child, :out]])
- * p io.read #=> "out\nerr\n"
+ * spawn('echo "Foo"') # => 799031
+ * Process.wait # => 799031
*
- * The <code>:chdir</code> key in +options+ specifies the current directory.
+ * Output:
*
- * pid = spawn(command, :chdir=>"/var/tmp")
+ * Foo
*
- * spawn closes all non-standard unspecified descriptors by default.
- * The "standard" descriptors are 0, 1 and 2.
- * This behavior is specified by :close_others option.
- * :close_others doesn't affect the standard descriptors which are
- * closed only if :close is specified explicitly.
+ * See {Execution Shell}[rdoc-ref:Process@Execution+Shell] for details about the shell.
*
- * pid = spawn(command, :close_others=>true) # close 3,4,5,... (default)
- * pid = spawn(command, :close_others=>false) # don't close 3,4,5,...
+ * Raises an exception if the new process could not execute.
*
- * :close_others is false by default for spawn and IO.popen.
+ * <b>Argument +exe_path+</b>
*
- * Note that fds which close-on-exec flag is already set are closed
- * regardless of :close_others option.
+ * Argument +exe_path+ is one of the following:
*
- * So IO.pipe and spawn can be used as IO.popen.
+ * - The string path to an executable to be called.
+ * - A 2-element array containing the path to an executable to be called,
+ * and the string to be used as the name of the executing process.
*
- * # similar to r = IO.popen(command)
- * r, w = IO.pipe
- * pid = spawn(command, :out=>w) # r, w is closed in the child process.
- * w.close
+ * spawn('/usr/bin/date') # Path to date on Unix-style system.
+ * Process.wait
*
- * :close is specified as a hash value to close a fd individually.
+ * Output:
*
- * f = open(foo)
- * system(command, f=>:close) # don't inherit f.
+ * Mon Aug 28 11:43:10 AM CDT 2023
*
- * If a file descriptor need to be inherited,
- * io=>io can be used.
+ * Ruby invokes the executable directly.
+ * This form does not use the shell;
+ * see {Arguments args}[rdoc-ref:Process@Arguments+args] for caveats.
*
- * # valgrind has --log-fd option for log destination.
- * # log_w=>log_w indicates log_w.fileno inherits to child process.
- * log_r, log_w = IO.pipe
- * pid = spawn("valgrind", "--log-fd=#{log_w.fileno}", "echo", "a", log_w=>log_w)
- * log_w.close
- * p log_r.read
+ * If one or more +args+ is given, each is an argument or option
+ * to be passed to the executable:
*
- * It is also possible to exchange file descriptors.
+ * spawn('echo', 'C*') # => 799392
+ * Process.wait # => 799392
+ * spawn('echo', 'hello', 'world') # => 799393
+ * Process.wait # => 799393
*
- * pid = spawn(command, :out=>:err, :err=>:out)
+ * Output:
*
- * The hash keys specify file descriptors in the child process.
- * The hash values specifies file descriptors in the parent process.
- * So the above specifies exchanging stdout and stderr.
- * Internally, +spawn+ uses an extra file descriptor to resolve such cyclic
- * file descriptor mapping.
+ * C*
+ * hello world
*
- * See Kernel.exec for the standard shell.
+ * Raises an exception if the new process could not execute.
*/
static VALUE
@@ -5091,22 +4938,18 @@ rb_f_spawn(int argc, VALUE *argv, VALUE _)
/*
* call-seq:
- * sleep([duration]) -> integer
- *
- * Suspends the current thread for _duration_ seconds (which may be any number,
- * including a +Float+ with fractional seconds). Returns the actual number of
- * seconds slept (rounded), which may be less than that asked for if another
- * thread calls Thread#run. Called without an argument, sleep()
- * will sleep forever.
- *
- * If the +duration+ is not supplied, or is +nil+, the thread sleeps forever.
- * Threads in this state may still be interrupted by other threads.
- *
- * Time.new #=> 2008-03-08 19:56:19 +0900
- * sleep 1.2 #=> 1
- * Time.new #=> 2008-03-08 19:56:20 +0900
- * sleep 1.9 #=> 2
- * Time.new #=> 2008-03-08 19:56:22 +0900
+ * sleep(secs = nil) -> slept_secs
+ *
+ * Suspends execution of the current thread for the number of seconds
+ * specified by numeric argument +secs+, or forever if +secs+ is +nil+;
+ * returns the integer number of seconds suspended (rounded).
+ *
+ * Time.new # => 2008-03-08 19:56:19 +0900
+ * sleep 1.2 # => 1
+ * Time.new # => 2008-03-08 19:56:20 +0900
+ * sleep 1.9 # => 2
+ * Time.new # => 2008-03-08 19:56:22 +0900
+ *
*/
static VALUE
@@ -5137,13 +4980,13 @@ rb_f_sleep(int argc, VALUE *argv, VALUE _)
#if (defined(HAVE_GETPGRP) && defined(GETPGRP_VOID)) || defined(HAVE_GETPGID)
/*
* call-seq:
- * Process.getpgrp -> integer
+ * Process.getpgrp -> integer
*
- * Returns the process group ID for this process. Not available on
- * all platforms.
+ * Returns the process group ID for the current process:
+ *
+ * Process.getpgid(0) # => 25527
+ * Process.getpgrp # => 25527
*
- * Process.getpgid(0) #=> 25527
- * Process.getpgrp #=> 25527
*/
static VALUE
@@ -5169,10 +5012,11 @@ proc_getpgrp(VALUE _)
#if defined(HAVE_SETPGID) || (defined(HAVE_SETPGRP) && defined(SETPGRP_VOID))
/*
* call-seq:
- * Process.setpgrp -> 0
+ * Process.setpgrp -> 0
+ *
+ * Equivalent to <tt>setpgid(0, 0)</tt>.
*
- * Equivalent to <code>setpgid(0,0)</code>. Not available on all
- * platforms.
+ * Not available on all platforms.
*/
static VALUE
@@ -5197,12 +5041,13 @@ proc_setpgrp(VALUE _)
#if defined(HAVE_GETPGID)
/*
* call-seq:
- * Process.getpgid(pid) -> integer
+ * Process.getpgid(pid) -> integer
*
- * Returns the process group ID for the given process id. Not
- * available on all platforms.
+ * Returns the process group ID for the given process ID +pid+:
+ *
+ * Process.getpgid(Process.ppid) # => 25527
*
- * Process.getpgid(Process.ppid()) #=> 25527
+ * Not available on all platforms.
*/
static VALUE
@@ -5222,10 +5067,12 @@ proc_getpgid(VALUE obj, VALUE pid)
#ifdef HAVE_SETPGID
/*
* call-seq:
- * Process.setpgid(pid, integer) -> 0
+ * Process.setpgid(pid, pgid) -> 0
*
- * Sets the process group ID of _pid_ (0 indicates this
- * process) to <em>integer</em>. Not available on all platforms.
+ * Sets the process group ID for the process given by process ID +pid+
+ * to +pgid+.
+ *
+ * Not available on all platforms.
*/
static VALUE
@@ -5247,15 +5094,16 @@ proc_setpgid(VALUE obj, VALUE pid, VALUE pgrp)
#ifdef HAVE_GETSID
/*
* call-seq:
- * Process.getsid() -> integer
- * Process.getsid(pid) -> integer
+ * Process.getsid(pid = nil) -> integer
*
- * Returns the session ID for the given process id. If not given,
- * return current process sid. Not available on all platforms.
+ * Returns the session ID of the given process ID +pid+,
+ * or of the current process if not given:
*
- * Process.getsid() #=> 27422
- * Process.getsid(0) #=> 27422
- * Process.getsid(Process.pid()) #=> 27422
+ * Process.getsid # => 27422
+ * Process.getsid(0) # => 27422
+ * Process.getsid(Process.pid()) # => 27422
+ *
+ * Not available on all platforms.
*/
static VALUE
proc_getsid(int argc, VALUE *argv, VALUE _)
@@ -5282,13 +5130,15 @@ static rb_pid_t ruby_setsid(void);
#endif
/*
* call-seq:
- * Process.setsid -> integer
+ * Process.setsid -> integer
*
- * Establishes this process as a new session and process group
- * leader, with no controlling tty. Returns the session id. Not
- * available on all platforms.
+ * Establishes the current process as a new session and process group leader,
+ * with no controlling tty;
+ * returns the session ID:
*
- * Process.setsid #=> 27422
+ * Process.setsid # => 27422
+ *
+ * Not available on all platforms.
*/
static VALUE
@@ -5307,7 +5157,7 @@ static rb_pid_t
ruby_setsid(void)
{
rb_pid_t pid;
- int ret;
+ int ret, fd;
pid = getpid();
#if defined(SETPGRP_VOID)
@@ -5336,19 +5186,26 @@ ruby_setsid(void)
#ifdef HAVE_GETPRIORITY
/*
* call-seq:
- * Process.getpriority(kind, integer) -> integer
- *
- * Gets the scheduling priority for specified process, process group,
- * or user. <em>kind</em> indicates the kind of entity to find: one
- * of Process::PRIO_PGRP,
- * Process::PRIO_USER, or
- * Process::PRIO_PROCESS. _integer_ is an id
- * indicating the particular process, process group, or user (an id
- * of 0 means _current_). Lower priorities are more favorable
- * for scheduling. Not available on all platforms.
- *
- * Process.getpriority(Process::PRIO_USER, 0) #=> 19
- * Process.getpriority(Process::PRIO_PROCESS, 0) #=> 19
+ * Process.getpriority(kind, id) -> integer
+ *
+ * Returns the scheduling priority for specified process, process group,
+ * or user.
+ *
+ * Argument +kind+ is one of:
+ *
+ * - Process::PRIO_PROCESS: return priority for process.
+ * - Process::PRIO_PGRP: return priority for process group.
+ * - Process::PRIO_USER: return priority for user.
+ *
+ * Argument +id+ is the ID for the process, process group, or user;
+ * zero specified the current ID for +kind+.
+ *
+ * Examples:
+ *
+ * Process.getpriority(Process::PRIO_USER, 0) # => 19
+ * Process.getpriority(Process::PRIO_PROCESS, 0) # => 19
+ *
+ * Not available on all platforms.
*/
static VALUE
@@ -5372,14 +5229,18 @@ proc_getpriority(VALUE obj, VALUE which, VALUE who)
#ifdef HAVE_GETPRIORITY
/*
* call-seq:
- * Process.setpriority(kind, integer, priority) -> 0
+ * Process.setpriority(kind, integer, priority) -> 0
*
* See Process.getpriority.
*
- * Process.setpriority(Process::PRIO_USER, 0, 19) #=> 0
- * Process.setpriority(Process::PRIO_PROCESS, 0, 19) #=> 0
- * Process.getpriority(Process::PRIO_USER, 0) #=> 19
- * Process.getpriority(Process::PRIO_PROCESS, 0) #=> 19
+ * Examples:
+ *
+ * Process.setpriority(Process::PRIO_USER, 0, 19) # => 0
+ * Process.setpriority(Process::PRIO_PROCESS, 0, 19) # => 0
+ * Process.getpriority(Process::PRIO_USER, 0) # => 19
+ * Process.getpriority(Process::PRIO_PROCESS, 0) # => 19
+ *
+ * Not available on all platforms.
*/
static VALUE
@@ -5627,22 +5488,24 @@ rlimit_resource_value(VALUE rval)
#if defined(HAVE_GETRLIMIT) && defined(RLIM2NUM)
/*
* call-seq:
- * Process.getrlimit(resource) -> [cur_limit, max_limit]
- *
- * Gets the resource limit of the process.
- * _cur_limit_ means current (soft) limit and
- * _max_limit_ means maximum (hard) limit.
- *
- * _resource_ indicates the kind of resource to limit.
- * It is specified as a symbol such as <code>:CORE</code>,
- * a string such as <code>"CORE"</code> or
- * a constant such as Process::RLIMIT_CORE.
- * See Process.setrlimit for details.
- *
- * _cur_limit_ and _max_limit_ may be Process::RLIM_INFINITY,
- * Process::RLIM_SAVED_MAX or
- * Process::RLIM_SAVED_CUR.
- * See Process.setrlimit and the system getrlimit(2) manual for details.
+ * Process.getrlimit(resource) -> [cur_limit, max_limit]
+ *
+ * Returns a 2-element array of the current (soft) limit
+ * and maximum (hard) limit for the given +resource+.
+ *
+ * Argument +resource+ specifies the resource whose limits are to be returned;
+ * see Process.setrlimit.
+ *
+ * Each of the returned values +cur_limit+ and +max_limit+ is an integer;
+ * see Process.setrlimit.
+ *
+ * Example:
+ *
+ * Process.getrlimit(:CORE) # => [0, 18446744073709551615]
+ *
+ * See Process.setrlimit.
+ *
+ * Not available on all platforms.
*/
static VALUE
@@ -5662,54 +5525,54 @@ proc_getrlimit(VALUE obj, VALUE resource)
#if defined(HAVE_SETRLIMIT) && defined(NUM2RLIM)
/*
* call-seq:
- * Process.setrlimit(resource, cur_limit, max_limit) -> nil
- * Process.setrlimit(resource, cur_limit) -> nil
- *
- * Sets the resource limit of the process.
- * _cur_limit_ means current (soft) limit and
- * _max_limit_ means maximum (hard) limit.
- *
- * If _max_limit_ is not given, _cur_limit_ is used.
- *
- * _resource_ indicates the kind of resource to limit.
- * It should be a symbol such as <code>:CORE</code>,
- * a string such as <code>"CORE"</code> or
- * a constant such as Process::RLIMIT_CORE.
- * The available resources are OS dependent.
- * Ruby may support following resources.
- *
- * [AS] total available memory (bytes) (SUSv3, NetBSD, FreeBSD, OpenBSD but 4.4BSD-Lite)
- * [CORE] core size (bytes) (SUSv3)
- * [CPU] CPU time (seconds) (SUSv3)
- * [DATA] data segment (bytes) (SUSv3)
- * [FSIZE] file size (bytes) (SUSv3)
- * [MEMLOCK] total size for mlock(2) (bytes) (4.4BSD, GNU/Linux)
- * [MSGQUEUE] allocation for POSIX message queues (bytes) (GNU/Linux)
- * [NICE] ceiling on process's nice(2) value (number) (GNU/Linux)
- * [NOFILE] file descriptors (number) (SUSv3)
- * [NPROC] number of processes for the user (number) (4.4BSD, GNU/Linux)
- * [NPTS] number of pseudo terminals (number) (FreeBSD)
- * [RSS] resident memory size (bytes) (4.2BSD, GNU/Linux)
- * [RTPRIO] ceiling on the process's real-time priority (number) (GNU/Linux)
- * [RTTIME] CPU time for real-time process (us) (GNU/Linux)
- * [SBSIZE] all socket buffers (bytes) (NetBSD, FreeBSD)
- * [SIGPENDING] number of queued signals allowed (signals) (GNU/Linux)
- * [STACK] stack size (bytes) (SUSv3)
- *
- * _cur_limit_ and _max_limit_ may be
- * <code>:INFINITY</code>, <code>"INFINITY"</code> or
- * Process::RLIM_INFINITY,
- * which means that the resource is not limited.
- * They may be Process::RLIM_SAVED_MAX,
- * Process::RLIM_SAVED_CUR and
- * corresponding symbols and strings too.
- * See system setrlimit(2) manual for details.
- *
- * The following example raises the soft limit of core size to
- * the hard limit to try to make core dump possible.
+ * Process.setrlimit(resource, cur_limit, max_limit = cur_limit) -> nil
+ *
+ * Sets limits for the current process for the given +resource+
+ * to +cur_limit+ (soft limit) and +max_limit+ (hard limit);
+ * returns +nil+.
+ *
+ * Argument +resource+ specifies the resource whose limits are to be set;
+ * the argument may be given as a symbol, as a string, or as a constant
+ * beginning with <tt>Process::RLIMIT_</tt>
+ * (e.g., +:CORE+, <tt>'CORE'</tt>, or <tt>Process::RLIMIT_CORE</tt>.
+ *
+ * The resources available and supported are system-dependent,
+ * and may include (here expressed as symbols):
+ *
+ * - +:AS+: Total available memory (bytes) (SUSv3, NetBSD, FreeBSD, OpenBSD except 4.4BSD-Lite).
+ * - +:CORE+: Core size (bytes) (SUSv3).
+ * - +:CPU+: CPU time (seconds) (SUSv3).
+ * - +:DATA+: Data segment (bytes) (SUSv3).
+ * - +:FSIZE+: File size (bytes) (SUSv3).
+ * - +:MEMLOCK+: Total size for mlock(2) (bytes) (4.4BSD, GNU/Linux).
+ * - +:MSGQUEUE+: Allocation for POSIX message queues (bytes) (GNU/Linux).
+ * - +:NICE+: Ceiling on process's nice(2) value (number) (GNU/Linux).
+ * - +:NOFILE+: File descriptors (number) (SUSv3).
+ * - +:NPROC+: Number of processes for the user (number) (4.4BSD, GNU/Linux).
+ * - +:NPTS+: Number of pseudo terminals (number) (FreeBSD).
+ * - +:RSS+: Resident memory size (bytes) (4.2BSD, GNU/Linux).
+ * - +:RTPRIO+: Ceiling on the process's real-time priority (number) (GNU/Linux).
+ * - +:RTTIME+: CPU time for real-time process (us) (GNU/Linux).
+ * - +:SBSIZE+: All socket buffers (bytes) (NetBSD, FreeBSD).
+ * - +:SIGPENDING+: Number of queued signals allowed (signals) (GNU/Linux).
+ * - +:STACK+: Stack size (bytes) (SUSv3).
+ *
+ * Arguments +cur_limit+ and +max_limit+ may be:
+ *
+ * - Integers (+max_limit+ should not be smaller than +cur_limit+).
+ * - Symbol +:SAVED_MAX+, string <tt>'SAVED_MAX'</tt>,
+ * or constant <tt>Process::RLIM_SAVED_MAX</tt>: saved maximum limit.
+ * - Symbol +:SAVED_CUR+, string <tt>'SAVED_CUR'</tt>,
+ * or constant <tt>Process::RLIM_SAVED_CUR</tt>: saved current limit.
+ * - Symbol +:INFINITY+, string <tt>'INFINITY'</tt>,
+ * or constant <tt>Process::RLIM_INFINITY</tt>: no limit on resource.
+ *
+ * This example raises the soft limit of core size to
+ * the hard limit to try to make core dump possible:
*
* Process.setrlimit(:CORE, Process.getrlimit(:CORE)[1])
*
+ * Not available on all platforms.
*/
static VALUE
@@ -5756,6 +5619,12 @@ check_gid_switch(void)
#if defined(HAVE_PWD_H)
+static inline bool
+login_not_found(int err)
+{
+ return (err == ENOTTY || err == ENXIO || err == ENOENT);
+}
+
/**
* Best-effort attempt to obtain the name of the login user, if any,
* associated with the process. Processes not descended from login(1) (or
@@ -5764,18 +5633,18 @@ check_gid_switch(void)
VALUE
rb_getlogin(void)
{
-#if ( !defined(USE_GETLOGIN_R) && !defined(USE_GETLOGIN) )
+# if !defined(USE_GETLOGIN_R) && !defined(USE_GETLOGIN)
return Qnil;
-#else
+# else
char MAYBE_UNUSED(*login) = NULL;
# ifdef USE_GETLOGIN_R
-#if defined(__FreeBSD__)
+# if defined(__FreeBSD__)
typedef int getlogin_r_size_t;
-#else
+# else
typedef size_t getlogin_r_size_t;
-#endif
+# endif
long loginsize = GETLOGIN_R_SIZE_INIT; /* maybe -1 */
@@ -5789,10 +5658,8 @@ rb_getlogin(void)
rb_str_set_len(maybe_result, loginsize);
int gle;
- errno = 0;
while ((gle = getlogin_r(login, (getlogin_r_size_t)loginsize)) != 0) {
-
- if (gle == ENOTTY || gle == ENXIO || gle == ENOENT) {
+ if (login_not_found(gle)) {
rb_str_resize(maybe_result, 0);
return Qnil;
}
@@ -5812,17 +5679,19 @@ rb_getlogin(void)
return Qnil;
}
+ rb_str_set_len(maybe_result, strlen(login));
return maybe_result;
-# elif USE_GETLOGIN
+# elif defined(USE_GETLOGIN)
errno = 0;
login = getlogin();
- if (errno) {
- if (errno == ENOTTY || errno == ENXIO || errno == ENOENT) {
+ int err = errno;
+ if (err) {
+ if (login_not_found(err)) {
return Qnil;
}
- rb_syserr_fail(errno, "getlogin");
+ rb_syserr_fail(err, "getlogin");
}
return login ? rb_str_new_cstr(login) : Qnil;
@@ -5831,10 +5700,46 @@ rb_getlogin(void)
#endif
}
+/* avoid treating as errors errno values that indicate "not found" */
+static inline bool
+pwd_not_found(int err)
+{
+ switch (err) {
+ case 0:
+ case ENOENT:
+ case ESRCH:
+ case EBADF:
+ case EPERM:
+ return true;
+ default:
+ return false;
+ }
+}
+
+# if defined(USE_GETPWNAM_R)
+struct getpwnam_r_args {
+ const char *login;
+ char *buf;
+ size_t bufsize;
+ struct passwd *result;
+ struct passwd pwstore;
+};
+
+# define GETPWNAM_R_ARGS(login_, buf_, bufsize_) (struct getpwnam_r_args) \
+ {.login = login_, .buf = buf_, .bufsize = bufsize_, .result = NULL}
+
+static void *
+nogvl_getpwnam_r(void *args)
+{
+ struct getpwnam_r_args *arg = args;
+ return (void *)(VALUE)getpwnam_r(arg->login, &arg->pwstore, arg->buf, arg->bufsize, &arg->result);
+}
+# endif
+
VALUE
rb_getpwdirnam_for_login(VALUE login_name)
{
-#if ( !defined(USE_GETPWNAM_R) && !defined(USE_GETPWNAM) )
+#if !defined(USE_GETPWNAM_R) && !defined(USE_GETPWNAM)
return Qnil;
#else
@@ -5843,13 +5748,11 @@ rb_getpwdirnam_for_login(VALUE login_name)
return Qnil;
}
- char *login = RSTRING_PTR(login_name);
+ const char *login = RSTRING_PTR(login_name);
- struct passwd *pwptr;
# ifdef USE_GETPWNAM_R
- struct passwd pwdnm;
char *bufnm;
long bufsizenm = GETPW_R_SIZE_INIT; /* maybe -1 */
@@ -5861,58 +5764,77 @@ rb_getpwdirnam_for_login(VALUE login_name)
bufnm = RSTRING_PTR(getpwnm_tmp);
bufsizenm = rb_str_capacity(getpwnm_tmp);
rb_str_set_len(getpwnm_tmp, bufsizenm);
+ struct getpwnam_r_args args = GETPWNAM_R_ARGS(login, bufnm, (size_t)bufsizenm);
int enm;
- errno = 0;
- while ((enm = getpwnam_r(login, &pwdnm, bufnm, bufsizenm, &pwptr)) != 0) {
-
- if (enm == ENOENT || enm== ESRCH || enm == EBADF || enm == EPERM) {
- /* not found; non-errors */
+ while ((enm = IO_WITHOUT_GVL_INT(nogvl_getpwnam_r, &args)) != 0) {
+ if (pwd_not_found(enm)) {
rb_str_resize(getpwnm_tmp, 0);
return Qnil;
}
- if (enm != ERANGE || bufsizenm >= GETPW_R_SIZE_LIMIT) {
+ if (enm != ERANGE || args.bufsize >= GETPW_R_SIZE_LIMIT) {
rb_str_resize(getpwnm_tmp, 0);
rb_syserr_fail(enm, "getpwnam_r");
}
- rb_str_modify_expand(getpwnm_tmp, bufsizenm);
- bufnm = RSTRING_PTR(getpwnm_tmp);
- bufsizenm = rb_str_capacity(getpwnm_tmp);
+ rb_str_modify_expand(getpwnm_tmp, (long)args.bufsize);
+ args.buf = RSTRING_PTR(getpwnm_tmp);
+ args.bufsize = (size_t)rb_str_capacity(getpwnm_tmp);
}
- if (pwptr == NULL) {
+ if (args.result == NULL) {
/* no record in the password database for the login name */
rb_str_resize(getpwnm_tmp, 0);
return Qnil;
}
/* found it */
- VALUE result = rb_str_new_cstr(pwptr->pw_dir);
+ VALUE result = rb_str_new_cstr(args.result->pw_dir);
rb_str_resize(getpwnm_tmp, 0);
return result;
-# elif USE_GETPWNAM
+# elif defined(USE_GETPWNAM)
+ struct passwd *pwptr;
errno = 0;
- pwptr = getpwnam(login);
- if (pwptr) {
- /* found it */
- return rb_str_new_cstr(pwptr->pw_dir);
- }
- if (errno
- /* avoid treating as errors errno values that indicate "not found" */
- && ( errno != ENOENT && errno != ESRCH && errno != EBADF && errno != EPERM)) {
- rb_syserr_fail(errno, "getpwnam");
+ if (!(pwptr = getpwnam(login))) {
+ int err = errno;
+
+ if (pwd_not_found(err)) {
+ return Qnil;
+ }
+
+ rb_syserr_fail(err, "getpwnam");
}
- return Qnil; /* not found */
+ /* found it */
+ return rb_str_new_cstr(pwptr->pw_dir);
# endif
#endif
}
+# if defined(USE_GETPWUID_R)
+struct getpwuid_r_args {
+ uid_t uid;
+ char *buf;
+ size_t bufsize;
+ struct passwd *result;
+ struct passwd pwstore;
+};
+
+# define GETPWUID_R_ARGS(uid_, buf_, bufsize_) (struct getpwuid_r_args) \
+ {.uid = uid_, .buf = buf_, .bufsize = bufsize_, .result = NULL}
+
+static void *
+nogvl_getpwuid_r(void *args)
+{
+ struct getpwuid_r_args *arg = args;
+ return (void *)(VALUE)getpwuid_r(arg->uid, &arg->pwstore, arg->buf, arg->bufsize, &arg->result);
+}
+# endif
+
/**
* Look up the user's dflt home dir in the password db, by uid.
*/
@@ -5925,11 +5847,8 @@ rb_getpwdiruid(void)
# else
uid_t ruid = getuid();
- struct passwd *pwptr;
-
# ifdef USE_GETPWUID_R
- struct passwd pwdid;
char *bufid;
long bufsizeid = GETPW_R_SIZE_INIT; /* maybe -1 */
@@ -5941,53 +5860,52 @@ rb_getpwdiruid(void)
bufid = RSTRING_PTR(getpwid_tmp);
bufsizeid = rb_str_capacity(getpwid_tmp);
rb_str_set_len(getpwid_tmp, bufsizeid);
+ struct getpwuid_r_args args = GETPWUID_R_ARGS(ruid, bufid, (size_t)bufsizeid);
int eid;
- errno = 0;
- while ((eid = getpwuid_r(ruid, &pwdid, bufid, bufsizeid, &pwptr)) != 0) {
-
- if (eid == ENOENT || eid== ESRCH || eid == EBADF || eid == EPERM) {
- /* not found; non-errors */
+ while ((eid = IO_WITHOUT_GVL_INT(nogvl_getpwuid_r, &args)) != 0) {
+ if (pwd_not_found(eid)) {
rb_str_resize(getpwid_tmp, 0);
return Qnil;
}
- if (eid != ERANGE || bufsizeid >= GETPW_R_SIZE_LIMIT) {
+ if (eid != ERANGE || args.bufsize >= GETPW_R_SIZE_LIMIT) {
rb_str_resize(getpwid_tmp, 0);
rb_syserr_fail(eid, "getpwuid_r");
}
- rb_str_modify_expand(getpwid_tmp, bufsizeid);
- bufid = RSTRING_PTR(getpwid_tmp);
- bufsizeid = rb_str_capacity(getpwid_tmp);
+ rb_str_modify_expand(getpwid_tmp, (long)args.bufsize);
+ args.buf = RSTRING_PTR(getpwid_tmp);
+ args.bufsize = (size_t)rb_str_capacity(getpwid_tmp);
}
- if (pwptr == NULL) {
+ if (args.result == NULL) {
/* no record in the password database for the uid */
rb_str_resize(getpwid_tmp, 0);
return Qnil;
}
/* found it */
- VALUE result = rb_str_new_cstr(pwptr->pw_dir);
+ VALUE result = rb_str_new_cstr(args.result->pw_dir);
rb_str_resize(getpwid_tmp, 0);
return result;
# elif defined(USE_GETPWUID)
+ struct passwd *pwptr;
errno = 0;
- pwptr = getpwuid(ruid);
- if (pwptr) {
- /* found it */
- return rb_str_new_cstr(pwptr->pw_dir);
- }
- if (errno
- /* avoid treating as errors errno values that indicate "not found" */
- && ( errno == ENOENT || errno == ESRCH || errno == EBADF || errno == EPERM)) {
- rb_syserr_fail(errno, "getpwuid");
+ if (!(pwptr = getpwuid(ruid))) {
+ int err = errno;
+
+ if (pwd_not_found(err)) {
+ return Qnil;
+ }
+
+ rb_syserr_fail(err, "getpwuid");
}
- return Qnil; /* not found */
+ /* found it */
+ return rb_str_new_cstr(pwptr->pw_dir);
# endif
#endif /* !defined(USE_GETPWUID_R) && !defined(USE_GETPWUID) */
@@ -6001,7 +5919,7 @@ rb_getpwdiruid(void)
* The Process::Sys module contains UID and GID
* functions which provide direct bindings to the system calls of the
* same names instead of the more-portable versions of the same
- * functionality found in the Process,
+ * functionality found in the +Process+,
* Process::UID, and Process::GID modules.
*/
@@ -6023,7 +5941,6 @@ obj2uid(VALUE id
const char *usrname = StringValueCStr(id);
struct passwd *pwptr;
#ifdef USE_GETPWNAM_R
- struct passwd pwbuf;
char *getpw_buf;
long getpw_buf_len;
int e;
@@ -6036,15 +5953,18 @@ obj2uid(VALUE id
getpw_buf_len = rb_str_capacity(*getpw_tmp);
rb_str_set_len(*getpw_tmp, getpw_buf_len);
errno = 0;
- while ((e = getpwnam_r(usrname, &pwbuf, getpw_buf, getpw_buf_len, &pwptr)) != 0) {
- if (e != ERANGE || getpw_buf_len >= GETPW_R_SIZE_LIMIT) {
+ struct getpwnam_r_args args = GETPWNAM_R_ARGS((char *)usrname, getpw_buf, (size_t)getpw_buf_len);
+
+ while ((e = IO_WITHOUT_GVL_INT(nogvl_getpwnam_r, &args)) != 0) {
+ if (e != ERANGE || args.bufsize >= GETPW_R_SIZE_LIMIT) {
rb_str_resize(*getpw_tmp, 0);
rb_syserr_fail(e, "getpwnam_r");
}
- rb_str_modify_expand(*getpw_tmp, getpw_buf_len);
- getpw_buf = RSTRING_PTR(*getpw_tmp);
- getpw_buf_len = rb_str_capacity(*getpw_tmp);
+ rb_str_modify_expand(*getpw_tmp, (long)args.bufsize);
+ args.buf = RSTRING_PTR(*getpw_tmp);
+ args.bufsize = (size_t)rb_str_capacity(*getpw_tmp);
}
+ pwptr = args.result;
#else
pwptr = getpwnam(usrname);
#endif
@@ -6083,6 +6003,26 @@ p_uid_from_name(VALUE self, VALUE id)
#endif
#if defined(HAVE_GRP_H)
+# if defined(USE_GETGRNAM_R)
+struct getgrnam_r_args {
+ const char *name;
+ char *buf;
+ size_t bufsize;
+ struct group *result;
+ struct group grp;
+};
+
+# define GETGRNAM_R_ARGS(name_, buf_, bufsize_) (struct getgrnam_r_args) \
+ {.name = name_, .buf = buf_, .bufsize = bufsize_, .result = NULL}
+
+static void *
+nogvl_getgrnam_r(void *args)
+{
+ struct getgrnam_r_args *arg = args;
+ return (void *)(VALUE)getgrnam_r(arg->name, &arg->grp, arg->buf, arg->bufsize, &arg->result);
+}
+# endif
+
static rb_gid_t
obj2gid(VALUE id
# ifdef USE_GETGRNAM_R
@@ -6100,7 +6040,6 @@ obj2gid(VALUE id
const char *grpname = StringValueCStr(id);
struct group *grptr;
#ifdef USE_GETGRNAM_R
- struct group grbuf;
char *getgr_buf;
long getgr_buf_len;
int e;
@@ -6113,15 +6052,18 @@ obj2gid(VALUE id
getgr_buf_len = rb_str_capacity(*getgr_tmp);
rb_str_set_len(*getgr_tmp, getgr_buf_len);
errno = 0;
- while ((e = getgrnam_r(grpname, &grbuf, getgr_buf, getgr_buf_len, &grptr)) != 0) {
- if (e != ERANGE || getgr_buf_len >= GETGR_R_SIZE_LIMIT) {
+ struct getgrnam_r_args args = GETGRNAM_R_ARGS(grpname, getgr_buf, (size_t)getgr_buf_len);
+
+ while ((e = IO_WITHOUT_GVL_INT(nogvl_getgrnam_r, &args)) != 0) {
+ if (e != ERANGE || args.bufsize >= GETGR_R_SIZE_LIMIT) {
rb_str_resize(*getgr_tmp, 0);
rb_syserr_fail(e, "getgrnam_r");
}
- rb_str_modify_expand(*getgr_tmp, getgr_buf_len);
- getgr_buf = RSTRING_PTR(*getgr_tmp);
- getgr_buf_len = rb_str_capacity(*getgr_tmp);
+ rb_str_modify_expand(*getgr_tmp, (long)args.bufsize);
+ args.buf = RSTRING_PTR(*getgr_tmp);
+ args.bufsize = (size_t)rb_str_capacity(*getgr_tmp);
}
+ grptr = args.result;
#elif defined(HAVE_GETGRNAM)
grptr = getgrnam(grpname);
#else
@@ -6288,13 +6230,14 @@ p_sys_setresuid(VALUE obj, VALUE rid, VALUE eid, VALUE sid)
/*
* call-seq:
- * Process.uid -> integer
- * Process::UID.rid -> integer
- * Process::Sys.getuid -> integer
+ * Process.uid -> integer
+ * Process::UID.rid -> integer
+ * Process::Sys.getuid -> integer
*
- * Returns the (real) user ID of this process.
+ * Returns the (real) user ID of the current process.
+ *
+ * Process.uid # => 1000
*
- * Process.uid #=> 501
*/
static VALUE
@@ -6308,10 +6251,13 @@ proc_getuid(VALUE obj)
#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRUID) || defined(HAVE_SETUID)
/*
* call-seq:
- * Process.uid= user -> numeric
+ * Process.uid = new_uid -> new_uid
+ *
+ * Sets the (user) user ID for the current process to +new_uid+:
+ *
+ * Process.uid = 1000 # => 1000
*
- * Sets the (user) user ID for this process. Not available on all
- * platforms.
+ * Not available on all platforms.
*/
static VALUE
@@ -6686,13 +6632,14 @@ p_sys_issetugid(VALUE obj)
/*
* call-seq:
- * Process.gid -> integer
- * Process::GID.rid -> integer
- * Process::Sys.getgid -> integer
+ * Process.gid -> integer
+ * Process::GID.rid -> integer
+ * Process::Sys.getgid -> integer
+ *
+ * Returns the (real) group ID for the current process:
*
- * Returns the (real) group ID for this process.
+ * Process.gid # => 1000
*
- * Process.gid #=> 500
*/
static VALUE
@@ -6706,9 +6653,12 @@ proc_getgid(VALUE obj)
#if defined(HAVE_SETRESGID) || defined(HAVE_SETREGID) || defined(HAVE_SETRGID) || defined(HAVE_SETGID)
/*
* call-seq:
- * Process.gid= integer -> integer
+ * Process.gid = new_gid -> new_gid
+ *
+ * Sets the group ID for the current process to +new_gid+:
+ *
+ * Process.gid = 1000 # => 1000
*
- * Sets the group ID for this process.
*/
static VALUE
@@ -6792,26 +6742,23 @@ maxgroups(void)
#ifdef HAVE_GETGROUPS
/*
* call-seq:
- * Process.groups -> array
+ * Process.groups -> array
*
- * Get an Array of the group IDs in the
- * supplemental group access list for this process.
+ * Returns an array of the group IDs
+ * in the supplemental group access list for the current process:
*
- * Process.groups #=> [27, 6, 10, 11]
+ * Process.groups # => [4, 24, 27, 30, 46, 122, 135, 136, 1000]
*
- * Note that this method is just a wrapper of getgroups(2).
- * This means that the following characteristics of
- * the result completely depend on your system:
+ * These properties of the returned array are system-dependent:
*
- * - the result is sorted
- * - the result includes effective GIDs
- * - the result does not include duplicated GIDs
- * - the result size does not exceed the value of Process.maxgroups
+ * - Whether (and how) the array is sorted.
+ * - Whether the array includes effective group IDs.
+ * - Whether the array includes duplicate group IDs.
+ * - Whether the array size exceeds the value of Process.maxgroups.
*
- * You can make sure to get a sorted unique GID list of
- * the current process by this expression:
+ * Use this call to get a sorted and unique array:
*
- * Process.groups.uniq.sort
+ * Process.groups.uniq.sort
*
*/
@@ -6848,14 +6795,14 @@ proc_getgroups(VALUE obj)
#ifdef HAVE_SETGROUPS
/*
* call-seq:
- * Process.groups= array -> array
+ * Process.groups = new_groups -> new_groups
*
- * Set the supplemental group access list to the given
- * Array of group IDs.
+ * Sets the supplemental group access list to the given
+ * array of group IDs.
*
- * Process.groups #=> [0, 1, 2, 3, 4, 6, 10, 11, 20, 26, 27]
- * Process.groups = [27, 6, 10, 11] #=> [27, 6, 10, 11]
- * Process.groups #=> [27, 6, 10, 11]
+ * Process.groups # => [0, 1, 2, 3, 4, 6, 10, 11, 20, 26, 27]
+ * Process.groups = [27, 6, 10, 11] # => [27, 6, 10, 11]
+ * Process.groups # => [27, 6, 10, 11]
*
*/
@@ -6897,19 +6844,21 @@ proc_setgroups(VALUE obj, VALUE ary)
#ifdef HAVE_INITGROUPS
/*
* call-seq:
- * Process.initgroups(username, gid) -> array
+ * Process.initgroups(username, gid) -> array
*
- * Initializes the supplemental group access list by reading the
- * system group database and using all groups of which the given user
- * is a member. The group with the specified _gid_ is also added to
- * the list. Returns the resulting Array of the GIDs of all the
- * groups in the supplementary group access list. Not available on
- * all platforms.
+ * Sets the supplemental group access list;
+ * the new list includes:
+ *
+ * - The group IDs of those groups to which the user given by +username+ belongs.
+ * - The group ID +gid+.
+ *
+ * Example:
*
- * Process.groups #=> [0, 1, 2, 3, 4, 6, 10, 11, 20, 26, 27]
- * Process.initgroups( "mgranger", 30 ) #=> [30, 6, 10, 11]
- * Process.groups #=> [30, 6, 10, 11]
+ * Process.groups # => [0, 1, 2, 3, 4, 6, 10, 11, 20, 26, 27]
+ * Process.initgroups('me', 30) # => [30, 6, 10, 11]
+ * Process.groups # => [30, 6, 10, 11]
*
+ * Not available on all platforms.
*/
static VALUE
@@ -6927,12 +6876,13 @@ proc_initgroups(VALUE obj, VALUE uname, VALUE base_grp)
#if defined(_SC_NGROUPS_MAX) || defined(NGROUPS_MAX)
/*
* call-seq:
- * Process.maxgroups -> integer
+ * Process.maxgroups -> integer
+ *
+ * Returns the maximum number of group IDs allowed
+ * in the supplemental group access list:
*
- * Returns the maximum number of GIDs allowed in the supplemental
- * group access list.
+ * Process.maxgroups # => 32
*
- * Process.maxgroups #=> 32
*/
static VALUE
@@ -6947,10 +6897,10 @@ proc_getmaxgroups(VALUE obj)
#ifdef HAVE_SETGROUPS
/*
* call-seq:
- * Process.maxgroups= integer -> integer
+ * Process.maxgroups = new_max -> new_max
*
- * Sets the maximum number of GIDs allowed in the supplemental group
- * access list.
+ * Sets the maximum number of group IDs allowed
+ * in the supplemental group access list.
*/
static VALUE
@@ -6981,15 +6931,22 @@ static int rb_daemon(int nochdir, int noclose);
/*
* call-seq:
- * Process.daemon() -> 0
- * Process.daemon(nochdir=nil,noclose=nil) -> 0
- *
- * Detach the process from controlling terminal and run in the
- * background as system daemon. Unless the argument _nochdir_ is
- * +true+, it changes the current working directory to the root
- * ("/"). Unless the argument _noclose_ is +true+, daemon() will
- * redirect standard input, standard output and standard error to
- * null device. Return zero on success, or raise one of Errno::*.
+ * Process.daemon(nochdir = nil, noclose = nil) -> 0
+ *
+ * Detaches the current process from its controlling terminal
+ * and runs it in the background as system daemon;
+ * returns zero.
+ *
+ * By default:
+ *
+ * - Changes the current working directory to the root directory.
+ * - Redirects $stdin, $stdout, and $stderr to the null device.
+ *
+ * If optional argument +nochdir+ is +true+,
+ * does not change the current working directory.
+ *
+ * If optional argument +noclose+ is +true+,
+ * does not redirect $stdin, $stdout, or $stderr.
*/
static VALUE
@@ -7246,13 +7203,14 @@ p_gid_change_privilege(VALUE obj, VALUE id)
/*
* call-seq:
- * Process.euid -> integer
- * Process::UID.eid -> integer
- * Process::Sys.geteuid -> integer
+ * Process.euid -> integer
+ * Process::UID.eid -> integer
+ * Process::Sys.geteuid -> integer
*
- * Returns the effective user ID for this process.
+ * Returns the effective user ID for the current process.
+ *
+ * Process.euid # => 501
*
- * Process.euid #=> 501
*/
static VALUE
@@ -7288,10 +7246,11 @@ proc_seteuid(rb_uid_t uid)
#if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID) || defined(HAVE_SETUID)
/*
* call-seq:
- * Process.euid= user
+ * Process.euid = new_euid -> new_euid
+ *
+ * Sets the effective user ID for the current process.
*
- * Sets the effective user ID for this process. Not available on all
- * platforms.
+ * Not available on all platforms.
*/
static VALUE
@@ -7369,14 +7328,15 @@ p_uid_grant_privilege(VALUE obj, VALUE id)
/*
* call-seq:
- * Process.egid -> integer
- * Process::GID.eid -> integer
- * Process::Sys.geteid -> integer
+ * Process.egid -> integer
+ * Process::GID.eid -> integer
+ * Process::Sys.geteid -> integer
+ *
+ * Returns the effective group ID for the current process:
*
- * Returns the effective group ID for this process. Not available on
- * all platforms.
+ * Process.egid # => 500
*
- * Process.egid #=> 500
+ * Not available on all platforms.
*/
static VALUE
@@ -7390,10 +7350,11 @@ proc_getegid(VALUE obj)
#if defined(HAVE_SETRESGID) || defined(HAVE_SETREGID) || defined(HAVE_SETEGID) || defined(HAVE_SETGID) || defined(_POSIX_SAVED_IDS)
/*
* call-seq:
- * Process.egid = integer -> integer
+ * Process.egid = new_egid -> new_egid
+ *
+ * Sets the effective group ID for the current process.
*
- * Sets the effective group ID for this process. Not available on all
- * platforms.
+ * Not available on all platforms.
*/
static VALUE
@@ -7866,14 +7827,15 @@ get_clk_tck(void)
/*
* call-seq:
- * Process.times -> aProcessTms
+ * Process.times -> process_tms
*
- * Returns a <code>Tms</code> structure (see Process::Tms)
- * that contains user and system CPU times for this process,
- * and also for children processes.
+ * Returns a Process::Tms structure that contains user and system CPU times
+ * for the current process, and for its children processes:
*
- * t = Process.times
- * [ t.utime, t.stime, t.cutime, t.cstime ] #=> [0.0, 0.02, 0.00, 0.00]
+ * Process.times
+ * # => #<struct Process::Tms utime=55.122118, stime=35.533068, cutime=0.0, cstime=0.002846>
+ *
+ * The precision is platform-defined.
*/
VALUE
@@ -8135,130 +8097,167 @@ ruby_real_ms_time(void)
/*
* call-seq:
- * Process.clock_gettime(clock_id [, unit]) -> number
- *
- * Returns a time returned by POSIX clock_gettime() function.
- *
- * p Process.clock_gettime(Process::CLOCK_MONOTONIC)
- * #=> 896053.968060096
- *
- * +clock_id+ specifies a kind of clock.
- * It is specified as a constant which begins with <code>Process::CLOCK_</code>
- * such as Process::CLOCK_REALTIME and Process::CLOCK_MONOTONIC.
- *
- * The supported constants depends on OS and version.
- * Ruby provides following types of +clock_id+ if available.
- *
- * [CLOCK_REALTIME] SUSv2 to 4, Linux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 2.1, macOS 10.12, Windows-8/Server-2012
- * [CLOCK_MONOTONIC] SUSv3 to 4, Linux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 3.4, macOS 10.12, Windows-2000
- * [CLOCK_PROCESS_CPUTIME_ID] SUSv3 to 4, Linux 2.5.63, FreeBSD 9.3, OpenBSD 5.4, macOS 10.12
- * [CLOCK_THREAD_CPUTIME_ID] SUSv3 to 4, Linux 2.5.63, FreeBSD 7.1, OpenBSD 5.4, macOS 10.12
- * [CLOCK_VIRTUAL] FreeBSD 3.0, OpenBSD 2.1
- * [CLOCK_PROF] FreeBSD 3.0, OpenBSD 2.1
- * [CLOCK_REALTIME_FAST] FreeBSD 8.1
- * [CLOCK_REALTIME_PRECISE] FreeBSD 8.1
- * [CLOCK_REALTIME_COARSE] Linux 2.6.32
- * [CLOCK_REALTIME_ALARM] Linux 3.0
- * [CLOCK_MONOTONIC_FAST] FreeBSD 8.1
- * [CLOCK_MONOTONIC_PRECISE] FreeBSD 8.1
- * [CLOCK_MONOTONIC_COARSE] Linux 2.6.32
- * [CLOCK_MONOTONIC_RAW] Linux 2.6.28, macOS 10.12
- * [CLOCK_MONOTONIC_RAW_APPROX] macOS 10.12
- * [CLOCK_BOOTTIME] Linux 2.6.39
- * [CLOCK_BOOTTIME_ALARM] Linux 3.0
- * [CLOCK_UPTIME] FreeBSD 7.0, OpenBSD 5.5
- * [CLOCK_UPTIME_FAST] FreeBSD 8.1
- * [CLOCK_UPTIME_RAW] macOS 10.12
- * [CLOCK_UPTIME_RAW_APPROX] macOS 10.12
- * [CLOCK_UPTIME_PRECISE] FreeBSD 8.1
- * [CLOCK_SECOND] FreeBSD 8.1
- * [CLOCK_TAI] Linux 3.10
+ * Process.clock_gettime(clock_id, unit = :float_second) -> number
+ *
+ * Returns a clock time as determined by POSIX function
+ * {clock_gettime()}[https://man7.org/linux/man-pages/man3/clock_gettime.3.html]:
+ *
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID) # => 198.650379677
+ *
+ * Argument +clock_id+ should be a symbol or a constant that specifies
+ * the clock whose time is to be returned;
+ * see below.
+ *
+ * Optional argument +unit+ should be a symbol that specifies
+ * the unit to be used in the returned clock time;
+ * see below.
+ *
+ * <b>Argument +clock_id+</b>
+ *
+ * Argument +clock_id+ specifies the clock whose time is to be returned;
+ * it may be a constant such as <tt>Process::CLOCK_REALTIME</tt>,
+ * or a symbol shorthand such as +:CLOCK_REALTIME+.
+ *
+ * The supported clocks depend on the underlying operating system;
+ * this method supports the following clocks on the indicated platforms
+ * (raises Errno::EINVAL if called with an unsupported clock):
+ *
+ * - +:CLOCK_BOOTTIME+: Linux 2.6.39.
+ * - +:CLOCK_BOOTTIME_ALARM+: Linux 3.0.
+ * - +:CLOCK_MONOTONIC+: SUSv3 to 4, Linux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 3.4, macOS 10.12, Windows-2000.
+ * - +:CLOCK_MONOTONIC_COARSE+: Linux 2.6.32.
+ * - +:CLOCK_MONOTONIC_FAST+: FreeBSD 8.1.
+ * - +:CLOCK_MONOTONIC_PRECISE+: FreeBSD 8.1.
+ * - +:CLOCK_MONOTONIC_RAW+: Linux 2.6.28, macOS 10.12.
+ * - +:CLOCK_MONOTONIC_RAW_APPROX+: macOS 10.12.
+ * - +:CLOCK_PROCESS_CPUTIME_ID+: SUSv3 to 4, Linux 2.5.63, FreeBSD 9.3, OpenBSD 5.4, macOS 10.12.
+ * - +:CLOCK_PROF+: FreeBSD 3.0, OpenBSD 2.1.
+ * - +:CLOCK_REALTIME+: SUSv2 to 4, Linux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 2.1, macOS 10.12, Windows-8/Server-2012.
+ * Time.now is recommended over +:CLOCK_REALTIME:.
+ * - +:CLOCK_REALTIME_ALARM+: Linux 3.0.
+ * - +:CLOCK_REALTIME_COARSE+: Linux 2.6.32.
+ * - +:CLOCK_REALTIME_FAST+: FreeBSD 8.1.
+ * - +:CLOCK_REALTIME_PRECISE+: FreeBSD 8.1.
+ * - +:CLOCK_SECOND+: FreeBSD 8.1.
+ * - +:CLOCK_TAI+: Linux 3.10.
+ * - +:CLOCK_THREAD_CPUTIME_ID+: SUSv3 to 4, Linux 2.5.63, FreeBSD 7.1, OpenBSD 5.4, macOS 10.12.
+ * - +:CLOCK_UPTIME+: FreeBSD 7.0, OpenBSD 5.5.
+ * - +:CLOCK_UPTIME_FAST+: FreeBSD 8.1.
+ * - +:CLOCK_UPTIME_PRECISE+: FreeBSD 8.1.
+ * - +:CLOCK_UPTIME_RAW+: macOS 10.12.
+ * - +:CLOCK_UPTIME_RAW_APPROX+: macOS 10.12.
+ * - +:CLOCK_VIRTUAL+: FreeBSD 3.0, OpenBSD 2.1.
*
* Note that SUS stands for Single Unix Specification.
* SUS contains POSIX and clock_gettime is defined in the POSIX part.
- * SUS defines CLOCK_REALTIME mandatory but
- * CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID and CLOCK_THREAD_CPUTIME_ID are optional.
- *
- * Also, several symbols are accepted as +clock_id+.
- * There are emulations for clock_gettime().
- *
- * For example, Process::CLOCK_REALTIME is defined as
- * +:GETTIMEOFDAY_BASED_CLOCK_REALTIME+ when clock_gettime() is not available.
- *
- * Emulations for +CLOCK_REALTIME+:
- * [:GETTIMEOFDAY_BASED_CLOCK_REALTIME]
- * Use gettimeofday() defined by SUS.
- * (SUSv4 obsoleted it, though.)
- * The resolution is 1 microsecond.
- * [:TIME_BASED_CLOCK_REALTIME]
- * Use time() defined by ISO C.
- * The resolution is 1 second.
- *
- * Emulations for +CLOCK_MONOTONIC+:
- * [:MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC]
- * Use mach_absolute_time(), available on Darwin.
- * The resolution is CPU dependent.
- * [:TIMES_BASED_CLOCK_MONOTONIC]
- * Use the result value of times() defined by POSIX.
- * POSIX defines it as "times() shall return the elapsed real time, in clock ticks, since an arbitrary point in the past (for example, system start-up time)".
- * For example, GNU/Linux returns a value based on jiffies and it is monotonic.
- * However, 4.4BSD uses gettimeofday() and it is not monotonic.
- * (FreeBSD uses clock_gettime(CLOCK_MONOTONIC) instead, though.)
- * The resolution is the clock tick.
- * "getconf CLK_TCK" command shows the clock ticks per second.
- * (The clock ticks per second is defined by HZ macro in older systems.)
- * If it is 100 and clock_t is 32 bits integer type, the resolution is 10 millisecond and
- * cannot represent over 497 days.
- *
- * Emulations for +CLOCK_PROCESS_CPUTIME_ID+:
- * [:GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID]
- * Use getrusage() defined by SUS.
- * getrusage() is used with RUSAGE_SELF to obtain the time only for
- * the calling process (excluding the time for child processes).
- * The result is addition of user time (ru_utime) and system time (ru_stime).
- * The resolution is 1 microsecond.
- * [:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID]
- * Use times() defined by POSIX.
- * The result is addition of user time (tms_utime) and system time (tms_stime).
- * tms_cutime and tms_cstime are ignored to exclude the time for child processes.
- * The resolution is the clock tick.
- * "getconf CLK_TCK" command shows the clock ticks per second.
- * (The clock ticks per second is defined by HZ macro in older systems.)
- * If it is 100, the resolution is 10 millisecond.
- * [:CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID]
- * Use clock() defined by ISO C.
- * The resolution is 1/CLOCKS_PER_SEC.
- * CLOCKS_PER_SEC is the C-level macro defined by time.h.
- * SUS defines CLOCKS_PER_SEC is 1000000.
- * Non-Unix systems may define it a different value, though.
- * If CLOCKS_PER_SEC is 1000000 as SUS, the resolution is 1 microsecond.
- * If CLOCKS_PER_SEC is 1000000 and clock_t is 32 bits integer type, it cannot represent over 72 minutes.
- *
- * If the given +clock_id+ is not supported, Errno::EINVAL is raised.
- *
- * +unit+ specifies a type of the return value.
- *
- * [:float_second] number of seconds as a float (default)
- * [:float_millisecond] number of milliseconds as a float
- * [:float_microsecond] number of microseconds as a float
- * [:second] number of seconds as an integer
- * [:millisecond] number of milliseconds as an integer
- * [:microsecond] number of microseconds as an integer
- * [:nanosecond] number of nanoseconds as an integer
+ * SUS defines +:CLOCK_REALTIME+ as mandatory but
+ * +:CLOCK_MONOTONIC+, +:CLOCK_PROCESS_CPUTIME_ID+,
+ * and +:CLOCK_THREAD_CPUTIME_ID+ are optional.
+ *
+ * Certain emulations are used when the given +clock_id+
+ * is not supported directly:
+ *
+ * - Emulations for +:CLOCK_REALTIME+:
+ *
+ * - +:GETTIMEOFDAY_BASED_CLOCK_REALTIME+:
+ * Use gettimeofday() defined by SUS (deprecated in SUSv4).
+ * The resolution is 1 microsecond.
+ * - +:TIME_BASED_CLOCK_REALTIME+:
+ * Use time() defined by ISO C.
+ * The resolution is 1 second.
+ *
+ * - Emulations for +:CLOCK_MONOTONIC+:
+ *
+ * - +:MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC+:
+ * Use mach_absolute_time(), available on Darwin.
+ * The resolution is CPU dependent.
+ * - +:TIMES_BASED_CLOCK_MONOTONIC+:
+ * Use the result value of times() defined by POSIX, thus:
+ * >>>
+ * Upon successful completion, times() shall return the elapsed real time,
+ * in clock ticks, since an arbitrary point in the past
+ * (for example, system start-up time).
+ *
+ * For example, GNU/Linux returns a value based on jiffies and it is monotonic.
+ * However, 4.4BSD uses gettimeofday() and it is not monotonic.
+ * (FreeBSD uses +:CLOCK_MONOTONIC+ instead, though.)
+ *
+ * The resolution is the clock tick.
+ * "getconf CLK_TCK" command shows the clock ticks per second.
+ * (The clock ticks-per-second is defined by HZ macro in older systems.)
+ * If it is 100 and clock_t is 32 bits integer type,
+ * the resolution is 10 millisecond and cannot represent over 497 days.
+ *
+ * - Emulations for +:CLOCK_PROCESS_CPUTIME_ID+:
+ *
+ * - +:GETRUSAGE_BASED_CLOCK_PROCESS_CPUTIME_ID+:
+ * Use getrusage() defined by SUS.
+ * getrusage() is used with RUSAGE_SELF to obtain the time only for
+ * the calling process (excluding the time for child processes).
+ * The result is addition of user time (ru_utime) and system time (ru_stime).
+ * The resolution is 1 microsecond.
+ * - +:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID+:
+ * Use times() defined by POSIX.
+ * The result is addition of user time (tms_utime) and system time (tms_stime).
+ * tms_cutime and tms_cstime are ignored to exclude the time for child processes.
+ * The resolution is the clock tick.
+ * "getconf CLK_TCK" command shows the clock ticks per second.
+ * (The clock ticks per second is defined by HZ macro in older systems.)
+ * If it is 100, the resolution is 10 millisecond.
+ * - +:CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID+:
+ * Use clock() defined by ISO C.
+ * The resolution is <tt>1/CLOCKS_PER_SEC</tt>.
+ * +CLOCKS_PER_SEC+ is the C-level macro defined by time.h.
+ * SUS defines +CLOCKS_PER_SEC+ as 1000000;
+ * other systems may define it differently.
+ * If +CLOCKS_PER_SEC+ is 1000000 (as in SUS),
+ * the resolution is 1 microsecond.
+ * If +CLOCKS_PER_SEC+ is 1000000 and clock_t is a 32-bit integer type,
+ * it cannot represent over 72 minutes.
+ *
+ * <b>Argument +unit+</b>
+ *
+ * Optional argument +unit+ (default +:float_second+)
+ * specifies the unit for the returned value.
+ *
+ * - +:float_microsecond+: Number of microseconds as a float.
+ * - +:float_millisecond+: Number of milliseconds as a float.
+ * - +:float_second+: Number of seconds as a float.
+ * - +:microsecond+: Number of microseconds as an integer.
+ * - +:millisecond+: Number of milliseconds as an integer.
+ * - +:nanosecond+: Number of nanoseconds as an integer.
+ * - +:second+: Number of seconds as an integer.
+ *
+ * Examples:
+ *
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :float_microsecond)
+ * # => 203605054.825
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :float_millisecond)
+ * # => 203643.696848
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :float_second)
+ * # => 203.762181929
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :microsecond)
+ * # => 204123212
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :millisecond)
+ * # => 204298
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :nanosecond)
+ * # => 204602286036
+ * Process.clock_gettime(:CLOCK_PROCESS_CPUTIME_ID, :second)
+ * # => 204
*
* The underlying function, clock_gettime(), returns a number of nanoseconds.
* Float object (IEEE 754 double) is not enough to represent
- * the return value for CLOCK_REALTIME.
+ * the return value for +:CLOCK_REALTIME+.
* If the exact nanoseconds value is required, use +:nanosecond+ as the +unit+.
*
- * The origin (zero) of the returned value varies.
- * For example, system start up time, process start up time, the Epoch, etc.
+ * The origin (time zero) of the returned value is system-dependent,
+ * and may be, for example, system start up time,
+ * process start up time, the Epoch, etc.
*
- * The origin in CLOCK_REALTIME is defined as the Epoch
- * (1970-01-01 00:00:00 UTC).
- * But some systems count leap seconds and others doesn't.
- * So the result can be interpreted differently across systems.
- * Time.now is recommended over CLOCK_REALTIME.
+ * The origin in +:CLOCK_REALTIME+ is defined as the Epoch:
+ * <tt>1970-01-01 00:00:00 UTC</tt>;
+ * some systems count leap seconds and others don't,
+ * so the result may vary across systems.
*/
static VALUE
rb_clock_gettime(int argc, VALUE *argv, VALUE _)
@@ -8453,45 +8452,39 @@ rb_clock_gettime(int argc, VALUE *argv, VALUE _)
/*
* call-seq:
- * Process.clock_getres(clock_id [, unit]) -> number
- *
- * Returns an estimate of the resolution of a +clock_id+ using the POSIX
- * <code>clock_getres()</code> function.
- *
- * Note the reported resolution is often inaccurate on most platforms due to
- * underlying bugs for this function and therefore the reported resolution
- * often differs from the actual resolution of the clock in practice.
- * Inaccurate reported resolutions have been observed for various clocks including
- * CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW when using Linux, macOS, BSD or AIX
- * platforms, when using ARM processors, or when using virtualization.
- *
- * +clock_id+ specifies a kind of clock.
- * See the document of +Process.clock_gettime+ for details.
- * +clock_id+ can be a symbol as for +Process.clock_gettime+.
+ * Process.clock_getres(clock_id, unit = :float_second) -> number
*
- * If the given +clock_id+ is not supported, Errno::EINVAL is raised.
+ * Returns a clock resolution as determined by POSIX function
+ * {clock_getres()}[https://man7.org/linux/man-pages/man3/clock_getres.3.html]:
*
- * +unit+ specifies the type of the return value.
- * +Process.clock_getres+ accepts +unit+ as +Process.clock_gettime+.
- * The default value, +:float_second+, is also the same as
- * +Process.clock_gettime+.
+ * Process.clock_getres(:CLOCK_REALTIME) # => 1.0e-09
*
- * +Process.clock_getres+ also accepts +:hertz+ as +unit+.
- * +:hertz+ means the reciprocal of +:float_second+.
+ * See Process.clock_gettime for the values of +clock_id+ and +unit+.
*
- * +:hertz+ can be used to obtain the exact value of
- * the clock ticks per second for the times() function and
- * CLOCKS_PER_SEC for the clock() function.
+ * Examples:
*
- * <code>Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :hertz)</code>
- * returns the clock ticks per second.
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :float_microsecond) # => 0.001
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :float_millisecond) # => 1.0e-06
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :float_second) # => 1.0e-09
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :microsecond) # => 0
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :millisecond) # => 0
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :nanosecond) # => 1
+ * Process.clock_getres(:CLOCK_PROCESS_CPUTIME_ID, :second) # => 0
*
- * <code>Process.clock_getres(:CLOCK_BASED_CLOCK_PROCESS_CPUTIME_ID, :hertz)</code>
- * returns CLOCKS_PER_SEC.
+ * In addition to the values for +unit+ supported in Process.clock_gettime,
+ * this method supports +:hertz+, the integer number of clock ticks per second
+ * (which is the reciprocal of +:float_second+):
*
- * p Process.clock_getres(Process::CLOCK_MONOTONIC)
- * #=> 1.0e-09
+ * Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :hertz) # => 100.0
+ * Process.clock_getres(:TIMES_BASED_CLOCK_PROCESS_CPUTIME_ID, :float_second) # => 0.01
*
+ * <b>Accuracy</b>:
+ * Note that the returned resolution may be inaccurate on some platforms
+ * due to underlying bugs.
+ * Inaccurate resolutions have been reported for various clocks including
+ * +:CLOCK_MONOTONIC+ and +:CLOCK_MONOTONIC_RAW+
+ * on Linux, macOS, BSD or AIX platforms, when using ARM processors,
+ * or when using virtualization.
*/
static VALUE
rb_clock_getres(int argc, VALUE *argv, VALUE _)
@@ -8647,38 +8640,82 @@ get_PROCESS_ID(ID _x, VALUE *_y)
/*
* call-seq:
- * Process.kill(signal, pid, *pids) -> integer
- *
- * Sends the given signal to the specified process id(s) if _pid_ is positive.
- * If _pid_ is zero, _signal_ is sent to all processes whose group ID is equal
- * to the group ID of the process. If _pid_ is negative, results are dependent
- * on the operating system. _signal_ may be an integer signal number or
- * a POSIX signal name (either with or without a +SIG+ prefix). If _signal_ is
- * negative (or starts with a minus sign), kills process groups instead of
- * processes. Not all signals are available on all platforms.
- * The keys and values of Signal.list are known signal names and numbers,
- * respectively.
- *
- * pid = fork do
- * Signal.trap("HUP") { puts "Ouch!"; exit }
- * # ... do some work ...
- * end
- * # ...
- * Process.kill("HUP", pid)
- * Process.wait
- *
- * <em>produces:</em>
+ * Process.kill(signal, *ids) -> count
+ *
+ * Sends a signal to each process specified by +ids+
+ * (which must specify at least one ID);
+ * returns the count of signals sent.
+ *
+ * For each given +id+, if +id+ is:
+ *
+ * - Positive, sends the signal to the process whose process ID is +id+.
+ * - Zero, send the signal to all processes in the current process group.
+ * - Negative, sends the signal to a system-dependent collection of processes.
+ *
+ * Argument +signal+ specifies the signal to be sent;
+ * the argument may be:
+ *
+ * - An integer signal number: e.g., +-29+, +0+, +29+.
+ * - A signal name (string), with or without leading <tt>'SIG'</tt>,
+ * and with or without a further prefixed minus sign (<tt>'-'</tt>):
+ * e.g.:
+ *
+ * - <tt>'SIGPOLL'</tt>.
+ * - <tt>'POLL'</tt>,
+ * - <tt>'-SIGPOLL'</tt>.
+ * - <tt>'-POLL'</tt>.
+ *
+ * - A signal symbol, with or without leading <tt>'SIG'</tt>,
+ * and with or without a further prefixed minus sign (<tt>'-'</tt>):
+ * e.g.:
+ *
+ * - +:SIGPOLL+.
+ * - +:POLL+.
+ * - <tt>:'-SIGPOLL'</tt>.
+ * - <tt>:'-POLL'</tt>.
+ *
+ * If +signal+ is:
+ *
+ * - A non-negative integer, or a signal name or symbol
+ * without prefixed <tt>'-'</tt>,
+ * each process with process ID +id+ is signalled.
+ * - A negative integer, or a signal name or symbol
+ * with prefixed <tt>'-'</tt>,
+ * each process group with group ID +id+ is signalled.
+ *
+ * Use method Signal.list to see which signals are supported
+ * by Ruby on the underlying platform;
+ * the method returns a hash of the string names
+ * and non-negative integer values of the supported signals.
+ * The size and content of the returned hash varies widely
+ * among platforms.
+ *
+ * Additionally, signal +0+ is useful to determine if the process exists.
+ *
+ * Example:
+ *
+ * pid = fork do
+ * Signal.trap('HUP') { puts 'Ouch!'; exit }
+ * # ... do some work ...
+ * end
+ * # ...
+ * Process.kill('HUP', pid)
+ * Process.wait
+ *
+ * Output:
*
* Ouch!
*
- * If _signal_ is an integer but wrong for signal, Errno::EINVAL or
- * RangeError will be raised. Otherwise unless _signal_ is a String
- * or a Symbol, and a known signal name, ArgumentError will be
- * raised.
+ * Exceptions:
*
- * Also, Errno::ESRCH or RangeError for invalid _pid_, Errno::EPERM
- * when failed because of no privilege, will be raised. In these
- * cases, signals may have been sent to preceding processes.
+ * - Raises Errno::EINVAL or RangeError if +signal+ is an integer
+ * but invalid.
+ * - Raises ArgumentError if +signal+ is a string or symbol
+ * but invalid.
+ * - Raises Errno::ESRCH or RangeError if one of +ids+ is invalid.
+ * - Raises Errno::EPERM if needed permissions are not in force.
+ *
+ * In the last two cases, signals may have been sent to some processes.
*/
static VALUE
@@ -8715,35 +8752,43 @@ static VALUE rb_mProcID_Syscall;
* * Precomputes the coderange of all strings.
* * Frees all empty heap pages and increments the allocatable pages counter
* by the number of pages freed.
+ * * Invoke +malloc_trim+ if available to free empty malloc pages.
*/
static VALUE
proc_warmup(VALUE _)
{
- RB_VM_LOCK_ENTER();
- rb_gc_prepare_heap();
- RB_VM_LOCK_LEAVE();
+ RB_VM_LOCKING() {
+ rb_gc_prepare_heap();
+ }
return Qtrue;
}
/*
* Document-module: Process
*
- * \Module +Process+ represents a process in the underlying operating system.
+ * Module +Process+ represents a process in the underlying operating system.
* Its methods support management of the current process and its child processes.
*
- * == \Process Creation
+ * == Process Creation
*
- * Each of these methods creates a process:
+ * Each of the following methods executes a given command in a new process or subshell,
+ * or multiple commands in new processes and/or subshells.
+ * The choice of process or subshell depends on the form of the command;
+ * see {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
*
- * - Process.exec: Replaces the current process by running a given external command.
- * - Process.spawn, Kernel#spawn: Executes the given command and returns its pid without waiting for completion.
- * - Kernel#system: Executes the given command in a subshell.
+ * - Process.spawn, Kernel#spawn: Executes the command;
+ * returns the new pid without waiting for completion.
+ * - Process.exec: Replaces the current process by executing the command.
*
- * Each of these methods accepts:
+ * In addition:
*
- * - An optional hash of environment variable names and values.
- * - An optional hash of execution options.
+ * - Method Kernel#system executes a given command-line (string) in a subshell;
+ * returns +true+, +false+, or +nil+.
+ * - Method Kernel#` executes a given command-line (string) in a subshell;
+ * returns its $stdout string.
+ * - Module Open3 supports creating child processes
+ * with access to their $stdin, $stdout, and $stderr streams.
*
* === Execution Environment
*
@@ -8756,7 +8801,6 @@ proc_warmup(VALUE _)
*
* Output:
*
- * nil
* "0"
*
* The effect is usually similar to that of calling ENV#update with argument +env+,
@@ -8768,6 +8812,119 @@ proc_warmup(VALUE _)
* if the new process fails.
* For example, hard resource limits are not restored.
*
+ * === Argument +command_line+ or +exe_path+
+ *
+ * The required string argument is one of the following:
+ *
+ * - +command_line+ if it begins with a shell reserved word or special built-in,
+ * or if it contains one or more meta characters.
+ * - +exe_path+ otherwise.
+ *
+ * ==== Argument +command_line+
+ *
+ * \String argument +command_line+ is a command line to be passed to a shell;
+ * it must begin with a shell reserved word, begin with a special built-in,
+ * or contain meta characters:
+ *
+ * system('if true; then echo "Foo"; fi') # => true # Shell reserved word.
+ * system('exit') # => true # Built-in.
+ * system('date > /tmp/date.tmp') # => true # Contains meta character.
+ * system('date > /nop/date.tmp') # => false
+ * system('date > /nop/date.tmp', exception: true) # Raises RuntimeError.
+ *
+ * The command line may also contain arguments and options for the command:
+ *
+ * system('echo "Foo"') # => true
+ *
+ * Output:
+ *
+ * Foo
+ *
+ * See {Execution Shell}[rdoc-ref:Process@Execution+Shell] for details about the shell.
+ *
+ * ==== Argument +exe_path+
+ *
+ * Argument +exe_path+ is one of the following:
+ *
+ * - The string path to an executable file to be called:
+ *
+ * Example:
+ *
+ * system('/usr/bin/date') # => true # Path to date on Unix-style system.
+ * system('foo') # => nil # Command execlution failed.
+ *
+ * Output:
+ *
+ * Thu Aug 31 10:06:48 AM CDT 2023
+ *
+ * A path or command name containing spaces without arguments cannot
+ * be distinguished from +command_line+ above, so you must quote or
+ * escape the entire command name using a shell in platform
+ * dependent manner, or use the array form below.
+ *
+ * If +exe_path+ does not contain any path separator, an executable
+ * file is searched from directories specified with the +PATH+
+ * environment variable. What the word "executable" means here is
+ * depending on platforms.
+ *
+ * Even if the file considered "executable", its content may not be
+ * in proper executable format. In that case, Ruby tries to run it
+ * by using <tt>/bin/sh</tt> on a Unix-like system, like system(3)
+ * does.
+ *
+ * File.write('shell_command', 'echo $SHELL', perm: 0o755)
+ * system('./shell_command') # prints "/bin/sh" or something.
+ *
+ * - A 2-element array containing the path to an executable
+ * and the string to be used as the name of the executing process:
+ *
+ * Example:
+ *
+ * pid = spawn(['sleep', 'Hello!'], '1') # 2-element array.
+ * p `ps -p #{pid} -o command=`
+ *
+ * Output:
+ *
+ * "Hello! 1\n"
+ *
+ * === Arguments +args+
+ *
+ * If +command_line+ does not contain shell meta characters except for
+ * spaces and tabs, or +exe_path+ is given, Ruby invokes the
+ * executable directly. This form does not use the shell:
+ *
+ * spawn("doesnt_exist") # Raises Errno::ENOENT
+ * spawn("doesnt_exist", "\n") # Raises Errno::ENOENT
+ *
+ * spawn("doesnt_exist\n") # => false
+ * # sh: 1: doesnot_exist: not found
+ *
+ * The error message is from a shell and would vary depending on your
+ * system.
+ *
+ * If one or more +args+ is given after +exe_path+, each is an
+ * argument or option to be passed to the executable:
+ *
+ * Example:
+ *
+ * system('echo', '<', 'C*', '|', '$SHELL', '>') # => true
+ *
+ * Output:
+ *
+ * < C* | $SHELL >
+ *
+ * However, there are exceptions on Windows. See {Execution Shell on
+ * Windows}[rdoc-ref:Process@Execution+Shell+on+Windows].
+ *
+ * If you want to invoke a path containing spaces with no arguments
+ * without shell, you will need to use a 2-element array +exe_path+.
+ *
+ * Example:
+ *
+ * path = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
+ * spawn(path) # Raises Errno::ENOENT; No such file or directory - /Applications/Google
+ * spawn([path] * 2)
+ *
* === Execution Options
*
* Optional trailing argument +options+ is a hash of execution options.
@@ -8803,7 +8960,7 @@ proc_warmup(VALUE _)
* The key for such an option may be an integer file descriptor (fd),
* specifying a source,
* or an array of fds, specifying multiple sources.
-
+ *
* An integer source fd may be specified as:
*
* - _n_: Specifies file descriptor _n_.
@@ -8860,7 +9017,7 @@ proc_warmup(VALUE _)
*
* 0644
*
- * ==== \Process Groups (+:pgroup+ and +:new_pgroup+)
+ * ==== Process Groups (+:pgroup+ and +:new_pgroup+)
*
* By default, the new process belongs to the same
* {process group}[https://en.wikipedia.org/wiki/Process_group]
@@ -8898,6 +9055,52 @@ proc_warmup(VALUE _)
* Use execution option <tt>:close_others => true</tt> to modify that inheritance
* by closing non-standard fds (3 and greater) that are not otherwise redirected.
*
+ * === Execution Shell
+ *
+ * On a Unix-like system, the shell invoked is <tt>/bin/sh</tt>;
+ * the entire string +command_line+ is passed as an argument
+ * to {shell option -c}[https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/sh.html].
+ *
+ * The shell performs normal shell expansion on the command line:
+ *
+ * Example:
+ *
+ * system('echo $SHELL: C*') # => true
+ *
+ * Output:
+ *
+ * /bin/bash: CONTRIBUTING.md COPYING COPYING.ja
+ *
+ * ==== Execution Shell on Windows
+ *
+ * On Windows, the shell invoked is determined by environment variable
+ * +RUBYSHELL+, if defined, or +COMSPEC+ otherwise; the entire string
+ * +command_line+ is passed as an argument to <tt>-c</tt> option for
+ * +RUBYSHELL+, as well as <tt>/bin/sh</tt>, and {/c
+ * option}[https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd]
+ * for +COMSPEC+. The shell is invoked automatically in the following
+ * cases:
+ *
+ * - The command is a built-in of +cmd.exe+, such as +echo+.
+ * - The executable file is a batch file; its name ends with +.bat+ or
+ * +.cmd+.
+ *
+ * Note that the command will still be invoked as +command_line+ form
+ * even when called in +exe_path+ form, because +cmd.exe+ does not
+ * accept a script name like <tt>/bin/sh</tt> does but only works with
+ * <tt>/c</tt> option.
+ *
+ * The standard shell +cmd.exe+ performs environment variable
+ * expansion but does not have globbing functionality:
+ *
+ * Example:
+ *
+ * system("echo %COMSPEC%: C*")' # => true
+ *
+ * Output:
+ *
+ * C:\WINDOWS\system32\cmd.exe: C*
+ *
* == What's Here
*
* === Current-Process Getters
@@ -8948,7 +9151,7 @@ proc_warmup(VALUE _)
* - ::waitall: Waits for all child processes to exit;
* returns their process IDs and statuses.
*
- * === \Process Groups
+ * === Process Groups
*
* - ::getpgid: Returns the process group ID for a process.
* - ::getpriority: Returns the scheduling priority
@@ -9045,8 +9248,6 @@ InitVM_process(void)
rb_define_singleton_method(rb_cProcessStatus, "wait", rb_process_status_waitv, -1);
rb_define_method(rb_cProcessStatus, "==", pst_equal, 1);
- rb_define_method(rb_cProcessStatus, "&", pst_bitand, 1);
- rb_define_method(rb_cProcessStatus, ">>", pst_rshift, 1);
rb_define_method(rb_cProcessStatus, "to_i", pst_to_i, 0);
rb_define_method(rb_cProcessStatus, "to_s", pst_to_s, 0);
rb_define_method(rb_cProcessStatus, "inspect", pst_inspect, 0);