summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorusa <usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2021-04-05 00:04:41 +0000
committerusa <usa@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2021-04-05 00:04:41 +0000
commit209d0f0e6d6fabd014988fdf2f655d6f03d4cc41 (patch)
treef168db962b5d678ad724e9cf509314195b8df221
parent5f83e778cc6cefe5982f73e3296286472b84a147 (diff)
downloadruby-209d0f0e6d6fabd014988fdf2f655d6f03d4cc41.tar.gz
merge revision(s) c15cddd1d515c5bd8dfe8fb2725e3f723aec63b8: [Backport #16787]
Allow Dir.home to work for non-login procs when $HOME not set Allow the 'Dir.home' method to reliably locate the user's home directory when all three of the following are true at the same time: 1. Ruby is running on a Unix-like OS 2. The $HOME environment variable is not set 3. The process is not a descendant of login(1) (or a work-alike) The prior behavior was that the lookup could only work for login-descended processes. This is accomplished by looking up the user's record in the password database by uid (getpwuid_r(3)) as a fallback to the lookup by name (getpwname_r(3)) which is still attempted first (based on the name, if any, returned by getlogin_r(3)). If getlogin_r(3), getpwnam_r(3), and/or getpwuid_r(3) is not available at compile time, will fallback on using their respective non-*_r() variants: getlogin(3), getpwnam(3), and/or getpwuid(3). The rationale for attempting to do the lookup by name prior to doing it by uid is to accommodate the possibility of multiple login names (each with its own record in the password database, so each with a potentially different home directory) being mapped to the same uid (as is explicitly allowed for by POSIX; see getlogin(3posix)). Preserves the existing behavior for login-descended processes, and adds the new capability of having Dir.home being able to find the user's home directory for non-login-descended processes. Fixes [Bug #16787] Related discussion: https://bugs.ruby-lang.org/issues/16787 https://github.com/ruby/ruby/pull/3034 git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_2_6@67931 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--configure.ac5
-rw-r--r--file.c51
-rw-r--r--internal.h6
-rw-r--r--process.c265
-rw-r--r--version.h2
5 files changed, 311 insertions, 18 deletions
diff --git a/configure.ac b/configure.ac
index 3a71fa0108..0a1d79cacd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1840,10 +1840,15 @@ AC_CHECK_FUNCS(getgidx)
AC_CHECK_FUNCS(getgrnam)
AC_CHECK_FUNCS(getgrnam_r)
AC_CHECK_FUNCS(getgroups)
+AC_CHECK_FUNCS(getlogin)
+AC_CHECK_FUNCS(getlogin_r)
AC_CHECK_FUNCS(getpgid)
AC_CHECK_FUNCS(getpgrp)
AC_CHECK_FUNCS(getpriority)
+AC_CHECK_FUNCS(getpwnam)
AC_CHECK_FUNCS(getpwnam_r)
+AC_CHECK_FUNCS(getpwuid)
+AC_CHECK_FUNCS(getpwuid_r)
AC_CHECK_FUNCS(getresgid)
AC_CHECK_FUNCS(getresuid)
AC_CHECK_FUNCS(getrlimit)
diff --git a/file.c b/file.c
index 6721d9b406..94ac327e74 100644
--- a/file.c
+++ b/file.c
@@ -3425,21 +3425,42 @@ rb_default_home_dir(VALUE result)
#if defined HAVE_PWD_H
if (!dir) {
- const char *login = getlogin();
- if (login) {
- struct passwd *pw = getpwnam(login);
- if (pw) {
- copy_home_path(result, pw->pw_dir);
- endpwent();
- return result;
- }
- endpwent();
- rb_raise(rb_eArgError, "couldn't find HOME for login `%s' -- expanding `~'",
- login);
- }
- else {
- rb_raise(rb_eArgError, "couldn't find login name -- expanding `~'");
- }
+ /* We'll look up the user's default home dir in the password db by
+ * login name, if possible, and failing that will fall back to looking
+ * the information up by uid (as would be needed for processes that
+ * are not a descendant of login(1) or a work-alike).
+ *
+ * While the lookup by uid is more likely to succeed (since we always
+ * have a uid, but may or may not have a login name), we prefer first
+ * looking up by name to accommodate the possibility of multiple login
+ * names (each with its own record in the password database, so each
+ * with a potentially different home directory) being mapped to the
+ * same uid (as explicitly allowed for by POSIX; see getlogin(3posix)).
+ */
+ VALUE login_name = rb_getlogin();
+
+# if !defined(HAVE_GETPWUID_R) && !defined(HAVE_GETPWUID)
+ /* This is a corner case, but for backward compatibility reasons we
+ * want to emit this error if neither the lookup by login name nor
+ * lookup by getuid() has a chance of succeeding.
+ */
+ if (NIL_P(login_name)) {
+ rb_raise(rb_eArgError, "couldn't find login name -- expanding `~'");
+ }
+# endif
+
+ VALUE pw_dir = rb_getpwdirnam_for_login(login_name);
+ if (NIL_P(pw_dir)) {
+ pw_dir = rb_getpwdiruid();
+ if (NIL_P(pw_dir)) {
+ rb_raise(rb_eArgError, "couldn't find home for uid `%ld'", (long)getuid());
+ }
+ }
+
+ /* found it */
+ copy_home_path(result, RSTRING_PTR(pw_dir));
+ rb_str_resize(pw_dir, 0);
+ return result;
}
#endif
if (!dir) {
diff --git a/internal.h b/internal.h
index 9583e8388e..37e5c73d18 100644
--- a/internal.h
+++ b/internal.h
@@ -2303,6 +2303,12 @@ void rb_write_error_str(VALUE mesg);
/* numeric.c (export) */
VALUE rb_int_positive_pow(long x, unsigned long y);
+#ifdef HAVE_PWD_H
+VALUE rb_getlogin(void);
+VALUE rb_getpwdirnam_for_login(VALUE login); /* read as: "get pwd db home dir by username for login" */
+VALUE rb_getpwdiruid(void); /* read as: "get pwd db home dir for getuid()" */
+#endif
+
/* process.c (export) */
int rb_exec_async_signal_safe(const struct rb_execarg *e, char *errmsg, size_t errmsg_buflen);
rb_pid_t rb_fork_async_signal_safe(int *status, int (*chfunc)(void*, char *, size_t), void *charg, VALUE fds, char *errmsg, size_t errmsg_buflen);
diff --git a/process.c b/process.c
index 47f4771974..863ee31f90 100644
--- a/process.c
+++ b/process.c
@@ -154,12 +154,40 @@ static int exec_async_signal_safe(const struct rb_execarg *, char *, size_t);
#define p_gid_from_name p_gid_from_name
#endif
+#if defined(HAVE_UNISTD_H)
+# if defined(HAVE_GETLOGIN_R)
+# define USE_GETLOGIN_R 1
+# define GETLOGIN_R_SIZE_DEFAULT 0x100
+# define GETLOGIN_R_SIZE_LIMIT 0x1000
+# if defined(_SC_LOGIN_NAME_MAX)
+# define GETLOGIN_R_SIZE_INIT sysconf(_SC_LOGIN_NAME_MAX)
+# else
+# define GETLOGIN_R_SIZE_INIT GETLOGIN_R_SIZE_DEFAULT
+# endif
+# elif defined(HAVE_GETLOGIN)
+# define USE_GETLOGIN 1
+# endif
+#endif
+
#if defined(HAVE_PWD_H)
-# if defined(HAVE_GETPWNAM_R) && defined(_SC_GETPW_R_SIZE_MAX)
+# if defined(HAVE_GETPWUID_R)
+# define USE_GETPWUID_R 1
+# elif defined(HAVE_GETPWUID)
+# define USE_GETPWUID 1
+# endif
+# if defined(HAVE_GETPWNAM_R)
# define USE_GETPWNAM_R 1
-# define GETPW_R_SIZE_INIT sysconf(_SC_GETPW_R_SIZE_MAX)
+# elif defined(HAVE_GETPWNAM)
+# define USE_GETPWNAM 1
+# endif
+# if defined(HAVE_GETPWNAM_R) || defined(HAVE_GETPWUID_R)
# define GETPW_R_SIZE_DEFAULT 0x1000
# define GETPW_R_SIZE_LIMIT 0x10000
+# if defined(_SC_GETPW_R_SIZE_MAX)
+# define GETPW_R_SIZE_INIT sysconf(_SC_GETPW_R_SIZE_MAX)
+# else
+# define GETPW_R_SIZE_INIT GETPW_R_SIZE_DEFAULT
+# endif
# endif
# ifdef USE_GETPWNAM_R
# define PREPARE_GETPWNAM \
@@ -5462,6 +5490,239 @@ check_gid_switch(void)
}
+#if defined(HAVE_PWD_H)
+/**
+ * Best-effort attempt to obtain the name of the login user, if any,
+ * associated with the process. Processes not descended from login(1) (or
+ * similar) may not have a logged-in user; returns Qnil in that case.
+ */
+VALUE
+rb_getlogin(void)
+{
+#if ( !defined(USE_GETLOGIN_R) && !defined(USE_GETLOGIN) )
+ return Qnil;
+#else
+ char MAYBE_UNUSED(*login) = NULL;
+
+# ifdef USE_GETLOGIN_R
+
+ long loginsize = GETLOGIN_R_SIZE_INIT; /* maybe -1 */
+
+ if (loginsize < 0)
+ loginsize = GETLOGIN_R_SIZE_DEFAULT;
+
+ VALUE maybe_result = rb_str_buf_new(loginsize);
+
+ login = RSTRING_PTR(maybe_result);
+ loginsize = rb_str_capacity(maybe_result);
+ rb_str_set_len(maybe_result, loginsize);
+
+ int gle;
+ errno = 0;
+ while ((gle = getlogin_r(login, loginsize)) != 0) {
+
+ if (gle == ENOTTY || gle == ENXIO || gle == ENOENT) {
+ rb_str_resize(maybe_result, 0);
+ return Qnil;
+ }
+
+ if (gle != ERANGE || loginsize >= GETLOGIN_R_SIZE_LIMIT) {
+ rb_str_resize(maybe_result, 0);
+ rb_syserr_fail(gle, "getlogin_r");
+ }
+
+ rb_str_modify_expand(maybe_result, loginsize);
+ login = RSTRING_PTR(maybe_result);
+ loginsize = rb_str_capacity(maybe_result);
+ }
+
+ if (login == NULL) {
+ rb_str_resize(maybe_result, 0);
+ return Qnil;
+ }
+
+ return maybe_result;
+
+# elif USE_GETLOGIN
+
+ errno = 0;
+ login = getlogin();
+ if (errno) {
+ if (errno == ENOTTY || errno == ENXIO || errno == ENOENT) {
+ return Qnil;
+ }
+ rb_syserr_fail(errno, "getlogin");
+ }
+
+ return login ? rb_str_new_cstr(login) : Qnil;
+# endif
+
+#endif
+}
+
+VALUE
+rb_getpwdirnam_for_login(VALUE login_name)
+{
+#if ( !defined(USE_GETPWNAM_R) && !defined(USE_GETPWNAM) )
+ return Qnil;
+#else
+
+ if (NIL_P(login_name)) {
+ /* nothing to do; no name with which to query the password database */
+ return Qnil;
+ }
+
+ 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 */
+
+ if (bufsizenm < 0)
+ bufsizenm = GETPW_R_SIZE_DEFAULT;
+
+ VALUE getpwnm_tmp = rb_str_tmp_new(bufsizenm);
+
+ bufnm = RSTRING_PTR(getpwnm_tmp);
+ bufsizenm = rb_str_capacity(getpwnm_tmp);
+ rb_str_set_len(getpwnm_tmp, 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 */
+ rb_str_resize(getpwnm_tmp, 0);
+ return Qnil;
+ }
+
+ if (enm != ERANGE || bufsizenm >= 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);
+ }
+
+ if (pwptr == 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);
+ rb_str_resize(getpwnm_tmp, 0);
+ return result;
+
+# elif USE_GETPWNAM
+
+ 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");
+ }
+
+ return Qnil; /* not found */
+# endif
+
+#endif
+}
+
+/**
+ * Look up the user's dflt home dir in the password db, by uid.
+ */
+VALUE
+rb_getpwdiruid(void)
+{
+# if !defined(USE_GETPWUID_R) && !defined(USE_GETPWUID)
+ /* Should never happen... </famous-last-words> */
+ return Qnil;
+# 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 */
+
+ if (bufsizeid < 0)
+ bufsizeid = GETPW_R_SIZE_DEFAULT;
+
+ VALUE getpwid_tmp = rb_str_tmp_new(bufsizeid);
+
+ bufid = RSTRING_PTR(getpwid_tmp);
+ bufsizeid = rb_str_capacity(getpwid_tmp);
+ rb_str_set_len(getpwid_tmp, 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 */
+ rb_str_resize(getpwid_tmp, 0);
+ return Qnil;
+ }
+
+ if (eid != ERANGE || bufsizeid >= 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);
+ }
+
+ if (pwptr == 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);
+ rb_str_resize(getpwid_tmp, 0);
+ return result;
+
+# elif defined(USE_GETPWUID)
+
+ 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");
+ }
+
+ return Qnil; /* not found */
+# endif
+
+#endif /* !defined(USE_GETPWUID_R) && !defined(USE_GETPWUID) */
+}
+#endif /* HAVE_PWD_H */
+
+
/*********************************************************************
* Document-class: Process::Sys
*
diff --git a/version.h b/version.h
index 5e65375fb1..868edf66f9 100644
--- a/version.h
+++ b/version.h
@@ -1,6 +1,6 @@
#define RUBY_VERSION "2.6.7"
#define RUBY_RELEASE_DATE "2021-04-05"
-#define RUBY_PATCHLEVEL 190
+#define RUBY_PATCHLEVEL 191
#define RUBY_RELEASE_YEAR 2021
#define RUBY_RELEASE_MONTH 4