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

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

Commit Message

Alistair Francis via Libc-alpha June 25, 2020, 4:05 a.m.
---
 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

-- 
2.21.1

Comments

Alistair Francis via Libc-alpha July 1, 2020, 7:41 p.m. | #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.  */

> 



-- 
Cheers,
Carlos.

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.  */