public inbox for glibc-cvs@sourceware.org
help / color / mirror / Atom feed
* [glibc/maskray/grte] Integrate nss_{borg, cache} local changes from glibc-2.18 to 2.19
@ 2021-08-27 23:23 Fangrui Song
  0 siblings, 0 replies; only message in thread
From: Fangrui Song @ 2021-08-27 23:23 UTC (permalink / raw)
  To: glibc-cvs

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=eb0025167791355d35cd52776d3976db03e0530e

commit eb0025167791355d35cd52776d3976db03e0530e
Author: Paul Pluzhnikov <ppluzhnikov@google.com>
Date:   Fri Feb 28 12:54:06 2014 -0800

    Integrate nss_{borg,cache} local changes from glibc-2.18 to 2.19

Diff:
---
 include/grp.h             |   1 +
 include/pwd.h             |   2 +
 include/shadow.h          |   1 +
 nss/Makefile              |  12 +-
 nss/Versions              |  29 ++
 nss/function.def          |  10 +
 nss/nss_borg/borg-pwd.c   | 171 ++++++++
 nss/nss_cache/nss_cache.c | 978 ++++++++++++++++++++++++++++++++++++++++++++++
 nss/nss_cache/nss_cache.h |  65 +++
 shlib-versions            |   2 +
 10 files changed, 1268 insertions(+), 3 deletions(-)

diff --git a/include/grp.h b/include/grp.h
index 871701adbe..16dd8bd2e7 100644
--- a/include/grp.h
+++ b/include/grp.h
@@ -53,6 +53,7 @@ extern enum nss_status _nss_ ## service ##_initgroups_dyn		   \
 			long int *size, gid_t **groupsp, long int limit,   \
 			int *errnop);
 
+DECLARE_NSS_PROTOTYPES (cache)
 DECLARE_NSS_PROTOTYPES (compat)
 DECLARE_NSS_PROTOTYPES (files)
 DECLARE_NSS_PROTOTYPES (hesiod)
diff --git a/include/pwd.h b/include/pwd.h
index fc995065d9..de8272cdd9 100644
--- a/include/pwd.h
+++ b/include/pwd.h
@@ -45,8 +45,10 @@ extern enum nss_status _nss_ ## service ##_getpwent_r			\
 		       (struct passwd *result, char *buffer,		\
 			size_t buflen, int *errnop);
 
+DECLARE_NSS_PROTOTYPES (cache)
 DECLARE_NSS_PROTOTYPES (compat)
 DECLARE_NSS_PROTOTYPES (files)
+DECLARE_NSS_PROTOTYPES (borg)
 DECLARE_NSS_PROTOTYPES (hesiod)
 DECLARE_NSS_PROTOTYPES (nis)
 DECLARE_NSS_PROTOTYPES (nisplus)
diff --git a/include/shadow.h b/include/shadow.h
index 366ea83482..3b7bf777d9 100644
--- a/include/shadow.h
+++ b/include/shadow.h
@@ -41,6 +41,7 @@ extern enum nss_status _nss_ ## service ## _getspnam_r			\
 		       (const char *name, struct spwd *pwd,		\
 			char *buffer, size_t buflen, int *errnop);
 
+DECLARE_NSS_PROTOTYPES (cache)
 DECLARE_NSS_PROTOTYPES (compat)
 DECLARE_NSS_PROTOTYPES (files)
 DECLARE_NSS_PROTOTYPES (hesiod)
diff --git a/nss/Makefile b/nss/Makefile
index a5cd2aacae..375f060b65 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -73,7 +73,7 @@ tests += tst-cancel-getpwuid_r
 endif
 
 # Specify rules for the nss_* modules.  We have some services.
-services		:= files db compat
+services		:= files db compat borg cache
 
 extra-libs		= $(services:%=libnss_%)
 # These libraries will be built in the `others' pass rather than
@@ -87,6 +87,8 @@ vpath %.c $(subdir-dirs) ../locale/programs ../intl
 
 libnss_files-routines	:= $(addprefix files-,$(databases)) \
 			   files-initgroups files-init
+libnss_borg-routines	:= borg-pwd
+libnss_cache-routines	:= nss_cache
 
 libnss_db-dbs		:= $(addprefix db-,\
 				       $(filter-out hosts network key alias,\
@@ -104,10 +106,14 @@ install-others		+= $(inst_vardbdir)/Makefile
 # Build static module into libc if requested
 libnss_files-inhibit-o	= $(filter-out .os,$(object-suffixes))
 libnss_db-inhibit-o	= $(filter-out .os,$(object-suffixes))
+libnss_borg-inhibit-o	= $(filter-out .os,$(object-suffixes))
+libnss_cache-inhibit-o	= $(filter-out .os,$(object-suffixes))
 libnss_compat-inhibit-o	= $(filter-out .os,$(object-suffixes))
 ifeq ($(build-static-nss),yes)
-routines                += $(libnss_files-routines)
-static-only-routines    += $(libnss_files-routines)
+routines                += $(libnss_files-routines) $(libnss_borg-routines) \
+                           $(libnss_cache-routines)
+static-only-routines    += $(libnss_files-routines) $(libnss_borg-routines) \
+                           $(libnss_cache-routines)
 tests-static		+= tst-nss-static
 endif
 extra-test-objs		+= nss_test1.os nss_test2.os
diff --git a/nss/Versions b/nss/Versions
index db8c887720..f472d64d59 100644
--- a/nss/Versions
+++ b/nss/Versions
@@ -174,3 +174,32 @@ libnss_compat {
     _nss_compat_initgroups_dyn;
   }
 }
+
+libnss_borg {
+  GLIBC_PRIVATE {
+    _nss_borg_setpwent;
+    _nss_borg_endpwent;
+    _nss_borg_getpwent_r;
+    _nss_borg_getpwnam_r;
+    _nss_borg_getpwuid_r;
+  }
+}
+
+libnss_cache {
+  GLIBC_PRIVATE {
+    _nss_cache_setpwent;
+    _nss_cache_endpwent;
+    _nss_cache_getpwent_r;
+    _nss_cache_getpwuid_r;
+    _nss_cache_getpwnam_r;
+    _nss_cache_setgrent;
+    _nss_cache_endgrent;
+    _nss_cache_getgrent_r;
+    _nss_cache_getgrgid_r;
+    _nss_cache_getgrnam_r;
+    _nss_cache_setspent;
+    _nss_cache_endspent;
+    _nss_cache_getspent_r;
+    _nss_cache_getspnam_r;
+  }
+}
diff --git a/nss/function.def b/nss/function.def
index 1a38d5a852..6464a921e7 100644
--- a/nss/function.def
+++ b/nss/function.def
@@ -31,6 +31,9 @@ DEFINE_ENT (files, ether)
 DEFINE_ENT (files, gr)
 DEFINE_GET (files, grgid)
 DEFINE_GET (files, grnam)
+DEFINE_ENT (cache, gr)
+DEFINE_GET (cache, grgid)
+DEFINE_GET (cache, grnam)
 
 /* hosts */
 DEFINE_ENT (files, host)
@@ -62,6 +65,11 @@ DEFINE_GETBY (files, proto, number)
 DEFINE_ENT (files, pw)
 DEFINE_GET (files, pwnam)
 DEFINE_GET (files, pwuid)
+DEFINE_GET (borg, pwnam)  /* /etc/passwd2 */
+DEFINE_GET (borg, pwuid)
+DEFINE_ENT (cache, pw)
+DEFINE_GET (cache, pwnam)
+DEFINE_GET (cache, pwuid)
 
 /* rpc */
 DEFINE_ENT (files, rpc)
@@ -76,3 +84,5 @@ DEFINE_GETBY (files, serv, port)
 /* shadow */
 DEFINE_ENT (files, sp)
 DEFINE_GET (files, spnam)
+DEFINE_ENT (cache, sp)
+DEFINE_GET (cache, spnam)
diff --git a/nss/nss_borg/borg-pwd.c b/nss/nss_borg/borg-pwd.c
new file mode 100644
index 0000000000..dc8206a489
--- /dev/null
+++ b/nss/nss_borg/borg-pwd.c
@@ -0,0 +1,171 @@
+// Copyright 2004 Google Inc.
+// Author: Paul Menage
+
+// An NSS module that extends local user account lookup to the file /etc/passwd.borg
+
+#include <stdio.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <nss.h>
+#include <errno.h>
+#include <string.h>
+
+#ifdef NSSBORG_STANDALONE
+#include <pthread.h>
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+#define NSSBORG_LOCK  pthread_mutex_lock(&mutex)
+#define NSSBORG_UNLOCK pthread_mutex_unlock(&mutex)
+#else
+#include <bits/libc-lock.h>
+__libc_lock_define_initialized (static, lock)
+#define NSSBORG_LOCK  __libc_lock_lock (lock)
+#define NSSBORG_UNLOCK  __libc_lock_unlock (lock);
+#endif
+
+static FILE *f;
+
+#define DEBUG(fmt, ...)
+
+// _nss_borg_setpwent_locked()
+// Internal setup routine
+
+static enum nss_status _nss_borg_setpwent_locked(void) {
+
+  DEBUG("Opening passwd.borg\n");
+  f = fopen("/etc/passwd.borg", "r");
+
+  if (f) {
+    return NSS_STATUS_SUCCESS;
+  } else {
+    return NSS_STATUS_UNAVAIL;
+  }
+}
+
+// _nss_borg_setpwent()
+// Called by NSS to open the passwd file
+// Oddly, NSS passes a boolean saying whether to keep the database file open; ignore it
+
+enum nss_status _nss_borg_setpwent(int stayopen) {
+  enum nss_status ret;
+  NSSBORG_LOCK;
+  ret = _nss_borg_setpwent_locked();
+  NSSBORG_UNLOCK;
+  return ret;
+}
+
+// _nss_borg_endpwent_locked()
+// Internal close routine
+
+static enum nss_status _nss_borg_endpwent_locked(void) {
+
+  DEBUG("Closing passwd.borg\n");
+  if (f) {
+    fclose(f);
+    f = NULL;
+  }
+  return NSS_STATUS_SUCCESS;
+}
+
+// _nss_borg_endpwent()
+// Called by NSS to close the passwd file
+
+enum nss_status _nss_borg_endpwent() {
+  enum nss_status ret;
+  NSSBORG_LOCK;
+  ret = _nss_borg_endpwent_locked();
+  NSSBORG_UNLOCK;
+  return ret;
+}
+
+// _nss_borg_getpwent_r_locked()
+// Called internally to return the next entry from the passwd file
+
+static enum nss_status _nss_borg_getpwent_r_locked(struct passwd *result,
+                                                   char *buffer, size_t buflen,
+                                                   int *errnop) {
+
+  enum nss_status ret;
+
+  if (fgetpwent_r(f, result, buffer, buflen, &result) == 0) {
+    DEBUG("Returning user %d:%s\n", result->pw_uid, result->pw_name);
+    ret = NSS_STATUS_SUCCESS;
+  } else {
+    *errnop = errno;
+    switch (*errnop) {
+      case ERANGE:
+        ret = NSS_STATUS_TRYAGAIN;
+        break;
+      case ENOENT:
+      default:
+        ret = NSS_STATUS_NOTFOUND;
+    }
+  }
+
+  return ret;
+}
+
+// _nss_borg_getpwent_r()
+// Called by NSS (I think) to look up next entry in passwd file
+enum nss_status _nss_borg_getpwent_r(struct passwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  enum nss_status ret;
+  NSSBORG_LOCK;
+  ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop);
+  NSSBORG_UNLOCK;
+  return ret;
+}
+
+// _nss_borg_getpwuid_r()
+// Find a user account by uid
+
+enum nss_status _nss_borg_getpwuid_r(uid_t uid, struct passwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+
+  enum nss_status ret;
+
+  NSSBORG_LOCK;
+  ret = _nss_borg_setpwent_locked();
+  DEBUG("Looking for uid %d\n", uid);
+
+  if (ret == NSS_STATUS_SUCCESS) {
+    while ((ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop))
+           == NSS_STATUS_SUCCESS) {
+      if (result->pw_uid == uid)
+        break;
+    }
+  }
+
+  _nss_borg_endpwent_locked();
+  NSSBORG_UNLOCK;
+
+  return ret;
+}
+
+// _nss_borg_getpwnam_r()
+// Find a user account by name
+
+enum nss_status _nss_borg_getpwnam_r(const char *name, struct passwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+
+  enum nss_status ret;
+
+  NSSBORG_LOCK;
+  ret = _nss_borg_setpwent_locked();
+  DEBUG("Looking for user %s\n", name);
+
+  if (ret == NSS_STATUS_SUCCESS) {
+    while ((ret = _nss_borg_getpwent_r_locked(result, buffer, buflen, errnop))
+           == NSS_STATUS_SUCCESS) {
+      if (!strcmp(result->pw_name, name))
+        break;
+    }
+  }
+
+  _nss_borg_endpwent_locked();
+  NSSBORG_UNLOCK;
+
+  return ret;
+}
diff --git a/nss/nss_cache/nss_cache.c b/nss/nss_cache/nss_cache.c
new file mode 100644
index 0000000000..6613dbbeea
--- /dev/null
+++ b/nss/nss_cache/nss_cache.c
@@ -0,0 +1,978 @@
+/* Copyright 2009 Google Inc.
+ *
+ * This 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.
+ *
+ * This 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA
+ */
+
+/* An NSS module which adds supports for file maps with a trailing .cache
+ * suffix (/etc/passwd.cache, /etc/group.cache, and /etc/shadow.cache)
+ */
+
+#include "nss_cache.h"
+
+// Locking implementation: use pthreads.
+#include <pthread.h>
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+#pragma weak pthread_mutex_lock
+#define NSS_CACHE_LOCK()    do { \
+    if (&pthread_mutex_lock != NULL) pthread_mutex_lock(&mutex); \
+  } while (0)
+
+#pragma weak pthread_mutex_unlock
+#define NSS_CACHE_UNLOCK()  do { \
+    if (&pthread_mutex_unlock != NULL) pthread_mutex_unlock(&mutex); \
+  } while (0)
+
+static FILE *p_file = NULL;
+static FILE *g_file = NULL;
+static FILE *s_file = NULL;
+static char p_filename[NSS_CACHE_PATH_LENGTH] = "/etc/passwd.cache";
+static char g_filename[NSS_CACHE_PATH_LENGTH] = "/etc/group.cache";
+static char s_filename[NSS_CACHE_PATH_LENGTH] = "/etc/shadow.cache";
+
+/* Common return code routine for all *ent_r_locked functions.
+ * We need to return TRYAGAIN if the underlying files guy raises ERANGE,
+ * so that our caller knows to try again with a bigger buffer.
+ */
+
+static inline enum nss_status _nss_cache_ent_bad_return_code(int errnoval) {
+  enum nss_status ret;
+
+  switch (errnoval) {
+    case ERANGE:
+      DEBUG("ERANGE: Try again with a bigger buffer\n");
+      ret = NSS_STATUS_TRYAGAIN;
+      break;
+    case ENOENT:
+    default:
+      DEBUG("ENOENT or default case: Not found\n");
+      ret = NSS_STATUS_NOTFOUND;
+  };
+  return ret;
+}
+
+//
+// Binary search routines below here
+//
+
+// _nss_cache_bsearch_lookup()
+// Binary search through a sorted nss file for a single record.
+
+enum nss_status _nss_cache_bsearch_lookup(FILE *file,
+                                         struct nss_cache_args *args,
+                                         int *errnop) {
+  enum nss_cache_match (*lookup)(
+                                  FILE *,
+                                  struct nss_cache_args *
+                                 ) = args->lookup_function;
+  long min = 0;
+  long max;
+  long pos;
+
+  // get the size of the file
+  if (fseek(file, 0, SEEK_END) != 0) {
+    DEBUG("fseek fail\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+  max = ftell(file);
+
+  // binary search until we are within 100 chars of the right line
+  while (min + 100 < max) {
+    pos = (min + max) / 2;
+
+    if (fseek(file, pos, SEEK_SET) != 0) {
+      DEBUG("fseek fail\n");
+      return NSS_STATUS_UNAVAIL;
+    }
+
+    // scan forward to the start of the next line.
+    for (;;) {
+      int c = getc_unlocked(file);
+      if (c == EOF) break;
+      ++pos;
+      if (c == '\n') break;
+    }
+
+    // break if we've stopped making progress in this loop (long lines)
+    if (pos <= min || pos >= max) {
+      break;
+    }
+
+    // see if this line matches
+    switch (lookup(file, args)) {
+      case NSS_CACHE_EXACT:
+        return NSS_STATUS_SUCCESS;  // done!
+      case NSS_CACHE_HIGH:
+        max = pos;
+        continue;  // search again
+      case NSS_CACHE_LOW:
+        min = pos;
+        continue;  // search again
+      case NSS_CACHE_ERROR:
+        if (errno == ERANGE) {
+          // let the caller retry
+          *errnop = errno;
+          return _nss_cache_ent_bad_return_code(*errnop);
+        }
+        DEBUG("expected error %s [errno=%d] from lookup function\n",
+              strerror(errno), errno);
+        return NSS_STATUS_UNAVAIL;
+    }
+  }
+
+  // fall back on a linear search in the remaining space
+  DEBUG("Switching to linear scan\n");
+  pos = min - 100;  // back 100, might be in the middle of the right line
+  if (fseek(file, pos, SEEK_SET) != 0) {
+    DEBUG("fseek fail\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+
+  while (pos < max) {
+    switch (lookup(file, args)) {
+      case NSS_CACHE_EXACT:
+        return NSS_STATUS_SUCCESS;
+      case NSS_CACHE_HIGH:
+      case NSS_CACHE_LOW:
+        pos = ftell(file);
+        continue;
+      case NSS_CACHE_ERROR:
+        if (errno == ERANGE) {
+          // let the caller retry
+          *errnop = errno;
+          return _nss_cache_ent_bad_return_code(*errnop);
+        }
+        break;
+    }
+    break;
+  }
+
+  return NSS_STATUS_NOTFOUND;
+}
+
+// _nss_cache_bsearch()
+// If a sorted nss file is present, attempt a binary search on it.
+
+enum nss_status _nss_cache_bsearch(struct nss_cache_args *args, int *errnop) {
+  FILE *file = NULL;
+  struct stat system_file;
+  struct stat sorted_file;
+  enum nss_status ret;
+
+  file =  fopen(args->sorted_filename, "r");
+  if (file == NULL) {
+    DEBUG("error opening %s\n", args->sorted_filename);
+    return NSS_STATUS_UNAVAIL;
+  }
+
+  // if the sorted file is older than the system file, do not risk stale
+  // data and abort
+  // TODO(vasilios):  should be a compile or runtime option
+  if (stat(args->system_filename, &system_file) != 0) {
+    DEBUG("failed to stat %s\n", args->system_filename);
+    fclose(file);
+    return NSS_STATUS_UNAVAIL;
+  }
+  if (fstat(fileno(file), &sorted_file) != 0) {
+    DEBUG("failed to stat %s\n", args->sorted_filename);
+    fclose(file);
+    return NSS_STATUS_UNAVAIL;
+  }
+  if (difftime(system_file.st_mtime, sorted_file.st_mtime) > 0) {
+    DEBUG("%s may be stale, aborting lookup\n", args->sorted_filename);
+    fclose(file);
+    return NSS_STATUS_UNAVAIL;
+  }
+
+  ret = _nss_cache_bsearch_lookup(file, args, errnop);
+
+  fclose(file);
+  return ret;
+
+}
+
+//
+// Routines for passwd map defined below here
+//
+
+// _nss_cache_setpwent_path()
+// Helper function for testing
+
+extern char* _nss_cache_setpwent_path(const char *path) {
+
+  DEBUG("%s %s\n", "Setting p_filename to", path);
+  return strncpy(p_filename, path, NSS_CACHE_PATH_LENGTH - 1);
+
+}
+
+// _nss_cache_pwuid_wrap()
+// Internal wrapper for binary searches, using uid-specific calls.
+
+static enum nss_cache_match _nss_cache_pwuid_wrap(FILE *file,
+                                                 struct nss_cache_args *args) {
+  struct passwd *result = args->lookup_result;
+  uid_t *uid = args->lookup_value;
+
+  if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+    if (result->pw_uid == *uid) {
+      DEBUG("SUCCESS: found user %d:%s\n", result->pw_uid, result->pw_name);
+      return NSS_CACHE_EXACT;
+    }
+    DEBUG("Failed match at uid %d\n", result->pw_uid);
+    if (result->pw_uid > *uid) {
+      return NSS_CACHE_HIGH;
+    } else {
+      return NSS_CACHE_LOW;
+    }
+  }
+
+  return NSS_CACHE_ERROR;
+}
+
+// _nss_cache_pwnam_wrap()
+// Internal wrapper for binary searches, using username-specific calls.
+
+static enum nss_cache_match _nss_cache_pwnam_wrap(FILE *file,
+                                                 struct nss_cache_args *args) {
+  struct passwd *result = args->lookup_result;
+  char *name = args->lookup_value;
+  int ret;
+
+  if (fgetpwent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+    ret = strcoll(result->pw_name, name);
+    if (ret == 0) {
+      DEBUG("SUCCESS: found user %s\n", result->pw_name);
+      return NSS_CACHE_EXACT;
+    }
+    DEBUG("Failed match at name %s\n", result->pw_name);
+    if (ret > 0) {
+      return NSS_CACHE_HIGH;
+    } else {
+      return NSS_CACHE_LOW;
+    }
+  }
+
+  return NSS_CACHE_ERROR;
+}
+
+// _nss_cache_setpwent_locked()
+// Internal setup routine
+
+static enum nss_status _nss_cache_setpwent_locked(void) {
+
+  DEBUG("%s %s\n", "Opening", p_filename);
+  p_file = fopen(p_filename, "r");
+
+  if (p_file) {
+    return NSS_STATUS_SUCCESS;
+  } else {
+    return NSS_STATUS_UNAVAIL;
+  }
+}
+
+// _nss_cache_setpwent()
+// Called by NSS to open the passwd file
+// 'stayopen' parameter is ignored.
+
+enum nss_status _nss_cache_setpwent(int stayopen) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_setpwent_locked();
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_endpwent_locked()
+// Internal close routine
+
+static enum nss_status _nss_cache_endpwent_locked(void) {
+
+  DEBUG("Closing passwd.cache\n");
+  if (p_file) {
+    fclose(p_file);
+    p_file = NULL;
+  }
+  return NSS_STATUS_SUCCESS;
+}
+
+// _nss_cache_endpwent()
+// Called by NSS to close the passwd file
+
+enum nss_status _nss_cache_endpwent(void) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_endpwent_locked();
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_getpwent_r_locked()
+// Called internally to return the next entry from the passwd file
+
+static enum nss_status _nss_cache_getpwent_r_locked(struct passwd *result,
+                                                   char *buffer, size_t buflen,
+                                                   int *errnop) {
+  enum nss_status ret = NSS_STATUS_SUCCESS;
+
+  if (p_file == NULL) {
+    DEBUG("p_file == NULL, going to setpwent\n");
+    ret = _nss_cache_setpwent_locked();
+  }
+
+  if (ret == NSS_STATUS_SUCCESS) {
+    if (fgetpwent_r(p_file, result, buffer, buflen, &result) == 0) {
+      DEBUG("Returning user %d:%s\n", result->pw_uid, result->pw_name);
+    } else {
+      *errnop = errno;
+      ret = _nss_cache_ent_bad_return_code(*errnop);
+    }
+  }
+
+  return ret;
+}
+
+// _nss_cache_getpwent_r()
+// Called by NSS to look up next entry in passwd file
+
+enum nss_status _nss_cache_getpwent_r(struct passwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_getpwent_r_locked(result, buffer, buflen, errnop);
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_getpwuid_r()
+// Find a user account by uid
+
+enum nss_status _nss_cache_getpwuid_r(uid_t uid, struct passwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  char filename[NSS_CACHE_PATH_LENGTH];
+  struct nss_cache_args args;
+  enum nss_status ret;
+
+  strncpy(filename, p_filename, NSS_CACHE_PATH_LENGTH - 1);
+  if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 7) {
+    DEBUG("filename too long\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncat(filename, ".byuid", 6);
+
+  args.sorted_filename = filename;
+  args.system_filename = p_filename;
+  args.lookup_function = _nss_cache_pwuid_wrap;
+  args.lookup_value = &uid;
+  args.lookup_result = result;
+  args.buffer = buffer;
+  args.buflen = buflen;
+
+  DEBUG("Binary search for uid %d\n", uid);
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_bsearch(&args, errnop);
+
+  // TODO(vasilios): make this a runtime or compile-time option, as this slows
+  // down legitimate misses as the trade off for safety.
+  if (ret == NSS_STATUS_NOTFOUND) {
+    DEBUG("Binary search returned nothing.\n");
+    ret = NSS_STATUS_UNAVAIL;
+  }
+
+  if (ret == NSS_STATUS_UNAVAIL) {
+    DEBUG("Binary search failed, falling back to full linear search\n");
+    ret = _nss_cache_setpwent_locked();
+
+    if (ret == NSS_STATUS_SUCCESS) {
+      while ((ret = _nss_cache_getpwent_r_locked(result,
+                                                buffer,
+                                                buflen,
+                                                errnop))
+             == NSS_STATUS_SUCCESS) {
+        if (result->pw_uid == uid)
+          break;
+      }
+    }
+  }
+
+  _nss_cache_endpwent_locked();
+  NSS_CACHE_UNLOCK();
+
+  return ret;
+}
+
+// _nss_cache_getpwnam_r()
+// Find a user account by name
+
+enum nss_status _nss_cache_getpwnam_r(const char *name, struct passwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  char *pw_name;
+  char filename[NSS_CACHE_PATH_LENGTH];
+  struct nss_cache_args args;
+  enum nss_status ret;
+
+  NSS_CACHE_LOCK();
+
+  // name is a const char, we need a non-const copy
+  pw_name = malloc(strlen(name) + 1);
+  if (pw_name == NULL) {
+    DEBUG("malloc error\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncpy(pw_name, name, strlen(name) + 1);
+
+  strncpy(filename, p_filename, NSS_CACHE_PATH_LENGTH - 1);
+  if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 8) {
+    DEBUG("filename too long\n");
+    free(pw_name);
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncat(filename, ".byname", 7);
+
+  args.sorted_filename = filename;
+  args.system_filename = p_filename;
+  args.lookup_function = _nss_cache_pwnam_wrap;
+  args.lookup_value = pw_name;
+  args.lookup_result = result;
+  args.buffer = buffer;
+  args.buflen = buflen;
+
+  DEBUG("Binary search for user %s\n", pw_name);
+  ret = _nss_cache_bsearch(&args, errnop);
+
+  // TODO(vasilios): make this a runtime or compile-time option, as this slows
+  // down legitimate misses as the trade off for safety.
+  if (ret == NSS_STATUS_NOTFOUND) {
+    DEBUG("Binary search returned nothing.\n");
+    ret = NSS_STATUS_UNAVAIL;
+  }
+
+  if (ret == NSS_STATUS_UNAVAIL) {
+    DEBUG("Binary search failed, falling back to full linear search\n");
+    ret = _nss_cache_setpwent_locked();
+
+    if (ret == NSS_STATUS_SUCCESS) {
+      while ((ret = _nss_cache_getpwent_r_locked(result,
+                                                buffer,
+                                                buflen,
+                                                errnop))
+             == NSS_STATUS_SUCCESS) {
+        if (!strcmp(result->pw_name, name))
+          break;
+      }
+    }
+  }
+
+  free(pw_name);
+  _nss_cache_endpwent_locked();
+  NSS_CACHE_UNLOCK();
+
+  return ret;
+}
+
+//
+//  Routines for group map defined here.
+//
+
+// _nss_cache_setgrent_path()
+// Helper function for testing
+
+extern char* _nss_cache_setgrent_path(const char *path) {
+
+  DEBUG("%s %s\n", "Setting g_filename to", path);
+  return strncpy(g_filename, path, NSS_CACHE_PATH_LENGTH - 1);
+
+}
+
+// _nss_cache_setgrent_locked()
+// Internal setup routine
+
+static enum nss_status _nss_cache_setgrent_locked(void) {
+
+  DEBUG("%s %s\n", "Opening", g_filename);
+  g_file = fopen(g_filename, "r");
+
+  if (g_file) {
+    return NSS_STATUS_SUCCESS;
+  } else {
+    return NSS_STATUS_UNAVAIL;
+  }
+}
+
+// _nss_cache_grgid_wrap()
+// Internal wrapper for binary searches, using gid-specific calls.
+
+static enum nss_cache_match _nss_cache_grgid_wrap(FILE *file,
+                                                 struct nss_cache_args *args) {
+  struct group *result = args->lookup_result;
+  gid_t *gid = args->lookup_value;
+
+  if (fgetgrent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+    if (result->gr_gid == *gid) {
+      DEBUG("SUCCESS: found group %d:%s\n", result->gr_gid, result->gr_name);
+      return NSS_CACHE_EXACT;
+    }
+    DEBUG("Failed match at gid %d\n", result->gr_gid);
+    if (result->gr_gid > *gid) {
+      return NSS_CACHE_HIGH;
+    } else {
+      return NSS_CACHE_LOW;
+    }
+  }
+
+  return NSS_CACHE_ERROR;
+}
+
+// _nss_cache_grnam_wrap()
+// Internal wrapper for binary searches, using groupname-specific calls.
+
+static enum nss_cache_match _nss_cache_grnam_wrap(FILE *file,
+                                                 struct nss_cache_args *args) {
+  struct group *result = args->lookup_result;
+  char *name = args->lookup_value;
+  int ret;
+
+  if (fgetgrent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+    ret = strcoll(result->gr_name, name);
+    if (ret == 0) {
+      DEBUG("SUCCESS: found group %s\n", result->gr_name);
+      return NSS_CACHE_EXACT;
+    }
+    DEBUG("Failed match at name %s\n", result->gr_name);
+    if (ret > 0) {
+      return NSS_CACHE_HIGH;
+    } else {
+      return NSS_CACHE_LOW;
+    }
+  }
+
+  return NSS_CACHE_ERROR;
+}
+
+// _nss_cache_setgrent()
+// Called by NSS to open the group file
+// 'stayopen' parameter is ignored.
+
+enum nss_status _nss_cache_setgrent(int stayopen) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_setgrent_locked();
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_endgrent_locked()
+// Internal close routine
+
+static enum nss_status _nss_cache_endgrent_locked(void) {
+
+  DEBUG("Closing group.cache\n");
+  if (g_file) {
+    fclose(g_file);
+    g_file = NULL;
+  }
+  return NSS_STATUS_SUCCESS;
+}
+
+// _nss_cache_endgrent()
+// Called by NSS to close the group file
+
+enum nss_status _nss_cache_endgrent(void) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_endgrent_locked();
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_getgrent_r_locked()
+// Called internally to return the next entry from the group file
+
+static enum nss_status _nss_cache_getgrent_r_locked(struct group *result,
+                                                   char *buffer, size_t buflen,
+                                                   int *errnop) {
+  enum nss_status ret = NSS_STATUS_SUCCESS;
+
+  if (g_file == NULL) {
+    DEBUG("g_file == NULL, going to setgrent\n");
+    ret = _nss_cache_setgrent_locked();
+  }
+
+  if (ret == NSS_STATUS_SUCCESS) {
+    fpos_t position;
+
+    fgetpos(g_file, &position);
+    if (fgetgrent_r(g_file, result, buffer, buflen, &result) == 0) {
+      DEBUG("Returning group %s (%d)\n", result->gr_name, result->gr_gid);
+    } else {
+      /* Rewind back to where we were just before, otherwise the data read
+       * into the buffer is probably going to be lost because there's no
+       * guarantee that the caller is going to have preserved the line we
+       * just read.  Note that glibc's nss/nss_files/files-XXX.c does
+       * something similar in CONCAT(_nss_files_get,ENTNAME_r) (around
+       * line 242 in glibc 2.4 sources).
+       */
+      fsetpos(g_file, &position);
+      *errnop = errno;
+      ret = _nss_cache_ent_bad_return_code(*errnop);
+    }
+  }
+
+  return ret;
+}
+
+// _nss_cache_getgrent_r()
+// Called by NSS to look up next entry in group file
+
+enum nss_status _nss_cache_getgrent_r(struct group *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_getgrent_r_locked(result, buffer, buflen, errnop);
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_getgrgid_r()
+// Find a group by gid
+
+enum nss_status _nss_cache_getgrgid_r(gid_t gid, struct group *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  char filename[NSS_CACHE_PATH_LENGTH];
+  struct nss_cache_args args;
+  enum nss_status ret;
+
+  strncpy(filename, g_filename, NSS_CACHE_PATH_LENGTH - 1);
+  if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 7) {
+    DEBUG("filename too long\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncat(filename, ".bygid", 6);
+
+  args.sorted_filename = filename;
+  args.system_filename = g_filename;
+  args.lookup_function = _nss_cache_grgid_wrap;
+  args.lookup_value = &gid;
+  args.lookup_result = result;
+  args.buffer = buffer;
+  args.buflen = buflen;
+
+  DEBUG("Binary search for gid %d\n", gid);
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_bsearch(&args, errnop);
+
+  // TODO(vasilios): make this a runtime or compile-time option, as this slows
+  // down legitimate misses as the trade off for safety.
+  if (ret == NSS_STATUS_NOTFOUND) {
+    DEBUG("Binary search returned nothing.\n");
+    ret = NSS_STATUS_UNAVAIL;
+  }
+
+  if (ret == NSS_STATUS_UNAVAIL) {
+    DEBUG("Binary search failed, falling back to full linear search\n");
+    ret = _nss_cache_setgrent_locked();
+
+    if (ret == NSS_STATUS_SUCCESS) {
+      while ((ret = _nss_cache_getgrent_r_locked(result,
+                                                buffer,
+                                                buflen,
+                                                errnop))
+             == NSS_STATUS_SUCCESS) {
+        if (result->gr_gid == gid)
+          break;
+      }
+    }
+  }
+
+  _nss_cache_endgrent_locked();
+  NSS_CACHE_UNLOCK();
+
+  return ret;
+}
+
+// _nss_cache_getgrnam_r()
+// Find a group by name
+
+enum nss_status _nss_cache_getgrnam_r(const char *name, struct group *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  char *gr_name;
+  char filename[NSS_CACHE_PATH_LENGTH];
+  struct nss_cache_args args;
+  enum nss_status ret;
+
+  NSS_CACHE_LOCK();
+
+  // name is a const char, we need a non-const copy
+  gr_name = malloc(strlen(name) + 1);
+  if (gr_name == NULL) {
+    DEBUG("malloc error\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncpy(gr_name, name, strlen(name) + 1);
+
+  strncpy(filename, g_filename, NSS_CACHE_PATH_LENGTH - 1);
+  if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 8) {
+    DEBUG("filename too long\n");
+    free(gr_name);
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncat(filename, ".byname", 7);
+
+  args.sorted_filename = filename;
+  args.system_filename = g_filename;
+  args.lookup_function = _nss_cache_grnam_wrap;
+  args.lookup_value = gr_name;
+  args.lookup_result = result;
+  args.buffer = buffer;
+  args.buflen = buflen;
+
+  DEBUG("Binary search for group %s\n", gr_name);
+  ret = _nss_cache_bsearch(&args, errnop);
+
+  // TODO(vasilios): make this a runtime or compile-time option, as this slows
+  // down legitimate misses as the trade off for safety.
+  if (ret == NSS_STATUS_NOTFOUND) {
+    DEBUG("Binary search returned nothing.\n");
+    ret = NSS_STATUS_UNAVAIL;
+  }
+
+  if (ret == NSS_STATUS_UNAVAIL) {
+    DEBUG("Binary search failed, falling back to full linear search\n");
+    ret = _nss_cache_setgrent_locked();
+
+    if (ret == NSS_STATUS_SUCCESS) {
+      while ((ret = _nss_cache_getgrent_r_locked(result,
+                                                buffer,
+                                                buflen,
+                                                errnop))
+             == NSS_STATUS_SUCCESS) {
+        if (!strcmp(result->gr_name, name))
+          break;
+      }
+    }
+  }
+
+  free(gr_name);
+  _nss_cache_endgrent_locked();
+  NSS_CACHE_UNLOCK();
+
+  return ret;
+}
+
+//
+//  Routines for shadow map defined here.
+//
+
+// _nss_cache_setspent_path()
+// Helper function for testing
+
+extern char* _nss_cache_setspent_path(const char *path) {
+
+  DEBUG("%s %s\n", "Setting s_filename to", path);
+  return strncpy(s_filename, path, NSS_CACHE_PATH_LENGTH - 1);
+
+}
+
+// _nss_cache_setspent_locked()
+// Internal setup routine
+
+static enum nss_status _nss_cache_setspent_locked(void) {
+
+  DEBUG("%s %s\n", "Opening", g_filename);
+  s_file = fopen(s_filename, "r");
+
+  if (s_file) {
+    return NSS_STATUS_SUCCESS;
+  } else {
+    return NSS_STATUS_UNAVAIL;
+  }
+}
+
+// _nss_cache_spnam_wrap()
+// Internal wrapper for binary searches, using shadow-specific calls.
+
+static enum nss_cache_match _nss_cache_spnam_wrap(FILE *file,
+                                                 struct nss_cache_args *args) {
+  struct spwd *result = args->lookup_result;
+  char *name = args->lookup_value;
+  int ret;
+
+  if (fgetspent_r(file, result, args->buffer, args->buflen, &result) == 0) {
+    ret = strcoll(result->sp_namp, name);
+    if (ret == 0) {
+      DEBUG("SUCCESS: found user %s\n", result->sp_namp);
+      return NSS_CACHE_EXACT;
+    }
+    DEBUG("Failed match at name %s\n", result->sp_namp);
+    if (ret > 0) {
+      return NSS_CACHE_HIGH;
+    } else {
+      return NSS_CACHE_LOW;
+    }
+  }
+
+  return NSS_CACHE_ERROR;
+}
+
+// _nss_cache_setspent()
+// Called by NSS to open the shadow file
+// 'stayopen' parameter is ignored.
+
+enum nss_status _nss_cache_setspent(int stayopen) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_setspent_locked();
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_endspent_locked()
+// Internal close routine
+
+static enum nss_status _nss_cache_endspent_locked(void) {
+
+  DEBUG("Closing shadow.cache\n");
+  if (s_file) {
+    fclose(s_file);
+    s_file = NULL;
+  }
+  return NSS_STATUS_SUCCESS;
+}
+
+// _nss_cache_endspent()
+// Called by NSS to close the shadow file
+
+enum nss_status _nss_cache_endspent(void) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_endspent_locked();
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_getspent_r_locked()
+// Called internally to return the next entry from the shadow file
+
+static enum nss_status _nss_cache_getspent_r_locked(struct spwd *result,
+                                                   char *buffer, size_t buflen,
+                                                   int *errnop) {
+
+  enum nss_status ret = NSS_STATUS_SUCCESS;
+
+  if (s_file == NULL) {
+    DEBUG("s_file == NULL, going to setspent\n");
+    ret = _nss_cache_setspent_locked();
+  }
+
+  if (ret == NSS_STATUS_SUCCESS) {
+    if (fgetspent_r(s_file, result, buffer, buflen, &result) == 0) {
+      DEBUG("Returning shadow entry %s\n", result->sp_namp);
+    } else {
+      *errnop = errno;
+      ret = _nss_cache_ent_bad_return_code(*errnop);
+    }
+  }
+
+  return ret;
+}
+
+// _nss_cache_getspent_r()
+// Called by NSS to look up next entry in the shadow file
+
+enum nss_status _nss_cache_getspent_r(struct spwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  enum nss_status ret;
+  NSS_CACHE_LOCK();
+  ret = _nss_cache_getspent_r_locked(result, buffer, buflen, errnop);
+  NSS_CACHE_UNLOCK();
+  return ret;
+}
+
+// _nss_cache_getspnam_r()
+// Find a user by name
+
+enum nss_status _nss_cache_getspnam_r(const char *name, struct spwd *result,
+                                     char *buffer, size_t buflen,
+                                     int *errnop) {
+  char *sp_namp;
+  char filename[NSS_CACHE_PATH_LENGTH];
+  struct nss_cache_args args;
+  enum nss_status ret;
+
+  NSS_CACHE_LOCK();
+
+  // name is a const char, we need a non-const copy
+  sp_namp = malloc(strlen(name) + 1);
+  if (sp_namp == NULL) {
+    DEBUG("malloc error\n");
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncpy(sp_namp, name, strlen(name) + 1);
+
+  strncpy(filename, s_filename, NSS_CACHE_PATH_LENGTH - 1);
+  if (strlen(filename) > NSS_CACHE_PATH_LENGTH - 8) {
+    DEBUG("filename too long\n");
+    free(sp_namp);
+    return NSS_STATUS_UNAVAIL;
+  }
+  strncat(filename, ".byname", 7);
+
+  args.sorted_filename = filename;
+  args.system_filename = s_filename;
+  args.lookup_function = _nss_cache_spnam_wrap;
+  args.lookup_value = sp_namp;
+  args.lookup_result = result;
+  args.buffer = buffer;
+  args.buflen = buflen;
+
+  DEBUG("Binary search for user %s\n", sp_namp);
+  ret = _nss_cache_bsearch(&args, errnop);
+
+  // TODO(vasilios): make this a runtime or compile-time option, as this slows
+  // down legitimate misses as the trade off for safety.
+  if (ret == NSS_STATUS_NOTFOUND) {
+    DEBUG("Binary search returned nothing.\n");
+    ret = NSS_STATUS_UNAVAIL;
+  }
+
+  if (ret == NSS_STATUS_UNAVAIL) {
+    DEBUG("Binary search failed, falling back to full linear search\n");
+    ret = _nss_cache_setspent_locked();
+
+    if (ret == NSS_STATUS_SUCCESS) {
+      while ((ret = _nss_cache_getspent_r_locked(result,
+                                                buffer,
+                                                buflen,
+                                                errnop))
+             == NSS_STATUS_SUCCESS) {
+        if (!strcmp(result->sp_namp, name))
+          break;
+      }
+    }
+  }
+
+  free(sp_namp);
+  _nss_cache_endspent_locked();
+  NSS_CACHE_UNLOCK();
+
+  return ret;
+}
diff --git a/nss/nss_cache/nss_cache.h b/nss/nss_cache/nss_cache.h
new file mode 100644
index 0000000000..8ce4b97d33
--- /dev/null
+++ b/nss/nss_cache/nss_cache.h
@@ -0,0 +1,65 @@
+/* Copyright 2009 Google Inc.
+ *
+ * This 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.
+ *
+ * This 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA
+ */
+
+#include <errno.h>
+#include <grp.h>
+#include <nss.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NSS_CACHE_H
+#define NSS_CACHE_H
+
+#ifdef DEBUG
+#undef DEBUG
+#define DEBUG(fmt, args...)  do { fprintf(stderr, fmt, ##args); } while (0)
+#else
+#define DEBUG(fmt, ...)      do { } while (0)
+#endif /* DEBUG */
+
+#define NSS_CACHE_PATH_LENGTH 255
+extern char* _nss_cache_setpwent_path(const char *path);
+extern char* _nss_cache_setgrent_path(const char *path);
+extern char* _nss_cache_setspent_path(const char *path);
+
+enum nss_cache_match {
+  NSS_CACHE_EXACT = 0,
+  NSS_CACHE_HIGH = 1,
+  NSS_CACHE_LOW = 2,
+  NSS_CACHE_ERROR = 3,
+};
+
+struct nss_cache_args {
+  char *system_filename;
+  char *sorted_filename;
+  void *lookup_function;
+  void *lookup_value;
+  void *lookup_result;
+  char *buffer;
+  size_t buflen;
+};
+
+#endif /* NSS_CACHE_H */
diff --git a/shlib-versions b/shlib-versions
index b9cb99d2fb..749346e267 100644
--- a/shlib-versions
+++ b/shlib-versions
@@ -48,6 +48,8 @@ libnss_nisplus=2
 libnss_ldap=2
 libnss_hesiod=2
 libnss_db=2
+libnss_borg=2
+libnss_cache=2
 
 # Tests for NSS.  They must have the same NSS_SHLIB_REVISION number as
 # the rest.


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2021-08-27 23:23 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-08-27 23:23 [glibc/maskray/grte] Integrate nss_{borg, cache} local changes from glibc-2.18 to 2.19 Fangrui Song

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).