Message ID | xnlfkb4wou.fsf@greed.delorie.com |
---|---|
State | New |
Headers | show |
Series | nsswitch.conf reloading refactoring | expand |
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 --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. */