2015-12-06  Florian Weimer  <fweimer@redhat.com>

	Implement test hook for nss_files database file selection.
	* nss/Makefile (tests): Add tst-nss-files.
	(libnss_files-routines): Add files-config.
	(LDLIBS-tst-nss-files): New variable.
	* nss/Versions (libnss_files): Export _nss_files_set_config.
	* nss/nss_db/db-XXX.c (DBFILE_): New macro.
	(DBFILE): Use it to stringify DATABASE.
	* nss/nss_files.h: New file.
	* nss/nss_files/files-XXX.c: Update comment: DATABASE is now an
	identifier.
	(DATAFILE): Adjust.
	* nss/nss_files/files-config.c: New file.
	* nss/nss_files/files-ethers.c (DATABASE): Adjust.
	* nss/nss_files/files-group.c (DATABASE): Likewise.
	* nss/nss_files/files-hosts.c (DATABASE): Likewise.
	* nss/nss_files/files-key.c (DATABASE): Likewise.
	* nss/nss_files/files-netgrp.c (DATABASE): Likewise.
	* nss/nss_files/files-network.c (DATABASE): Likewise.
	* nss/nss_files/files-parse.c: Update comment: DATABASE is now an
	identifier.
	* nss/nss_files/files-proto.c (DATABASE): Adjust.
	* nss/nss_files/files-pwd.c (DATABASE): Likewise.
	* nss/nss_files/files-rpc.c (DATABASE): Likewise.
	* nss/nss_files/files-services.c (DATABASE): Likewise.
	* nss/nss_files/files-sgrp.c (DATABASE): Likewise.
	* nss/nss_files/files-spwd.c (DATABASE): Likewise.
	* nss/test-data/files/netgroup: New file.
	* nss/test-data/files/passwd: Likewise.
	* nss/tst-nss-files.c: Likewise.

diff --git a/nss/Makefile b/nss/Makefile
index bbbad85..e666ec6 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -50,7 +50,7 @@ extra-objs		+= $(makedb-modules:=.o)
 
 tests-static            = tst-field
 tests			= test-netdb tst-nss-test1 test-digits-dots \
-			  tst-nss-getpwent bug17079 \
+			  tst-nss-getpwent bug17079 tst-nss-files \
 			  $(tests-static)
 xtests			= bug-erange
 
@@ -68,7 +68,8 @@ vpath %.c $(subdir-dirs) ../locale/programs ../intl
 
 
 libnss_files-routines	:= $(addprefix files-,$(databases)) \
-			   files-initgroups files-have_o_cloexec files-init
+			   files-initgroups files-have_o_cloexec files-init \
+			   files-config
 
 libnss_db-dbs		:= $(addprefix db-,\
 				       $(filter-out hosts network key alias,\
@@ -124,3 +125,5 @@ $(objpfx)/libnss_test1.so$(libnss_test1.so-version): $(objpfx)/libnss_test1.so
 	$(make-link)
 endif
 $(objpfx)tst-nss-test1.out: $(objpfx)/libnss_test1.so$(libnss_test1.so-version)
+
+LDLIBS-tst-nss-files = -ldl
diff --git a/nss/Versions b/nss/Versions
index f8ababc..ff7dd29 100644
--- a/nss/Versions
+++ b/nss/Versions
@@ -100,6 +100,7 @@ libnss_files {
     _nss_files_initgroups_dyn;
 
     _nss_files_init;
+    _nss_files_set_config;
   }
 }
 
diff --git a/nss/nss_db/db-XXX.c b/nss/nss_db/db-XXX.c
index 314edc9..5db2711 100644
--- a/nss/nss_db/db-XXX.c
+++ b/nss/nss_db/db-XXX.c
@@ -39,7 +39,8 @@
 #define ENTNAME_r	CONCAT(ENTNAME,_r)
 
 #include <paths.h>
-#define	DBFILE		_PATH_VARDB DATABASE ".db"
+#define DBFILE_(name)	_PATH_VARDB #name ".db"
+#define	DBFILE		DBFILE_(name)
 
 #ifdef NEED_H_ERRNO
 # define H_ERRNO_PROTO	, int *herrnop
diff --git a/nss/nss_files.h b/nss/nss_files.h
new file mode 100644
index 0000000..f700c22
--- /dev/null
+++ b/nss/nss_files.h
@@ -0,0 +1,51 @@
+/* Internal interfaces of the nss_files service module.
+   Copyright (C) 2015 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
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef NSS_FILES_H
+#define NSS_FILES_H
+
+struct nss_files_config
+{
+  const char *path_ethers;
+  const char *path_group;
+  const char *path_gshadow;
+  const char *path_hosts;
+  const char *path_netgroup;
+  const char *path_networks;
+  const char *path_passwd;
+  const char *path_protocols;
+  const char *path_publickey;
+  const char *path_rpc;
+  const char *path_services;
+  const char *path_shadow;
+};
+
+/* Internal configuration setting.  Only available within the DSO.  */
+extern struct nss_files_config _nss_files_config attribute_hidden;
+
+/* Internal access to the file name based on the database name.  */
+#define NSS_FILES_DATABASE_(name) _nss_files_config.path_##name
+#define NSS_FILES_DATABASE(name) NSS_FILES_DATABASE_ (name)
+
+/* Internal function to set _nss_files_config.  Only copies the
+   struct, but not the value of the strings.  Outside the DSO, this
+   function has to be looked up with dlsym.  */
+void _nss_files_set_config (const struct nss_files_config *)
+  internal_function;
+
+#endif /* NSS_FILES_H */
diff --git a/nss/nss_files/files-XXX.c b/nss/nss_files/files-XXX.c
index cca4322..63b0c02 100644
--- a/nss/nss_files/files-XXX.c
+++ b/nss/nss_files/files-XXX.c
@@ -22,6 +22,7 @@
 #include <fcntl.h>
 #include <libc-lock.h>
 #include "nsswitch.h"
+#include <nss_files.h>
 
 #include <kernel-features.h>
 
@@ -29,7 +30,7 @@
 
    ENTNAME -- database name of the structure and functions (hostent, pwent).
    STRUCTURE -- struct name, define only if not ENTNAME (passwd, group).
-   DATABASE -- string of the database file's name ("hosts", "passwd").
+   DATABASE -- name of the database file's name ("hosts", "passwd").
 
    NEED_H_ERRNO - defined iff an arg `int *herrnop' is used.
 
@@ -38,7 +39,7 @@
 
 #define ENTNAME_r	CONCAT(ENTNAME,_r)
 
-#define DATAFILE	"/etc/" DATABASE
+#define DATAFILE	NSS_FILES_DATABASE (DATABASE)
 
 #ifdef NEED_H_ERRNO
 # include <netdb.h>
diff --git a/nss/nss_files/files-config.c b/nss/nss_files/files-config.c
new file mode 100644
index 0000000..68f9907
--- /dev/null
+++ b/nss/nss_files/files-config.c
@@ -0,0 +1,42 @@
+/* Files configuration for nss_files.
+   Copyright (C) 2015 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <nss_files.h>
+
+struct nss_files_config _nss_files_config =
+  {
+    .path_ethers = "/etc/ethers",
+    .path_group = "/etc/group",
+    .path_gshadow = "/etc/gshadow",
+    .path_hosts = "/etc/hosts",
+    .path_netgroup = "/etc/netgroup",
+    .path_networks = "/etc/networks",
+    .path_passwd = "/etc/passwd",
+    .path_protocols = "/etc/protocols",
+    .path_publickey = "/etc/publickey",
+    .path_rpc = "/etc/rpc",
+    .path_services = "/etc/services",
+    .path_shadow = "/etc/shadow",
+  };
+
+void
+internal_function
+_nss_files_set_config (const struct nss_files_config *config)
+{
+  _nss_files_config = *config;
+}
diff --git a/nss/nss_files/files-ethers.c b/nss/nss_files/files-ethers.c
index 47fa784..ca96799 100644
--- a/nss/nss_files/files-ethers.c
+++ b/nss/nss_files/files-ethers.c
@@ -22,7 +22,7 @@
 struct etherent_data {};
 
 #define ENTNAME		etherent
-#define DATABASE	"ethers"
+#define DATABASE	ethers
 #include "files-parse.c"
 LINE_PARSER
 ("#",
diff --git a/nss/nss_files/files-grp.c b/nss/nss_files/files-grp.c
index 42155bc..6a631ef 100644
--- a/nss/nss_files/files-grp.c
+++ b/nss/nss_files/files-grp.c
@@ -20,7 +20,7 @@
 
 #define STRUCTURE	group
 #define ENTNAME		grent
-#define DATABASE	"group"
+#define DATABASE	group
 struct grent_data {};
 
 /* Our parser function is already defined in fgetgrent.c, so use that.
diff --git a/nss/nss_files/files-hosts.c b/nss/nss_files/files-hosts.c
index 4117458..aad8d90 100644
--- a/nss/nss_files/files-hosts.c
+++ b/nss/nss_files/files-hosts.c
@@ -30,7 +30,7 @@
 
 
 #define ENTNAME		hostent
-#define DATABASE	"hosts"
+#define DATABASE	hosts
 #define NEED_H_ERRNO
 
 #define EXTRA_ARGS	 , af, flags
diff --git a/nss/nss_files/files-key.c b/nss/nss_files/files-key.c
index 2d64744..60fb0f4 100644
--- a/nss/nss_files/files-key.c
+++ b/nss/nss_files/files-key.c
@@ -23,8 +23,9 @@
 #include <rpc/key_prot.h>
 #include <rpc/des_crypt.h>
 #include "nsswitch.h"
+#include <nss_files.h>
 
-#define DATAFILE "/etc/publickey"
+#define DATAFILE _nss_files_config.path_publickey
 
 
 static enum nss_status
diff --git a/nss/nss_files/files-netgrp.c b/nss/nss_files/files-netgrp.c
index 8886c26..c1c3f00 100644
--- a/nss/nss_files/files-netgrp.c
+++ b/nss/nss_files/files-netgrp.c
@@ -26,8 +26,9 @@
 #include <string.h>
 #include "nsswitch.h"
 #include "netgroup.h"
+#include <nss_files.h>
 
-#define DATAFILE	"/etc/netgroup"
+#define DATAFILE	_nss_files_config.path_netgroup
 
 libnss_files_hidden_proto (_nss_files_endnetgrent)
 
diff --git a/nss/nss_files/files-network.c b/nss/nss_files/files-network.c
index 46e9945..d9868c4 100644
--- a/nss/nss_files/files-network.c
+++ b/nss/nss_files/files-network.c
@@ -22,7 +22,7 @@
 #include <stdint.h>
 
 #define ENTNAME		netent
-#define DATABASE	"networks"
+#define DATABASE	networks
 #define NEED_H_ERRNO
 
 struct netent_data {};
diff --git a/nss/nss_files/files-parse.c b/nss/nss_files/files-parse.c
index 9911633..836a0c9 100644
--- a/nss/nss_files/files-parse.c
+++ b/nss/nss_files/files-parse.c
@@ -26,7 +26,7 @@
 
    ENTNAME -- database name of the structure and functions (hostent, pwent).
    STRUCTURE -- struct name, define only if not ENTNAME (passwd, group).
-   DATABASE -- string of the database file's name ("hosts", "passwd").
+   DATABASE -- name of the database (hosts, passwd).
 
    ENTDATA -- if defined, `struct ENTDATA' is used by the parser to store
 	      things pointed to by the resultant `struct STRUCTURE'.
diff --git a/nss/nss_files/files-proto.c b/nss/nss_files/files-proto.c
index 370266a..e2099ef 100644
--- a/nss/nss_files/files-proto.c
+++ b/nss/nss_files/files-proto.c
@@ -20,7 +20,7 @@
 
 
 #define ENTNAME		protoent
-#define DATABASE	"protocols"
+#define DATABASE	protocols
 
 struct protoent_data {};
 
diff --git a/nss/nss_files/files-pwd.c b/nss/nss_files/files-pwd.c
index 84ce5bb..4936624 100644
--- a/nss/nss_files/files-pwd.c
+++ b/nss/nss_files/files-pwd.c
@@ -20,7 +20,7 @@
 
 #define STRUCTURE	passwd
 #define ENTNAME		pwent
-#define DATABASE	"passwd"
+#define DATABASE	passwd
 struct pwent_data {};
 
 /* Our parser function is already defined in fgetpwent_r.c, so use that
diff --git a/nss/nss_files/files-rpc.c b/nss/nss_files/files-rpc.c
index 34f6aec..3415ca6 100644
--- a/nss/nss_files/files-rpc.c
+++ b/nss/nss_files/files-rpc.c
@@ -20,7 +20,7 @@
 
 
 #define ENTNAME		rpcent
-#define DATABASE	"rpc"
+#define DATABASE	rpc
 
 struct rpcent_data {};
 
diff --git a/nss/nss_files/files-service.c b/nss/nss_files/files-service.c
index 7fccbdb..0bbf046 100644
--- a/nss/nss_files/files-service.c
+++ b/nss/nss_files/files-service.c
@@ -21,7 +21,7 @@
 
 
 #define ENTNAME		servent
-#define DATABASE	"services"
+#define DATABASE	services
 
 struct servent_data {};
 
diff --git a/nss/nss_files/files-sgrp.c b/nss/nss_files/files-sgrp.c
index ac74324..538e417 100644
--- a/nss/nss_files/files-sgrp.c
+++ b/nss/nss_files/files-sgrp.c
@@ -20,7 +20,7 @@
 
 #define STRUCTURE	sgrp
 #define ENTNAME		sgent
-#define DATABASE	"gshadow"
+#define DATABASE	gshadow
 struct sgent_data {};
 
 /* Our parser function is already defined in sgetspent_r.c, so use that
diff --git a/nss/nss_files/files-spwd.c b/nss/nss_files/files-spwd.c
index a255454..6436ee1 100644
--- a/nss/nss_files/files-spwd.c
+++ b/nss/nss_files/files-spwd.c
@@ -20,7 +20,7 @@
 
 #define STRUCTURE	spwd
 #define ENTNAME		spent
-#define DATABASE	"shadow"
+#define DATABASE	shadow
 struct spent_data {};
 
 /* Our parser function is already defined in sgetspent_r.c, so use that
diff --git a/nss/test-data/files/netgroup b/nss/test-data/files/netgroup
new file mode 100644
index 0000000..b367576
--- /dev/null
+++ b/nss/test-data/files/netgroup
@@ -0,0 +1,6 @@
+netgr gr1 gr2 gr3
+gr1 (,u1,)
+gr2 (,u2,)
+gr3 gr5 gr6
+gr5
+gr6 (,u3,)
diff --git a/nss/test-data/files/passwd b/nss/test-data/files/passwd
new file mode 100644
index 0000000..873152a
--- /dev/null
+++ b/nss/test-data/files/passwd
@@ -0,0 +1 @@
+root:x:0:0:glibc root test user:/root:/bin/bash
diff --git a/nss/tst-nss-files.c b/nss/tst-nss-files.c
new file mode 100644
index 0000000..8d3cb75
--- /dev/null
+++ b/nss/tst-nss-files.c
@@ -0,0 +1,211 @@
+/* Test nss_files parsing.
+   Copyright (C) 2015 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <gnu/lib-names.h>
+#include <netdb.h>
+#include <nss_files.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static void
+configure_nss (void)
+{
+  __nss_configure_lookup ("passwd", "files");
+}
+
+static char *
+file_name_for_database (const char *name)
+{
+  char *curdir = realpath (".", NULL);
+  if (curdir == NULL)
+    {
+      printf ("error: realpath (\".\", NULL): %m\n");
+      exit (1);
+    }
+
+  char *result;
+  int ret = asprintf (&result, "%s/test-data/files/%s", curdir, name);
+  if (ret < 0)
+    {
+      printf ("error: asprintf: %m\n");
+      exit (1);
+    }
+  return result;
+}
+
+static struct nss_files_config test_config;
+
+static void
+init_test_config (void)
+{
+  test_config = (struct nss_files_config)
+    {
+      .path_ethers = file_name_for_database ("ethers"),
+      .path_group = file_name_for_database ("group"),
+      .path_gshadow = file_name_for_database ("gshadow"),
+      .path_hosts = file_name_for_database ("hosts"),
+      .path_netgroup = file_name_for_database ("netgroup"),
+      .path_networks = file_name_for_database ("networks"),
+      .path_passwd = file_name_for_database ("passwd"),
+      .path_protocols = file_name_for_database ("protocols"),
+      .path_publickey = file_name_for_database ("publickey"),
+      .path_rpc = file_name_for_database ("rpc"),
+      .path_services = file_name_for_database ("services"),
+      .path_shadow = file_name_for_database ("shadow"),
+    };
+}
+
+static bool errors;
+
+static int
+sort_strings (const void *a, const void *b)
+{
+  return strcmp (*(const char **) a, *(const char **) b);
+}
+
+
+/* Test case from bug 17363: getnetgrent does not return the complete
+   list of entries if one of the netgroups in its definition tree is
+   empty.  */
+static void
+test_bug17363 (void)
+{
+  int ret = setnetgrent ("netgr");
+  if (ret != 1)
+    {
+      printf ("error: setnetgrent (\"netgr\"): %m\n");
+      errors = true;
+    }
+
+#define COUNT 3
+  static const char *const expected[COUNT] =
+    {
+      ",u1,",
+      ",u2,",
+      ",u3,",
+    };
+
+  char *actual[COUNT] = {};
+
+  for (size_t i = 0; ; ++i)
+    {
+      char *host;
+      char *user;
+      char *domain;
+      ret = getnetgrent (&host, &user, &domain);
+      if (ret != 1)
+        {
+          if (i == COUNT)
+            break;
+          printf ("error: getnetgrent (%zu)\n", i);
+          errors = true;
+          break;
+        }
+      if (i == COUNT)
+        {
+          printf ("error: getnetgrent (%zu): unexpected success\n", i);
+          errors = true;
+          break;
+        }
+
+      if (host == NULL)
+        host = (char *) "";
+      if (user == NULL)
+        user = (char *) "";
+      if (domain == NULL)
+        domain = (char *) "";
+
+      ret = asprintf (actual + i, "%s,%s,%s", host, user, domain);
+      if (ret < 0)
+        {
+          printf ("error: asprintf: %m\n");
+          exit (1);
+        }
+    }
+
+  endnetgrent ();
+
+  if (!errors)
+    {
+      qsort (actual, COUNT, sizeof (actual[0]), sort_strings);
+      for (size_t i = 0; i < COUNT; ++i)
+        {
+          if (strcmp (actual[i], expected[i]) != 0)
+            {
+              printf ("error: getnetgrent (%zu): \"%s\" instead of \"%s\"\n",
+                      i, actual[i], expected[i]);
+              errors = true;
+            }
+          free (actual[i]);
+        }
+    }
+#undef COUNT
+}
+
+static int
+do_test (void)
+{
+  configure_nss ();
+
+  void *handle = dlopen (LIBNSS_FILES_SO, RTLD_LAZY);
+  if (handle == NULL)
+    {
+      printf ("error: cannot open " LIBNSS_FILES_SO ": %s\n",
+              dlerror ());
+      return 1;
+    }
+
+  __typeof__ (&_nss_files_set_config) config_function =
+    dlsym (handle, "_nss_files_set_config");
+  if (config_function == NULL)
+    {
+      printf ("error: cannot find configuration function symbol: %s\n",
+              dlerror ());
+      return 1;
+    }
+
+  init_test_config ();
+  config_function (&test_config);
+
+  /* Check that the reconfiguration to the test files was
+     successful.  */
+  const struct passwd *pw = getpwuid (0);
+  if (pw == NULL)
+    {
+      printf ("error: root user lookup failed: %m\n");
+      return 1;
+    }
+  if (strcmp (pw->pw_gecos, "glibc root test user") != 0)
+    {
+      printf ("error: incorrect GECOS field for root user: \"%s\n\"",
+              pw->pw_gecos);
+      return 1;
+    }
+
+  test_bug17363 ();
+  dlclose (handle);
+
+  return errors;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
