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 + +g_unix_get_passwd_entry + g_unix_error_quark 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 +#include +#include /** * 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 +#include 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(); }