diff mbox series

[3/4] nss: Implement <nss_database.h>

Message ID xnlfkb4wou.fsf@greed.delorie.com
State Superseded
Headers show
Series nsswitch.conf reloading refactoring | expand

Commit Message

DJ Delorie June 25, 2020, 4:05 a.m. UTC
---
 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

Carlos O'Donell July 1, 2020, 7:41 p.m. UTC | #1
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.  */
>
diff mbox series

Patch

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
 
 # 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.
+   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;
+}
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.
+   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 */
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;
+
   /* 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.  */