[3/4] nss: Implement <nss_database.h>
Commit Message
---
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
Comments
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. */
>
@@ -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
# These are the databases that go through nss dispatch.
# Caution: if you add a database here, you must add its real name
new file mode 100644
@@ -0,0 +1,424 @@
+/* Mapping NSS services to action lists.
+ 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);
+};
+
+static void *global_place;
+
+static void *
+global_allocate (void *closure)
+{
+ 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;
+}
+
+static struct nss_database_state *
+nss_database_state_get (void)
+{
+ return allocate_once (&global_place, global_allocate, NULL, NULL);
+}
+
+/* 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. */
+};
+
+/* 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,
+ };
+
+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);
+ if (*result == NULL)
+ {
+ assert (errno == ENOMEM);
+ return false;
+ }
+ else
+ return true;
+}
+
+/* database_name must be large enough for each individual name plus a
+ null terminator. */
+typedef char database_name[11];
+#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);
+}
+
+static int
+name_to_database_index (const char *name)
+{
+ 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). */
+ return true;
+
+ nss_action_list result = __nss_action_parse (line);
+ if (result == NULL)
+ return false;
+ data->services[db] = result;
+ return true;
+}
+
+/* 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;
+}
+
+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;
+ }
+ 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);
+
+ /* 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;
+
+ 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, };
+
+ bool ok = nss_database_reload (&staging, &initial);
+
+ 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);
+}
+
+nss_action_list
+__nss_database_get_noreload (enum nss_database db)
+{
+ /* 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;
+}
+
+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);
+ }
+}
+
+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);
+ }
+ 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;
+}
new file mode 100644
@@ -0,0 +1,73 @@
+/* Mapping NSS services to action lists.
+ 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 */
@@ -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, ());
@@ -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;
+
/* 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);
+
_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);
}
/* Reset the lock the dynamic loader uses to protect its data. */