summaryrefslogtreecommitdiff
path: root/process.c
diff options
context:
space:
mode:
Diffstat (limited to 'process.c')
-rw-r--r--process.c620
1 files changed, 347 insertions, 273 deletions
diff --git a/process.c b/process.c
index eeafc4c483..19f3172cb8 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"
@@ -875,109 +875,6 @@ pst_equal(VALUE st1, VALUE st2)
/*
* call-seq:
- * stat & mask -> integer
- *
- * This method is deprecated as #to_i value is system-specific; use
- * predicate methods like #exited? or #stopped?, or getters like #exitstatus
- * or #stopsig.
- *
- * Returns the logical AND of the value of #to_i with +mask+:
- *
- * `cat /nop`
- * stat = $? # => #<Process::Status: pid 1155508 exit 1>
- * sprintf('%x', stat.to_i) # => "100"
- * stat & 0x00 # => 0
- *
- * ArgumentError is raised if +mask+ is negative.
- */
-
-static VALUE
-pst_bitand(VALUE st1, VALUE st2)
-{
- int status = PST2INT(st1);
- int mask = NUM2INT(st2);
-
- if (mask < 0) {
- rb_raise(rb_eArgError, "negative mask value: %d", mask);
- }
-#define WARN_SUGGEST(suggest) \
- rb_warn_deprecated_to_remove_at(3.5, "Process::Status#&", suggest)
-
- switch (mask) {
- case 0x80:
- WARN_SUGGEST("Process::Status#coredump?");
- break;
- case 0x7f:
- WARN_SUGGEST("Process::Status#signaled? or Process::Status#termsig");
- break;
- case 0xff:
- WARN_SUGGEST("Process::Status#exited?, Process::Status#stopped? or Process::Status#coredump?");
- break;
- case 0xff00:
- WARN_SUGGEST("Process::Status#exitstatus or Process::Status#stopsig");
- break;
- default:
- WARN_SUGGEST("other Process::Status predicates");
- break;
- }
-#undef WARN_SUGGEST
- status &= mask;
-
- return INT2NUM(status);
-}
-
-
-/*
- * call-seq:
- * stat >> places -> integer
- *
- * This method is deprecated as #to_i value is system-specific; use
- * predicate methods like #exited? or #stopped?, or getters like #exitstatus
- * or #stopsig.
- *
- * Returns the value of #to_i, shifted +places+ to the right:
- *
- * `cat /nop`
- * stat = $? # => #<Process::Status: pid 1155508 exit 1>
- * stat.to_i # => 256
- * stat >> 1 # => 128
- * stat >> 2 # => 64
- *
- * ArgumentError is raised if +places+ is negative.
- */
-
-static VALUE
-pst_rshift(VALUE st1, VALUE st2)
-{
- int status = PST2INT(st1);
- int places = NUM2INT(st2);
-
- if (places < 0) {
- rb_raise(rb_eArgError, "negative shift value: %d", places);
- }
-#define WARN_SUGGEST(suggest) \
- rb_warn_deprecated_to_remove_at(3.5, "Process::Status#>>", suggest)
-
- switch (places) {
- case 7:
- WARN_SUGGEST("Process::Status#coredump?");
- break;
- case 8:
- WARN_SUGGEST("Process::Status#exitstatus or Process::Status#stopsig");
- break;
- default:
- WARN_SUGGEST("other Process::Status attributes");
- break;
- }
-#undef WARN_SUGGEST
- status >>= places;
-
- return INT2NUM(status);
-}
-
-
-/*
- * call-seq:
* stopped? -> true or false
*
* Returns +true+ if this process is stopped,
@@ -1200,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;
@@ -1452,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.
*
@@ -1677,12 +1576,14 @@ 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();
@@ -2734,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);
}
@@ -2825,7 +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();
+ VALUE tmpbuf = rb_imemo_tmpbuf_new();
rb_imemo_tmpbuf_set_ptr(tmpbuf, ruby_xmalloc(run_exec_dup2_tmpbuf_size(len)));
eargp->dup2_tmpbuf = tmpbuf;
}
@@ -2929,7 +2830,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;
/*
@@ -3056,7 +2957,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];
@@ -3083,7 +2984,7 @@ NORETURN(static VALUE f_exec(int c, const VALUE *a, VALUE _));
* or contain meta characters:
*
* exec('if true; then echo "Foo"; fi') # Shell reserved word.
- * exec('echo') # Built-in.
+ * exec('exit') # Built-in.
* exec('date > date.tmp') # Contains meta character.
*
* The command line may also contain arguments and options for the command:
@@ -3114,7 +3015,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
*
@@ -3139,9 +3042,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);
@@ -3358,6 +3265,7 @@ run_exec_dup2(VALUE ary, VALUE tmpbuf, struct rb_execarg *sargp, char *errmsg, s
// 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;
}
}
@@ -4103,7 +4011,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
@@ -4191,7 +4102,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
@@ -4210,50 +4121,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();
before_fork_ruby();
+ rb_thread_acquire_fork_lock();
disable_child_handler_before_fork(&old);
- {
- pid = rb_fork();
- err = errno;
- if (status) {
- status->pid = pid;
- status->error = err;
- }
- }
- disable_child_handler_fork_parent(&old); /* yes, bad name */
- after_fork_ruby(pid);
- 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;
}
@@ -4734,7 +4642,7 @@ rb_spawn(int argc, const VALUE *argv)
* - 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:
*
@@ -4772,14 +4680,14 @@ rb_spawn(int argc, const VALUE *argv)
* or contain meta characters:
*
* system('if true; then echo "Foo"; fi') # => true # Shell reserved word.
- * system('echo') # => true # Built-in.
+ * 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('echo') # => true # Built-in.
+ * system('exit') # => true # Built-in.
* $? # => #<Process::Status: pid 640610 exit 0>
* system('date > /nop/date.tmp') # => false
* $? # => #<Process::Status: pid 640742 exit 2>
@@ -4820,7 +4728,9 @@ rb_spawn(int argc, const VALUE *argv)
* system('foo') # => nil
* $? # => #<Process::Status: pid 645608 exit 127>
*
- * 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.
*
* system('doesnt_exist') # => nil
*
@@ -4912,7 +4822,7 @@ rb_f_system(int argc, VALUE *argv, 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].
*
* Returns the process ID (pid) of the new process,
* without waiting for it to complete.
@@ -4948,7 +4858,7 @@ rb_f_system(int argc, VALUE *argv, VALUE _)
*
* spawn('if true; then echo "Foo"; fi') # => 798847 # Shell reserved word.
* Process.wait # => 798847
- * spawn('echo') # => 798848 # Built-in.
+ * spawn('exit') # => 798848 # Built-in.
* Process.wait # => 798848
* spawn('date > /tmp/date.tmp') # => 798879 # Contains meta character.
* Process.wait # => 798849
@@ -4972,26 +4882,20 @@ rb_f_system(int argc, VALUE *argv, VALUE _)
*
* Argument +exe_path+ is one of the following:
*
- * - The string path to an executable to be called:
+ * - 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.
*
* spawn('/usr/bin/date') # Path to date on Unix-style system.
* Process.wait
*
* Output:
*
- * Thu Aug 31 10:06:48 AM CDT 2023
- *
- * - A 2-element array containing the path to an executable
- * and the string to be used as the name of the executing process:
- *
- * pid = spawn(['sleep', 'Hello!'], '1') # 2-element array.
- * p `ps -p #{pid} -o command=`
- *
- * Output:
- *
- * "Hello! 1\n"
+ * Mon Aug 28 11:43:10 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.
*
* If one or more +args+ is given, each is an argument or option
* to be passed to the executable:
@@ -5257,7 +5161,7 @@ static rb_pid_t
ruby_setsid(void)
{
rb_pid_t pid;
- int ret;
+ int ret, fd;
pid = getpid();
#if defined(SETPGRP_VOID)
@@ -5719,6 +5623,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
@@ -5727,18 +5637,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 */
@@ -5752,10 +5662,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;
}
@@ -5775,17 +5683,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;
@@ -5794,10 +5704,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
@@ -5806,13 +5752,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 */
@@ -5824,58 +5768,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.
*/
@@ -5888,11 +5851,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 */
@@ -5904,53 +5864,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) */
@@ -5964,7 +5923,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.
*/
@@ -5986,7 +5945,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;
@@ -5999,15 +5957,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
@@ -6046,6 +6007,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
@@ -6063,7 +6044,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;
@@ -6076,15 +6056,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
@@ -8247,7 +8230,7 @@ ruby_real_ms_time(void)
* - +: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.
+ * - +:second+: Number of seconds as an integer.
*
* Examples:
*
@@ -8779,19 +8762,19 @@ static VALUE rb_mProcID_Syscall;
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 the following methods executes a given command in a new process or subshell,
* or multiple commands in new processes and/or subshells.
@@ -8804,11 +8787,11 @@ proc_warmup(VALUE _)
*
* In addition:
*
- * - \Method Kernel#system executes a given command-line (string) in a subshell;
+ * - 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;
+ * - Method Kernel#` executes a given command-line (string) in a subshell;
* returns its $stdout string.
- * - \Module Open3 supports creating child processes
+ * - Module Open3 supports creating child processes
* with access to their $stdin, $stdout, and $stderr streams.
*
* === Execution Environment
@@ -8841,14 +8824,14 @@ proc_warmup(VALUE _)
* or if it contains one or more meta characters.
* - +exe_path+ otherwise.
*
- * <b>Argument +command_line+</b>
+ * ==== 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('echo') # => true # Built-in.
+ * 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.
@@ -8863,22 +8846,88 @@ proc_warmup(VALUE _)
*
* See {Execution Shell}[rdoc-ref:Process@Execution+Shell] for details about the shell.
*
- * <b>Argument +exe_path+</b>
+ * ==== Argument +exe_path+
*
* 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 to be called,
- * and the string to be used as the name of the executing process.
+ * - 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('/usr/bin/date') # => true # Path to date on Unix-style system.
- * system('foo') # => nil # Command failed.
+ * system('echo', '<', 'C*', '|', '$SHELL', '>') # => true
*
* Output:
*
- * Mon Aug 28 11:43:10 AM CDT 2023
+ * < 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
*
@@ -8972,7 +9021,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]
@@ -9013,21 +9062,48 @@ proc_warmup(VALUE _)
* === Execution Shell
*
* On a Unix-like system, the shell invoked is <tt>/bin/sh</tt>;
- * otherwise the shell invoked is determined by environment variable
- * <tt>ENV['RUBYSHELL']</tt>, if defined, or <tt>ENV['COMSPEC']</tt> otherwise.
- *
- * 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:
*
- * spawn('echo C*') # => 799139
- * Process.wait # => 799139
+ * 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:
*
- * CONTRIBUTING.md COPYING COPYING.ja
+ * C:\WINDOWS\system32\cmd.exe: C*
*
* == What's Here
*
@@ -9079,7 +9155,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
@@ -9176,8 +9252,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);