[PATCH 2/6] posix: Move the glob home directory lookups out of __glob

Adhemerval Zanella <[email protected]> Mon, 3 Aug 2026 16:19:16 -0300
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <[email protected]>
Expanding "~" or "~user" needs a struct scratch_buffer to call
getpwnam_r through, where thecoce 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
tGLOB_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.
---
 posix/glob.c           | 177 ++++++++++++++++++++++++++---------------
 posix/tst-glob-tilde.c |  19 +++++
 2 files changed, 132 insertions(+), 64 deletions(-)

diff --git a/posix/glob.c b/posix/glob.c
index a81a2285b89..1b1fd8eda02 100644
--- a/posix/glob.c
+++ b/posix/glob.c
@@ -285,6 +285,99 @@ 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;
+  struct passwd pwbuf;
+  struct scratch_buffer s;
+  char *result;
+  int err;
+
+  *nospace = false;
+  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;
+        }
+    }
+
+  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 +735,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 +871,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);
-
-#  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))
-                  {
-                    retval = GLOB_NOSPACE;
-                    goto out;
-                  }
-              }
-#  else
-            p = getpwnam (user_name);
-#  endif
+            bool nospace;
+            char *home_dir = glob_user_home_dir (user_name, &nospace);
 
             if (__glibc_unlikely (malloc_user_name))
               free (user_name);
 
-            /* If we found a home directory use this.  */
-            if (p != NULL)
+            if (__glibc_unlikely (nospace))
               {
-                size_t home_len = strlen (p->pw_dir);
+                retval = GLOB_NOSPACE;
+                goto out;
+              }
+
+            /* If we found a home directory use this.  */
+            if (home_dir != NULL)
+              {
+                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 +899,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 +921,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 c893df6800b..83d05faa11f 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;
-- 
2.53.0