[glibc] posix: Move the glob home directory lookups out of __glob

Adhemerval Zanella via Glibc-cvs <[email protected]>
Newsgroups gmane.comp.lib.glibc.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=c31848f01b792e7069276e7cae9b417352a89e18

commit c31848f01b792e7069276e7cae9b417352a89e18
Author: Adhemerval Zanella <[email protected]>
Date:   Fri Jul 31 16:35:53 2026 +0000

    posix: Move the glob home directory lookups out of __glob
    
    Expanding "~" or "~user" needs a struct scratch_buffer to call
    getpwnam_r through, where the code might reserve extra stack in
    every glob frame (around 1224 bytes on x86_64).  Even though the
    lookups only run when the caller passed GLOB_TILDE or
    GLOB_TILDE_CHECK.
    
    Move the two lookups into glob_current_home_dir and glob_user_home_dir,
    which return the directory as a malloc'ed string.  The frame of each
    glob call drops to around 184 bytes.
    
    This also fixes a small leak: the ~user path returned GLOB_NOSPACE
    without freeing user_name when scratch_buffer_grow failed.
    
    Checked on x86_64-linux-gnu, aarch64-linux-gnu, and i686-linux-gnu.
    
    Reviewed-by: Collin Funk <[email protected]>

Diff:
---
 posix/glob.c           | 175 +++++++++++++++++++++++++++++++------------------
 posix/tst-glob-tilde.c |  19 ++++++
 2 files changed, 132 insertions(+), 62 deletions(-)

diff --git a/posix/glob.c b/posix/glob.c
index a81a2285b8..0e9bf02bb4 100644
--- a/posix/glob.c
+++ b/posix/glob.c
@@ -285,6 +285,101 @@ next_brace_sub (const char *cp, int flags)
   return *cp != '\0' ? cp : NULL;
 }
 
+#ifndef WINDOWS32
+static char *
+glob_dup_pw_dir (const struct passwd *p, bool *nospace)
+{
+  char *result = NULL;
+
+  if (p != NULL)
+    {
+      result = strdup (p->pw_dir);
+      if (result == NULL)
+        *nospace = true;
+    }
+  return result;
+}
+
+/* Return the home directory of the current user as a malloc'ed string, or
+   NULL if it cannot be determined.  Set *NOSPACE if the failure was caused
+   by an allocation failure.  */
+static char *
+glob_current_home_dir (bool *nospace)
+{
+  struct passwd *p;
+# if defined HAVE_GETPWNAM_R || defined _LIBC
+  struct passwd pwbuf;
+# endif
+  int err;
+
+  *nospace = false;
+
+  struct scratch_buffer s;
+  scratch_buffer_init (&s);
+  while (true)
+    {
+      p = NULL;
+      err = __getlogin_r (s.data, s.length);
+      if (err == 0)
+        {
+# if defined HAVE_GETPWNAM_R || defined _LIBC
+          size_t ssize = strlen (s.data) + 1;
+          char *sdata = s.data;
+          err = getpwnam_r (sdata, &pwbuf, sdata + ssize, s.length - ssize,
+                            &p);
+# else
+          p = getpwnam (s.data);
+          if (p == NULL)
+            err = errno;
+# endif
+        }
+      if (err != ERANGE)
+        break;
+      if (!scratch_buffer_grow (&s))
+        {
+          /* scratch_buffer_grow has already released the buffer.  */
+          *nospace = true;
+          return NULL;
+        }
+    }
+
+  char *result = err == 0 ? glob_dup_pw_dir (p, nospace) : NULL;
+  scratch_buffer_free (&s);
+  return result;
+}
+
+/* Likewise, for the home directory of the user named USER_NAME.  */
+static char *
+glob_user_home_dir (const char *user_name, bool *nospace)
+{
+  struct passwd *p;
+  struct scratch_buffer pwtmpbuf;
+  char *result;
+
+  *nospace = false;
+  scratch_buffer_init (&pwtmpbuf);
+
+# if defined HAVE_GETPWNAM_R || defined _LIBC
+  struct passwd pwbuf;
+
+  while (getpwnam_r (user_name, &pwbuf, pwtmpbuf.data, pwtmpbuf.length, &p)
+         == ERANGE)
+    if (!scratch_buffer_grow (&pwtmpbuf))
+      {
+        /* scratch_buffer_grow has already released the buffer.  */
+        *nospace = true;
+        return NULL;
+      }
+# else
+  p = getpwnam (user_name);
+# endif
+
+  result = glob_dup_pw_dir (p, nospace);
+  scratch_buffer_free (&pwtmpbuf);
+  return result;
+}
+#endif /* !WINDOWS32 */
+
 #ifndef GLOB_ATTRIBUTE
 # define GLOB_ATTRIBUTE
 #endif
@@ -642,43 +737,12 @@ __glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
               else
                 home_dir = "c:/users/default"; /* poor default */
 #else
-              int err;
-              struct passwd *p;
-              struct passwd pwbuf;
-              struct scratch_buffer s;
-              scratch_buffer_init (&s);
-              while (true)
-                {
-                  p = NULL;
-                  err = __getlogin_r (s.data, s.length);
-                  if (err == 0)
-                    {
-# if defined HAVE_GETPWNAM_R || defined _LIBC
-                      size_t ssize = strlen (s.data) + 1;
-                      char *sdata = s.data;
-                      err = getpwnam_r (sdata, &pwbuf, sdata + ssize,
-                                        s.length - ssize, &p);
-# else
-                      p = getpwnam (s.data);
-                      if (p == NULL)
-                        err = errno;
-# endif
-                    }
-                  if (err != ERANGE)
-                    break;
-                  if (!scratch_buffer_grow (&s))
-                    {
-                      retval = GLOB_NOSPACE;
-                      goto out;
-                    }
-                }
-              if (err == 0)
-                {
-                  home_dir = strdup (p->pw_dir);
-                  malloc_home_dir = 1;
-                }
-              scratch_buffer_free (&s);
-              if (err == 0 && home_dir == NULL)
+              bool nospace;
+
+              home_dir = glob_current_home_dir (&nospace);
+              if (home_dir != NULL)
+                malloc_home_dir = 1;
+              else if (nospace)
                 {
                   retval = GLOB_NOSPACE;
                   goto out;
@@ -809,34 +873,22 @@ __glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
 
           /* Look up specific user's home directory.  */
           {
-            struct passwd *p;
-            struct scratch_buffer pwtmpbuf;
-            scratch_buffer_init (&pwtmpbuf);
+            bool nospace;
+            char *home_dir = glob_user_home_dir (user_name, &nospace);
 
-#  if defined HAVE_GETPWNAM_R || defined _LIBC
-            struct passwd pwbuf;
+            if (__glibc_unlikely (malloc_user_name))
+              free (user_name);
 
-            while (getpwnam_r (user_name, &pwbuf,
-                               pwtmpbuf.data, pwtmpbuf.length, &p)
-                   == ERANGE)
+            if (__glibc_unlikely (nospace))
               {
-                if (!scratch_buffer_grow (&pwtmpbuf))
-                  {
-                    retval = GLOB_NOSPACE;
-                    goto out;
-                  }
+                retval = GLOB_NOSPACE;
+                goto out;
               }
-#  else
-            p = getpwnam (user_name);
-#  endif
-
-            if (__glibc_unlikely (malloc_user_name))
-              free (user_name);
 
             /* If we found a home directory use this.  */
-            if (p != NULL)
+            if (home_dir != NULL)
               {
-                size_t home_len = strlen (p->pw_dir);
+                size_t home_len = strlen (home_dir);
                 size_t rest_len = end_name == NULL ? 0 : strlen (end_name);
                 /* dirname contains end_name; we can't free it now.  */
                 char *prev_dirname =
@@ -849,17 +901,18 @@ __glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
                 if (dirname == NULL)
                   {
                     free (prev_dirname);
-                    scratch_buffer_free (&pwtmpbuf);
+                    free (home_dir);
                     retval = GLOB_NOSPACE;
                     goto out;
                   }
                 malloc_dirname = 1;
-                d = mempcpy (dirname, p->pw_dir, home_len);
+                d = mempcpy (dirname, home_dir, home_len);
                 if (end_name != NULL)
                   d = mempcpy (d, end_name, rest_len);
                 *d = '\0';
 
                 free (prev_dirname);
+                free (home_dir);
 
                 dirlen = home_len + rest_len;
                 dirname_modified = 1;
@@ -870,12 +923,10 @@ __glob (const char *pattern, int flags, int (*errfunc) (const char *, int),
                   {
                     /* We have to regard it as an error if we cannot find the
                        home directory.  */
-                    scratch_buffer_free (&pwtmpbuf);
                     retval = GLOB_NOMATCH;
                     goto out;
                   }
               }
-            scratch_buffer_free (&pwtmpbuf);
           }
 #else /* WINDOWS32 */
           /* On native Windows, access to a user's home directory
diff --git a/posix/tst-glob-tilde.c b/posix/tst-glob-tilde.c
index c893df6800..83d05faa11 100644
--- a/posix/tst-glob-tilde.c
+++ b/posix/tst-glob-tilde.c
@@ -135,6 +135,25 @@ do_test (void)
 	      one_test ("*/~", user_name, "/a/b");
 	    }
 
+  /* Exercise the current-user expansion (getlogin_r followed by getpwnam_r)
+     rather than the ~user lookup, with HOME unset and empty.  */
+  do_onlydir = 0;
+  do_mark = 0;
+  do_noescape = 0;
+  unsetenv ("HOME");
+  for (do_nocheck = 0; do_nocheck < 2; ++do_nocheck)
+    {
+      one_test ("~", "", "");
+      one_test ("~", "", "/");
+      one_test ("~", "", "/a/b");
+    }
+  setenv ("HOME", "", 1);
+  for (do_nocheck = 0; do_nocheck < 2; ++do_nocheck)
+    {
+      one_test ("~", "", "");
+      one_test ("~", "", "/a/b");
+    }
+
   free (repeat);
 
   return 0;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.