[glib: 1/3] glib-unix: Add g_unix_get_passwd_entry() function



commit 94a800fb9d30705625c96b01dfeca9af82a0961a
Author: Philip Withnall <withnall endlessm com>
Date:   Tue Jun 11 13:52:48 2019 +0100

    glib-unix: Add g_unix_get_passwd_entry() function
    
    This is a convenience wrapper around getpwnam_r() which handles all the
    memory allocation faff.
    
    Signed-off-by: Philip Withnall <withnall endlessm com>
    
    Helps: #1687

 docs/reference/glib/glib-sections.txt |   3 +
 glib/glib-unix.c                      | 121 ++++++++++++++++++++++++++++++++++
 glib/glib-unix.h                      |   4 ++
 glib/tests/unix.c                     |  37 +++++++++++
 4 files changed, 165 insertions(+)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index e9dfa73e9..15f4a22e6 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -2455,6 +2455,9 @@ g_unix_fd_add
 g_unix_fd_add_full
 g_unix_fd_source_new
 
+<SUBSECTION>
+g_unix_get_passwd_entry
+
 <SUBSECTION Private>
 g_unix_error_quark
 </SECTION>
diff --git a/glib/glib-unix.c b/glib/glib-unix.c
index 9328e7008..87fa57ae8 100644
--- a/glib/glib-unix.c
+++ b/glib/glib-unix.c
@@ -30,6 +30,8 @@
 #include "gmain-internal.h"
 
 #include <string.h>
+#include <sys/types.h>
+#include <pwd.h>
 
 /**
  * SECTION:gunix
@@ -421,3 +423,122 @@ g_unix_fd_add (gint              fd,
 {
   return g_unix_fd_add_full (G_PRIORITY_DEFAULT, fd, condition, function, user_data, NULL);
 }
+
+/**
+ * g_unix_get_passwd_entry:
+ * @user_name: the username to get the passwd file entry for
+ * @error: return location for a #GError, or %NULL
+ *
+ * Get the `passwd` file entry for the given @user_name using `getpwnam_r()`.
+ * This can fail if the given @user_name doesn’t exist.
+ *
+ * The returned `struct passwd` has been allocated using g_malloc() and should
+ * be freed using g_free(). The strings referenced by the returned struct are
+ * included in the same allocation, so are valid until the `struct passwd` is
+ * freed.
+ *
+ * This function is safe to call from multiple threads concurrently.
+ *
+ * You will need to include `pwd.h` to get the definition of `struct passwd`.
+ *
+ * Returns: (transfer full): passwd entry, or %NULL on error; free the returned
+ *    value with g_free()
+ * Since: 2.64
+ */
+struct passwd *
+g_unix_get_passwd_entry (const gchar  *user_name,
+                         GError      **error)
+{
+  struct passwd *passwd_file_entry;
+  struct
+    {
+      struct passwd pwd;
+      char string_buffer[];
+    } *buffer = NULL;
+  gsize string_buffer_size = 0;
+  GError *local_error = NULL;
+  int errsv = 0;
+
+  g_return_val_if_fail (user_name != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+#ifdef _SC_GETPW_R_SIZE_MAX
+    {
+      /* Get the recommended buffer size */
+      glong string_buffer_size_long = sysconf (_SC_GETPW_R_SIZE_MAX);
+      if (string_buffer_size_long > 0)
+        string_buffer_size = string_buffer_size_long;
+    }
+#endif /* _SC_GETPW_R_SIZE_MAX */
+
+  /* Default starting size. */
+  if (string_buffer_size == 0)
+    string_buffer_size = 64;
+
+  do
+    {
+      int retval;
+
+      g_free (buffer);
+      /* Allocate space for the `struct passwd`, and then a buffer for all its
+       * strings (whose size is @string_buffer_size, which increases in this
+       * loop until it’s big enough). Add 6 extra bytes to work around a bug in
+       * macOS < 10.3. See #156446.
+       */
+      buffer = g_malloc0 (sizeof (buffer) + string_buffer_size + 6);
+
+      errno = 0;
+      retval = getpwnam_r (user_name, &buffer->pwd, buffer->string_buffer,
+                           string_buffer_size, &passwd_file_entry);
+      errsv = errno;
+
+      /* Bail out if: the lookup was successful, or if the user id can't be
+       * found (should be pretty rare case actually), or if the buffer should be
+       * big enough and yet lookups are still not successful.
+       */
+      if (passwd_file_entry != NULL)
+        {
+          /* Success. */
+          break;
+        }
+      else if (retval == 0 ||
+          errsv == ENOENT || errsv == ESRCH ||
+          errsv == EBADF || errsv == EPERM)
+        {
+          /* Username not found. */
+          g_unix_set_error_from_errno (&local_error, errsv);
+          break;
+        }
+      else if (errsv == ERANGE)
+        {
+          /* Can’t allocate enough string buffer space. */
+          if (string_buffer_size > 32 * 1024)
+            {
+              g_unix_set_error_from_errno (&local_error, errsv);
+              break;
+            }
+
+          string_buffer_size *= 2;
+          continue;
+        }
+      else
+        {
+          g_unix_set_error_from_errno (&local_error, errsv);
+          break;
+        }
+    }
+  while (passwd_file_entry == NULL);
+
+  g_assert (passwd_file_entry == NULL ||
+            (gpointer) passwd_file_entry == (gpointer) buffer);
+
+  /* Success or error. */
+  if (local_error != NULL)
+    {
+      g_clear_pointer (&buffer, g_free);
+      g_propagate_error (error, g_steal_pointer (&local_error));
+      errno = errsv;
+    }
+
+  return (struct passwd *) g_steal_pointer (&buffer);
+}
diff --git a/glib/glib-unix.h b/glib/glib-unix.h
index ef8702d79..a5ea9a1c5 100644
--- a/glib/glib-unix.h
+++ b/glib/glib-unix.h
@@ -114,6 +114,10 @@ guint    g_unix_fd_add             (gint              fd,
                                     GUnixFDSourceFunc function,
                                     gpointer          user_data);
 
+GLIB_AVAILABLE_IN_2_64
+struct passwd *g_unix_get_passwd_entry (const gchar  *user_name,
+                                        GError      **error);
+
 G_END_DECLS
 
 #endif  /* __G_UNIX_H__ */
diff --git a/glib/tests/unix.c b/glib/tests/unix.c
index 82ffd0f56..7639d066a 100644
--- a/glib/tests/unix.c
+++ b/glib/tests/unix.c
@@ -25,6 +25,7 @@
 
 #include "glib-unix.h"
 #include <string.h>
+#include <pwd.h>
 
 static void
 test_pipe (void)
@@ -295,6 +296,40 @@ test_callback_after_signal (void)
   g_main_context_unref (context);
 }
 
+static void
+test_get_passwd_entry_root (void)
+{
+  struct passwd *pwd;
+  GError *local_error = NULL;
+
+  g_test_summary ("Tests that g_unix_get_passwd_entry() works for a "
+                  "known-existing username.");
+
+  pwd = g_unix_get_passwd_entry ("root", &local_error);
+  g_assert_no_error (local_error);
+
+  g_assert_cmpstr (pwd->pw_name, ==, "root");
+  g_assert_cmpuint (pwd->pw_uid, ==, 0);
+
+  g_free (pwd);
+}
+
+static void
+test_get_passwd_entry_nonexistent (void)
+{
+  struct passwd *pwd;
+  GError *local_error = NULL;
+
+  g_test_summary ("Tests that g_unix_get_passwd_entry() returns an error for a "
+                  "nonexistent username.");
+
+  pwd = g_unix_get_passwd_entry ("thisusernamedoesntexist", &local_error);
+  g_assert_error (local_error, G_UNIX_ERROR, 0);
+  g_assert_null (pwd);
+
+  g_clear_error (&local_error);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -310,6 +345,8 @@ main (int   argc,
   g_test_add_func ("/glib-unix/sighup_add_remove", test_sighup_add_remove);
   g_test_add_func ("/glib-unix/sighup_nested", test_sighup_nested);
   g_test_add_func ("/glib-unix/callback_after_signal", test_callback_after_signal);
+  g_test_add_func ("/glib-unix/get-passwd-entry/root", test_get_passwd_entry_root);
+  g_test_add_func ("/glib-unix/get-passwd-entry/nonexistent", test_get_passwd_entry_nonexistent);
 
   return g_test_run();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]