[PATCH 3/4] nss: Implement <nss_database.h>
Carlos O'Donell
carlos@redhat.com
Wed Jul 1 19:41:28 GMT 2020
On 6/25/20 12:05 AM, DJ Delorie via Libc-alpha wrote:
Please post v2 with
- Suggested comments.
- Minor suggestion about global_place name for global database structure name.
> ---
> nss/Makefile | 2 +-
> nss/nss_database.c | 424 +++++++++++++++++++++++++++++++++++++++
> nss/nss_database.h | 73 +++++++
> sysdeps/mach/hurd/fork.c | 8 +
> sysdeps/nptl/fork.c | 9 +
> 5 files changed, 515 insertions(+), 1 deletion(-)
> create mode 100644 nss/nss_database.c
> create mode 100644 nss/nss_database.h
>
> diff --git a/nss/Makefile b/nss/Makefile
> index 464655d045..194b183c91 100644
> --- a/nss/Makefile
> +++ b/nss/Makefile
> @@ -29,7 +29,7 @@ routines = nsswitch getnssent getnssent_r digits_dots \
> valid_field valid_list_field rewrite_field \
> $(addsuffix -lookup,$(databases)) \
> compat-lookup nss_hash nss_module nss_action \
> - nss_action_parse
> + nss_action_parse nss_database
OK. Add nss_database.
>
> # These are the databases that go through nss dispatch.
> # Caution: if you add a database here, you must add its real name
> diff --git a/nss/nss_database.c b/nss/nss_database.c
> new file mode 100644
> index 0000000000..0f6342d0c8
> --- /dev/null
> +++ b/nss/nss_database.c
> @@ -0,0 +1,424 @@
> +/* Mapping NSS services to action lists.
OK.
> + Copyright (C) 1996-2020 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include "nss_database.h"
> +
> +#include <allocate_once.h>
> +#include <array_length.h>
> +#include <assert.h>
> +#include <atomic.h>
> +#include <ctype.h>
> +#include <file_change_detection.h>
> +#include <libc-lock.h>
> +#include <netdb.h>
> +#include <stdio_ext.h>
> +#include <string.h>
> +
> +struct nss_database_state
> +{
> + struct nss_database_data data;
> + __libc_lock_define (, lock);
> +};
OK.
> +
Suggest:
/* Global NSS database state. */
> +static void *global_place;
I suggest calling this something more descriptive. Please pick any
name other than "global_place" which seems like a placeholder.
> +
Suggest:
/* Allocate and return pointer to nss_database_state object or
on failure return NULL. */
> +static void *
> +global_allocate (void *closure)
Name this to match what you call "global_place" e.g. foo_allocate.
> +{
> + struct nss_database_state *result = malloc (sizeof (*result));
> + if (result != NULL)
> + {
> + result->data.nsswitch_conf.size = -1; /* Force reload. */
> + memset (result->data.services, 0, sizeof (result->data.services));
> + result->data.initialized = true;
> + result->data.reload_disabled = false;
> + __libc_lock_init (result->lock);
> + }
> + return result;
> +}
> +
Suggest:
/* Return pointer to global NSS database state, allocating as
required, or returning NULL on failure. */
> +static struct nss_database_state *
> +nss_database_state_get (void)
> +{
> + return allocate_once (&global_place, global_allocate, NULL, NULL);
> +}
OK.
> +
> +/* Database default selections. nis/compat mappings get turned into
> + "files" for !LINK_OBSOLETE_NSL configurations. */
> +enum nss_database_default
> +{
> + nss_database_default_defconfig = 0, /* "nis [NOTFOUND=return] files". */
> + nss_database_default_compat, /* "compat [NOTFOUND=return] files". */
> + nss_database_default_dns, /* "dns [!UNAVAIL=return] files". */
> + nss_database_default_files, /* "files". */
> + nss_database_default_nis, /* "nis". */
> + nss_database_default_nis_nisplus, /* "nis nisplus". */
> + nss_database_default_none, /* Empty list. */
> +
> + NSS_DATABASE_DEFAULT_COUNT /* Number of defaults. */
> +};
OK.
> +
> +/* Databases not listed default to nss_database_default_defconfig. */
> +static const char per_database_defaults[NSS_DATABASE_COUNT] =
> + {
> + [nss_database_group] = nss_database_default_compat,
> + [nss_database_gshadow] = nss_database_default_files,
> + [nss_database_hosts] = nss_database_default_dns,
> + [nss_database_initgroups] = nss_database_default_none,
> + [nss_database_networks] = nss_database_default_dns,
> + [nss_database_passwd] = nss_database_default_compat,
> + [nss_database_publickey] = nss_database_default_nis_nisplus,
> + [nss_database_shadow] = nss_database_default_compat,
> + };
OK.
> +
> +struct nss_database_default_cache
> +{
> + nss_action_list caches[NSS_DATABASE_DEFAULT_COUNT];
> +};
> +
> +static bool
> +nss_database_select_default (struct nss_database_default_cache *cache,
> + enum nss_database db, nss_action_list *result)
> +{
> + enum nss_database_default def = per_database_defaults[db];
> + *result = cache->caches[def];
> + if (*result != NULL)
> + return true;
> +
> + /* Determine the default line string. */
> + const char *line;
> + switch (def)
> + {
> +#ifdef LINK_OBSOLETE_NSL
> + case nss_database_default_defconfig:
> + line = "nis [NOTFOUND=return] files";
> + break;
> + case nss_database_default_compat:
> + line = "compat [NOTFOUND=return] files";
> + break;
> +#endif
> +
> + case nss_database_default_dns:
> + line = "dns [!UNAVAIL=return] files";
> + break;
> +
> + case nss_database_default_files:
> +#ifndef LINK_OBSOLETE_NSL
> + case nss_database_default_defconfig:
> + case nss_database_default_compat:
> +#endif
> + line = "files";
> + break;
> +
> + case nss_database_default_nis:
> + line = "nis";
> + break;
> +
> + case nss_database_default_nis_nisplus:
> + line = "nis nisplus";
> + break;
> +
> + case nss_database_default_none:
> + /* Very special case: Leave *result as NULL. */
> + return true;
> +
> + case NSS_DATABASE_DEFAULT_COUNT:
> + __builtin_unreachable ();
> + }
> + if (def < 0 || def >= NSS_DATABASE_DEFAULT_COUNT)
> + /* Tell GCC that line is initialized. */
> + __builtin_unreachable ();
> +
> + *result = __nss_action_parse (line);
OK. Parse and cache the line.
> + if (*result == NULL)
> + {
> + assert (errno == ENOMEM);
> + return false;
> + }
> + else
> + return true;
> +}
OK.
> +
> +/* database_name must be large enough for each individual name plus a
> + null terminator. */
> +typedef char database_name[11];
OK.
> +#define DEFINE_DATABASE(name) \
> + _Static_assert (sizeof (#name) <= sizeof (database_name), #name);
> +#include "databases.def"
> +#undef DEFINE_DATABASE
> +
> +static const database_name nss_database_name_array[] =
> + {
> +#define DEFINE_DATABASE(name) #name,
> +#include "databases.def"
> +#undef DEFINE_DATABASE
> + };
> +
> +static int
> +name_search (const void *left, const void *right)
> +{
> + return strcmp (left, right);
> +}
OK.
> +
> +static int
> +name_to_database_index (const char *name)
OK.
> +{
> + database_name *name_entry = bsearch (name, nss_database_name_array,
> + array_length (nss_database_name_array),
> + sizeof (database_name), name_search);
> + if (name_entry == NULL)
> + return -1;
> + return name_entry - nss_database_name_array;
> +}
> +
> +static bool
> +process_line (struct nss_database_data *data, char *line)
> +{
> + /* Ignore leading white spaces. ATTENTION: this is different from
> + what is implemented in Solaris. The Solaris man page says a line
> + beginning with a white space character is ignored. We regard
> + this as just another misfeature in Solaris. */
> + while (isspace (line[0]))
> + ++line;
> +
> + /* Recognize `<database> ":"'. */
> + char *name = line;
> + while (line[0] != '\0' && !isspace (line[0]) && line[0] != ':')
> + ++line;
> + if (line[0] == '\0' || name == line)
> + /* Syntax error. Skip this line. */
> + return true;
> + *line++ = '\0';
> +
> + int db = name_to_database_index (name);
> + if (db < 0)
> + /* Not our database (e.g., sudoers). */
Suggest:
/* Not our database e.g. sudoers, automount etc. */
> + return true;
> +
> + nss_action_list result = __nss_action_parse (line);
> + if (result == NULL)
> + return false;
> + data->services[db] = result;
> + return true;
OK.
> +}
> +
> +/* Iterate over the lines in FP, parse them, and store them in DATA.
> + Return false on memory allocation failure, true on success. */
> +static bool
> +nss_database_reload_1 (struct nss_database_data *data, FILE *fp)
> +{
> + char *line = NULL;
> + size_t line_allocated = 0;
> + bool result = false;
> +
> + while (true)
> + {
> + ssize_t ret = __getline (&line, &line_allocated, fp);
> + if (ferror_unlocked (fp))
> + break;
> + if (feof_unlocked (fp))
> + {
> + result = true;
> + break;
> + }
> + assert (ret > 0);
> + (void) ret; /* For NDEBUG builds. */
> +
> + if (!process_line (data, line))
> + break;
> + }
> +
> + free (line);
> + return result;
> +}
OK. Parse all lines one at a time storing to DATA.
> +
> +static bool
> +nss_database_reload (struct nss_database_data *staging,
> + struct file_change_detection *initial)
> +{
> + FILE *fp = fopen (_PATH_NSSWITCH_CONF, "rce");
> + if (fp == NULL)
> + switch (errno)
> + {
> + case EACCES:
> + case EISDIR:
> + case ELOOP:
> + case ENOENT:
> + case ENOTDIR:
> + case EPERM:
> + /* Ignore these errors. They are persistent errors caused
> + by file system contents. */
> + break;
> + default:
> + /* Other errors refer to resource allocation problems and
> + need to be handled by the application. */
> + return false;
OK.
> + }
> + else
> + /* No other threads have access to fp. */
> + __fsetlocking (fp, FSETLOCKING_BYCALLER);
> +
> + bool ok = true;
> + if (fp != NULL)
> + ok = nss_database_reload_1 (staging, fp);
OK. Parse into STAGING.
> +
> + /* Apply defaults. */
> + if (ok)
> + {
> + struct nss_database_default_cache cache = { };
> + for (int i = 0; i < NSS_DATABASE_COUNT; ++i)
> + if (staging->services[i] == NULL)
> + {
> + ok = nss_database_select_default (&cache, i,
> + &staging->services[i]);
> + if (!ok)
> + break;
> + }
> + }
> +
> + if (ok)
> + ok = __file_change_detection_for_fp (&staging->nsswitch_conf, fp);
> +
> + if (fp != NULL)
> + {
> + int saved_errno = errno;
> + fclose (fp);
> + __set_errno (saved_errno);
> + }
> +
> + if (ok && !__file_is_unchanged (&staging->nsswitch_conf, initial))
> + /* Reload is required because the file changed while reading. */
> + staging->nsswitch_conf.size = -1;
OK.
> +
> + return ok;
> +}
> +
> +static bool
> +nss_database_check_reload_and_get (struct nss_database_state *local,
> + nss_action_list *result,
> + enum nss_database database_index)
> +{
> + /* Acquire MO is needed because the thread that sets reload_disabled
> + may have loaded the configuration first, so synchronize with the
> + Release MO store there. */
> + if (atomic_load_acquire (&local->data.reload_disabled))
> + /* No reload, so there is no error. */
> + return true;
> +
> + struct file_change_detection initial;
> + if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF))
> + return false;
> +
> + __libc_lock_lock (local->lock);
> + if (__file_is_unchanged (&initial, &local->data.nsswitch_conf))
> + {
> + /* Configuration is up-to-date. Read it and return it to the
> + caller. */
> + *result = local->data.services[database_index];
> + __libc_lock_unlock (local->lock);
> + return true;
> + }
> + __libc_lock_unlock (local->lock);
> +
> + /* Avoid overwriting the global configuration until we have loaded
> + everything successfully. Otherwise, if the file change
> + information changes back to what is in the global configuration,
> + the lookups would use the partially-written configuration. */
> + struct nss_database_data staging = { .initialized = true, };
OK. Use a staging nss_database_data structure.
> +
> + bool ok = nss_database_reload (&staging, &initial);
OK. Reload.
> +
> + if (ok)
> + {
> + __libc_lock_lock (local->lock);
> +
> + /* See above for memory order. */
> + if (!atomic_load_acquire (&local->data.reload_disabled))
> + /* This may go back in time if another thread beats this
> + thread with the update, but in this case, a reload happens
> + on the next NSS call. */
> + local->data = staging;
> +
> + *result = local->data.services[database_index];
> + __libc_lock_unlock (local->lock);
> + }
> +
> + return ok;
> +}
> +
> +bool
> +__nss_database_get (enum nss_database db, nss_action_list *actions)
> +{
> + struct nss_database_state *local = nss_database_state_get ();
> + return nss_database_check_reload_and_get (local, actions, db);
OK. Reload as required.
> +}
> +
> +nss_action_list
> +__nss_database_get_noreload (enum nss_database db)
OK. No reload, just get the database.
> +{
> + /* There must have been a previous __nss_database_get call. */
> + struct nss_database_state *local = atomic_load_acquire (&global_place);
> + assert (local != NULL);
> +
> + __libc_lock_lock (local->lock);
> + nss_action_list result = local->data.services[db];
> + __libc_lock_unlock (local->lock);
> + return result;
> +}
> +
> +void __libc_freeres_fn_section
> +__nss_database_freeres (void)
> +{
> + free (global_place);
> + global_place = NULL;
OK. Straight forward freeres.
> +}
> +
> +void
> +__nss_database_fork_prepare_parent (struct nss_database_data *data)
> +{
> + /* Do not use allocate_once to trigger loading unnecessarily. */
> + struct nss_database_state *local = atomic_load_acquire (&global_place);
> + if (local == NULL)
> + data->initialized = false;
> + else
> + {
> + /* Make a copy of the configuration. This approach was chosen
> + because it avoids acquiring the lock during the actual
> + fork. */
> + __libc_lock_lock (local->lock);
> + *data = local->data;
> + __libc_lock_unlock (local->lock);
> + }
OK. Fork handling.
> +}
> +
> +void
> +__nss_database_fork_subprocess (struct nss_database_data *data)
> +{
> + struct nss_database_state *local = atomic_load_acquire (&global_place);
> + if (data->initialized)
> + {
> + /* Restore the state at the point of the fork. */
> + assert (local != NULL);
> + local->data = *data;
> + __libc_lock_init (local->lock);
OK. Init lock in subprocess.
> + }
> + else if (local != NULL)
> + /* The NSS configuration was loaded concurrently during fork. We
> + do not know its state, so we need to discard it. */
> + global_place = NULL;
OK. Lost this data and so if this happens too much we will leak here, but
otherwise doing anything differently is going to be hard.
> +}
> diff --git a/nss/nss_database.h b/nss/nss_database.h
> new file mode 100644
> index 0000000000..a157bbdbb0
> --- /dev/null
> +++ b/nss/nss_database.h
> @@ -0,0 +1,73 @@
> +/* Mapping NSS services to action lists.
OK.
> + Copyright (C) 2020 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#ifndef _NSS_DATABASE_H
> +#define _NSS_DATABASE_H
> +
> +#include <file_change_detection.h>
> +
> +#include "nss_action.h"
> +
> +/* The enumeration literal in enum nss_database for the database NAME
> + (e.g., nss_database_hosts for hosts). */
> +#define NSS_DATABASE_LITERAL(name) nss_database_##name
> +
> +enum nss_database
> +{
> +#define DEFINE_DATABASE(name) NSS_DATABASE_LITERAL (name),
> +#include "databases.def"
> +#undef DEFINE_DATABASE
> +
> + /* Total number of databases. */
> + NSS_DATABASE_COUNT
> +};
> +
> +
> +/* Looks up the action list for DB and stores it in *ACTIONS. Returns
> + true on success or false on failure. Success can mean that
> + *ACTIONS is NULL. */
> +bool __nss_database_get (enum nss_database db, nss_action_list *actions)
> + attribute_hidden;
> +
> +/* Like __nss_database_get, but does not reload /etc/nsswitch.conf
> + from disk. This assumes that there has been a previous successful
> + __nss_database_get call (which may not have returned any data). */
> +nss_action_list __nss_database_get_noreload (enum nss_database db)
> + attribute_hidden;
> +
> +/* Called from __libc_freeres. */
> +void __nss_database_freeres (void) attribute_hidden;
> +
> +/* Internal type. Exposed only for fork handling purposes. */
> +struct nss_database_data
> +{
> + struct file_change_detection nsswitch_conf;
> + nss_action_list services[NSS_DATABASE_COUNT];
> + int reload_disabled; /* Actually bool; int for atomic access. */
> + bool initialized;
> +};
> +
> +/* Called by fork in the parent process, before forking. */
> +void __nss_database_fork_prepare_parent (struct nss_database_data *data)
> + attribute_hidden;
> +
> +/* Called by fork in the new subprocess, after forking. */
> +void __nss_database_fork_subprocess (struct nss_database_data *data)
> + attribute_hidden;
> +
> +#endif /* _NSS_DATABASE_H */
OK.
> diff --git a/sysdeps/mach/hurd/fork.c b/sysdeps/mach/hurd/fork.c
> index 32783069ec..1aec951e76 100644
> --- a/sysdeps/mach/hurd/fork.c
> +++ b/sysdeps/mach/hurd/fork.c
> @@ -28,6 +28,7 @@
> #include "hurdmalloc.h" /* XXX */
> #include <tls.h>
> #include <malloc/malloc-internal.h>
> +#include <nss/nss_database.h>
>
> #undef __fork
>
> @@ -68,6 +69,7 @@ __fork (void)
> size_t i;
> error_t err;
> struct hurd_sigstate *volatile ss;
> + struct nss_database_data nss_database_data;
>
> RUN_HOOK (_hurd_atfork_prepare_hook, ());
>
> @@ -109,6 +111,9 @@ __fork (void)
> /* Run things that prepare for forking before we create the task. */
> RUN_HOOK (_hurd_fork_prepare_hook, ());
>
> + call_function_static_weak (__nss_database_fork_prepare_parent,
> + &nss_database_data);
> +
> /* Lock things that want to be locked before we fork. */
> {
> void *const *p;
> @@ -666,6 +671,9 @@ __fork (void)
> _hurd_malloc_fork_child ();
> call_function_static_weak (__malloc_fork_unlock_child);
>
> + call_function_static_weak (__nss_database_fork_subprocess,
> + &nss_database_data);
> +
> /* Run things that want to run in the child task to set up. */
> RUN_HOOK (_hurd_fork_child_hook, ());
>
> diff --git a/sysdeps/nptl/fork.c b/sysdeps/nptl/fork.c
> index 5091a000e3..964eb1e5a8 100644
> --- a/sysdeps/nptl/fork.c
> +++ b/sysdeps/nptl/fork.c
> @@ -32,6 +32,7 @@
> #include <arch-fork.h>
> #include <futex-internal.h>
> #include <malloc/malloc-internal.h>
> +#include <nss/nss_database.h>
>
> static void
> fresetlockfiles (void)
> @@ -57,6 +58,8 @@ __libc_fork (void)
>
> __run_fork_handlers (atfork_run_prepare, multiple_threads);
>
> + struct nss_database_data nss_database_data;
OK.
> +
> /* If we are not running multiple threads, we do not have to
> preserve lock state. If fork runs from a signal handler, only
> async-signal-safe functions can be used in the child. These data
> @@ -64,6 +67,9 @@ __libc_fork (void)
> not matter if fork was called from a signal handler. */
> if (multiple_threads)
> {
> + call_function_static_weak (__nss_database_fork_prepare_parent,
> + &nss_database_data);
OK.
> +
> _IO_list_lock ();
>
> /* Acquire malloc locks. This needs to come last because fork
> @@ -118,6 +124,9 @@ __libc_fork (void)
>
> /* Reset locks in the I/O code. */
> _IO_list_resetlock ();
> +
> + call_function_static_weak (__nss_database_fork_subprocess,
> + &nss_database_data);
OK.
> }
>
> /* Reset the lock the dynamic loader uses to protect its data. */
>
--
Cheers,
Carlos.
More information about the Libc-alpha
mailing list