about summary refs log tree commit diff
path: root/nscd/connections.c
diff options
context:
space:
mode:
authorUlrich Drepper <drepper@redhat.com>2004-08-26 18:35:05 +0000
committerUlrich Drepper <drepper@redhat.com>2004-08-26 18:35:05 +0000
commita95a08b4af38992cbcf3d1da97199ef19528fbde (patch)
tree669bba61a337bf8b9c9c15857877bc589792435c /nscd/connections.c
parent1114ffff54dfbd35cbff9c845376b8221c2c9ced (diff)
downloadglibc-a95a08b4af38992cbcf3d1da97199ef19528fbde.tar.gz
glibc-a95a08b4af38992cbcf3d1da97199ef19528fbde.tar.xz
glibc-a95a08b4af38992cbcf3d1da97199ef19528fbde.zip
Update.
2004-08-26  Ulrich Drepper  <drepper@redhat.com>

	* nscd/cache.c: Major rewrite.  The data is now optionally kept in
	a mmaped memory region which is automatically mirrored on disk.
	This implements persistent data storage.  The Memory handled
	needed to be completely revamped, it now uses a garbage collection
	mechanism instead of malloc.
	* nscd/connections.c: Likewise.
	* nscd/nscd.c: Likewise.
	* nscd/nscd.h: Likewise.
	* nscd/nscd_conf.c: Likewise.
	* nscd/nscd_stat.c: Likewise.
	* nscd/grpcache.c: Likewise.
	* nscd/hstcache.c:: Likewise.
	* nscd/pwdcache.c:: Likewise.
	* nscd/Makefile: Add rules to build mem.c.
	* nscd/mem.c: New file.
	* nscd/nscd.conf: Describe new configuration options.
Diffstat (limited to 'nscd/connections.c')
-rw-r--r--nscd/connections.c383
1 files changed, 323 insertions, 60 deletions
diff --git a/nscd/connections.c b/nscd/connections.c
index 313ca0dc45..c3100816df 100644
--- a/nscd/connections.c
+++ b/nscd/connections.c
@@ -24,14 +24,15 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <grp.h>
+#include <libintl.h>
 #include <pthread.h>
 #include <pwd.h>
 #include <resolv.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include <libintl.h>
 #include <arpa/inet.h>
+#include <sys/mman.h>
 #include <sys/param.h>
 #include <sys/poll.h>
 #include <sys/socket.h>
@@ -41,6 +42,11 @@
 #include "nscd.h"
 #include "dbg_log.h"
 
+
+/* Number of bytes of data we initially reserve for each hash table bucket.  */
+#define DEFAULT_DATASIZE_PER_BUCKET 1024
+
+
 /* Wrapper functions with error checking for standard functions.  */
 extern void *xmalloc (size_t n);
 extern void *xcalloc (size_t n, size_t s);
@@ -56,25 +62,11 @@ static gid_t *server_groups;
 #ifndef NGROUPS
 # define NGROUPS 32
 #endif
-static int server_ngroups = NGROUPS;
+static int server_ngroups;
 
 static void begin_drop_privileges (void);
 static void finish_drop_privileges (void);
 
-
-/* Mapping of request type to database.  */
-static const dbtype serv2db[LASTDBREQ + 1] =
-{
-  [GETPWBYNAME] = pwddb,
-  [GETPWBYUID] = pwddb,
-  [GETGRBYNAME] = grpdb,
-  [GETGRBYGID] = grpdb,
-  [GETHOSTBYNAME] = hstdb,
-  [GETHOSTBYNAMEv6] = hstdb,
-  [GETHOSTBYADDR] = hstdb,
-  [GETHOSTBYADDRv6] = hstdb,
-};
-
 /* Map request type to a string.  */
 const char *serv2str[LASTREQ] =
 {
@@ -92,43 +84,71 @@ const char *serv2str[LASTREQ] =
 };
 
 /* The control data structures for the services.  */
-struct database dbs[lastdb] =
+struct database_dyn dbs[lastdb] =
 {
   [pwddb] = {
     .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
     .enabled = 0,
     .check_file = 1,
+    .persistent = 0,
     .filename = "/etc/passwd",
-    .module = 211,
+    .db_filename = _PATH_NSCD_PASSWD_DB,
     .disabled_iov = &pwd_iov_disabled,
     .postimeout = 3600,
-    .negtimeout = 20
+    .negtimeout = 20,
+    .wr_fd = -1,
+    .ro_fd = -1,
+    .mmap_used = false
   },
   [grpdb] = {
     .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
     .enabled = 0,
     .check_file = 1,
+    .persistent = 0,
     .filename = "/etc/group",
-    .module = 211,
+    .db_filename = _PATH_NSCD_GROUP_DB,
     .disabled_iov = &grp_iov_disabled,
     .postimeout = 3600,
-    .negtimeout = 60
+    .negtimeout = 60,
+    .wr_fd = -1,
+    .ro_fd = -1,
+    .mmap_used = false
   },
   [hstdb] = {
     .lock = PTHREAD_RWLOCK_WRITER_NONRECURSIVE_INITIALIZER_NP,
     .enabled = 0,
     .check_file = 1,
+    .persistent = 0,
     .filename = "/etc/hosts",
-    .module = 211,
+    .db_filename = _PATH_NSCD_HOSTS_DB,
     .disabled_iov = &hst_iov_disabled,
     .postimeout = 3600,
-    .negtimeout = 20
+    .negtimeout = 20,
+    .wr_fd = -1,
+    .ro_fd = -1,
+    .mmap_used = false
   }
 };
 
+
+/* Mapping of request type to database.  */
+static struct database_dyn *const serv2db[LASTDBREQ + 1] =
+{
+  [GETPWBYNAME] = &dbs[pwddb],
+  [GETPWBYUID] = &dbs[pwddb],
+  [GETGRBYNAME] = &dbs[grpdb],
+  [GETGRBYGID] = &dbs[grpdb],
+  [GETHOSTBYNAME] = &dbs[hstdb],
+  [GETHOSTBYNAMEv6] = &dbs[hstdb],
+  [GETHOSTBYADDR] = &dbs[hstdb],
+  [GETHOSTBYADDRv6] = &dbs[hstdb]
+};
+
+
 /* Number of seconds between two cache pruning runs.  */
 #define CACHE_PRUNE_INTERVAL	15
 
+
 /* Number of threads to use.  */
 int nthreads = -1;
 
@@ -138,6 +158,9 @@ static int sock;
 /* Number of times clients had to wait.  */
 unsigned long int client_queued;
 
+/* Alignment requirement of the beginning of the data region.  */
+#define ALIGN 16
+
 
 /* Initialize database information structures.  */
 void
@@ -166,13 +189,256 @@ nscd_init (void)
     if (dbs[cnt].enabled)
       {
 	pthread_rwlock_init (&dbs[cnt].lock, NULL);
+	pthread_mutex_init (&dbs[cnt].memlock, NULL);
 
-	dbs[cnt].array = (struct hashentry **)
-	  calloc (dbs[cnt].module, sizeof (struct hashentry *));
-	if (dbs[cnt].array == NULL)
+	if (dbs[cnt].persistent)
 	  {
-	    dbg_log (_("while allocating cache: %s"), strerror (errno));
-	    exit (1);
+	    /* Try to open the appropriate file on disk.  */
+	    int fd = open (dbs[cnt].db_filename, O_RDWR);
+	    if (fd != -1)
+	      {
+		struct stat64 st;
+		void *mem;
+		size_t total;
+		struct database_pers_head head;
+		ssize_t n = TEMP_FAILURE_RETRY (read (fd, &head,
+						      sizeof (head)));
+		if (n != sizeof (head) || fstat64 (fd, &st) != 0)
+		  {
+		  fail_db:
+		    dbg_log (_("invalid persistent database file \"%s\": %s"),
+			     dbs[cnt].db_filename, strerror (errno));
+		    dbs[cnt].persistent = 0;
+		  }
+		else if (head.module == 0 && head.data_size == 0)
+		  {
+		    /* The file has been created, but the head has not been
+		       initialized yet.  Remove the old file.  */
+		    unlink (dbs[cnt].db_filename);
+		  }
+		else if (head.header_size != (int) sizeof (head))
+		  {
+		    dbg_log (_("invalid persistent database file \"%s\": %s"),
+			     dbs[cnt].db_filename,
+			     _("header size does not match"));
+		    dbs[cnt].persistent = 0;
+		  }
+		else if ((total = (sizeof (head)
+				   + roundup (head.module
+					      * sizeof (struct hashentry),
+					      ALIGN)
+				   + head.data_size))
+			 < st.st_size)
+		  {
+		    dbg_log (_("invalid persistent database file \"%s\": %s"),
+			     dbs[cnt].db_filename,
+			     _("file size does not match"));
+		    dbs[cnt].persistent = 0;
+		  }
+		else if ((mem = mmap (NULL, total, PROT_READ | PROT_WRITE,
+				      MAP_SHARED, fd, 0)) == MAP_FAILED)
+		  goto fail_db;
+		else
+		  {
+		    /* Success.  We have the database.  */
+		    dbs[cnt].head = mem;
+		    dbs[cnt].memsize = total;
+		    dbs[cnt].data = (char *)
+		      &dbs[cnt].head->array[roundup (dbs[cnt].head->module,
+						     ALIGN / sizeof (ref_t))];
+		    dbs[cnt].mmap_used = true;
+
+		    if (dbs[cnt].suggested_module > head.module)
+		      dbg_log (_("suggested size of table for database %s larger than the persistent database's table"),
+			       dbnames[cnt]);
+
+		    dbs[cnt].wr_fd = fd;
+		    fd = -1;
+		    /* We also need a read-only descriptor.  */
+		    dbs[cnt].ro_fd = open (dbs[cnt].db_filename, O_RDONLY);
+		    if (dbs[cnt].ro_fd == -1)
+		      dbg_log (_("\
+cannot create read-only descriptor for \"%s\"; no mmap"),
+			       dbs[cnt].db_filename);
+
+		    // XXX Shall we test whether the descriptors actually
+		    // XXX point to the same file?
+		  }
+
+		/* Close the file descriptors in case something went
+		   wrong in which case the variable have not been
+		   assigned -1.  */
+		if (fd != -1)
+		  close (fd);
+	      }
+	  }
+
+	if (dbs[cnt].head == NULL)
+	  {
+	    /* No database loaded.  Allocate the data structure,
+	       possibly on disk.  */
+	    struct database_pers_head head;
+	    size_t total = (sizeof (head)
+			    + roundup (dbs[cnt].suggested_module
+				       * sizeof (ref_t), ALIGN)
+			    + (dbs[cnt].suggested_module
+			       * DEFAULT_DATASIZE_PER_BUCKET));
+
+	    /* Try to create the database.  If we do not need a
+	       persistent database create a temporary file.  */
+	    int fd;
+	    int ro_fd = -1;
+	    if (dbs[cnt].persistent)
+	      {
+		fd = open (dbs[cnt].db_filename,
+			   O_RDWR | O_CREAT | O_EXCL | O_TRUNC,
+			   S_IRUSR | S_IWUSR);
+		if (fd != -1)
+		  ro_fd = open (dbs[cnt].db_filename, O_RDONLY);
+	      }
+	    else
+	      {
+		size_t slen = strlen (dbs[cnt].db_filename);
+		char fname[slen + 8];
+		strcpy (mempcpy (fname, dbs[cnt].db_filename, slen),
+			".XXXXXX");
+		fd = mkstemp (fname);
+
+		/* We do not need the file name anymore after we
+		   opened another file descriptor in read-only mode.  */
+		if (fd != -1)
+		  {
+		    ro_fd = open (fname, O_RDONLY);
+
+		    unlink (fname);
+		  }
+	      }
+
+	    if (fd == -1)
+	      {
+		if (errno == EEXIST)
+		  {
+		    dbg_log (_("database for %s corrupted or simultaneously used; remove %s manually if necessary and restart"),
+			     dbnames[cnt], dbs[cnt].db_filename);
+		    // XXX Correct way to terminate?
+		    exit (1);
+		  }
+
+		if  (dbs[cnt].persistent)
+		  dbg_log (_("cannot create %s; no persistent database used"),
+			   dbs[cnt].db_filename);
+		else
+		  dbg_log (_("cannot create %s; no sharing possible"),
+			   dbs[cnt].db_filename);
+
+		dbs[cnt].persistent = 0;
+		// XXX remember: no mmap
+	      }
+	    else
+	      {
+		/* Tell the user if we could not create the read-only
+		   descriptor.  */
+		if (ro_fd == -1)
+		  dbg_log (_("\
+cannot create read-only descriptor for \"%s\"; no mmap"),
+			   dbs[cnt].db_filename);
+
+		/* Before we create the header, initialiye the hash
+		   table.  So that if we get interrupted if writing
+		   the header we can recognize a partially initialized
+		   database.  */
+		size_t ps = sysconf (_SC_PAGESIZE);
+		char tmpbuf[ps];
+		assert (~ENDREF == 0);
+		memset (tmpbuf, '\xff', ps);
+
+		size_t remaining = dbs[cnt].suggested_module * sizeof (ref_t);
+		off_t offset = sizeof (head);
+
+		size_t towrite;
+		if (offset % ps != 0)
+		  {
+		    towrite = MIN (remaining, ps - (offset % ps));
+		    pwrite (fd, tmpbuf, towrite, offset);
+		    offset += towrite;
+		    remaining -= towrite;
+		  }
+
+		while (remaining > ps)
+		  {
+		    pwrite (fd, tmpbuf, ps, offset);
+		    offset += ps;
+		    remaining -= ps;
+		  }
+
+		if (remaining > 0)
+		  pwrite (fd, tmpbuf, remaining, offset);
+
+		/* Create the header of the file.  */
+		struct database_pers_head head =
+		  {
+		    .version = DB_VERSION,
+		    .header_size = sizeof (head),
+		    .module = dbs[cnt].suggested_module,
+		    .data_size = (dbs[cnt].suggested_module
+				  * DEFAULT_DATASIZE_PER_BUCKET),
+		    .first_free = 0
+		  };
+		void *mem;
+
+		if ((TEMP_FAILURE_RETRY (write (fd, &head, sizeof (head)))
+		     != sizeof (head))
+		    || ftruncate (fd, total) != 0
+		    || (mem = mmap (NULL, total, PROT_READ | PROT_WRITE,
+				    MAP_SHARED, fd, 0)) == MAP_FAILED)
+		  {
+		    unlink (dbs[cnt].db_filename);
+		    dbg_log (_("cannot write to database file %s: %s"),
+			     dbs[cnt].db_filename, strerror (errno));
+		    dbs[cnt].persistent = 0;
+		  }
+		else
+		  {
+		    /* Success.  */
+		    dbs[cnt].head = mem;
+		    dbs[cnt].data = (char *)
+		      &dbs[cnt].head->array[roundup (dbs[cnt].head->module,
+						     ALIGN / sizeof (ref_t))];
+		    dbs[cnt].memsize = total;
+		    dbs[cnt].mmap_used = true;
+
+		    /* Remember the descriptors.  */
+		    dbs[cnt].wr_fd = fd;
+		    dbs[cnt].ro_fd = ro_fd;
+		    fd = -1;
+		    ro_fd = -1;
+		  }
+
+		if (fd != -1)
+		  close (fd);
+		if (ro_fd != -1)
+		  close (ro_fd);
+	      }
+	  }
+
+	if (dbs[cnt].head == NULL)
+	  {
+	    /* We do not use the persistent database.  Just
+	       create an in-memory data structure.  */
+	    assert (! dbs[cnt].persistent);
+
+	    dbs[cnt].head = xmalloc (sizeof (struct database_pers_head)
+				     + (dbs[cnt].suggested_module
+					* sizeof (ref_t)));
+	    memset (dbs[cnt].head, '\0', sizeof (dbs[cnt].head));
+	    assert (~ENDREF == 0);
+	    memset (dbs[cnt].head->array, '\xff',
+		    dbs[cnt].suggested_module * sizeof (ref_t));
+	    dbs[cnt].head->module = dbs[cnt].suggested_module;
+	    dbs[cnt].head->data_size = (DEFAULT_DATASIZE_PER_BUCKET
+					* dbs[cnt].head->module);
+	    dbs[cnt].data = xmalloc (dbs[cnt].head->data_size);
+	    dbs[cnt].head->first_free = 0;
 	  }
 
 	if (dbs[cnt].check_file)
@@ -215,7 +481,7 @@ nscd_init (void)
     fcntl (sock, F_SETFL, fl | O_NONBLOCK);
 
   /* Set permissions for the socket.  */
-  chmod (_PATH_NSCDSOCKET, 0666);
+  chmod (_PATH_NSCDSOCKET, DEFFILEMODE);
 
   /* Set the socket up to accept connections.  */
   if (listen (sock, SOMAXCONN) < 0)
@@ -276,12 +542,11 @@ cannot handle old request version %d; current version is %d"),
       return;
     }
 
+  struct database_dyn *db = serv2db[req->type];
+
   if (__builtin_expect (req->type, GETPWBYNAME) >= GETPWBYNAME
       && __builtin_expect (req->type, LASTDBREQ) <= LASTDBREQ)
     {
-      struct hashentry *cached;
-      struct database *db = &dbs[serv2db[req->type]];
-
       if (__builtin_expect (debug_level, 0) > 0)
 	{
 	  if (req->type == GETHOSTBYADDR || req->type == GETHOSTBYADDRv6)
@@ -294,7 +559,7 @@ cannot handle old request version %d; current version is %d"),
 				  key, buf, sizeof (buf)));
 	    }
 	  else
-	    dbg_log ("\t%s (%s)", serv2str[req->type], (char *)key);
+	    dbg_log ("\t%s (%s)", serv2str[req->type], (char *) key);
 	}
 
       /* Is this service enabled?  */
@@ -318,18 +583,19 @@ cannot handle old request version %d; current version is %d"),
       /* Be sure we can read the data.  */
       if (__builtin_expect (pthread_rwlock_tryrdlock (&db->lock) != 0, 0))
 	{
-	  ++db->rdlockdelayed;
+	  ++db->head->rdlockdelayed;
 	  pthread_rwlock_rdlock (&db->lock);
 	}
 
       /* See whether we can handle it from the cache.  */
-      cached = (struct hashentry *) cache_search (req->type, key, req->key_len,
-						  db, uid);
+      struct datahead *cached;
+      cached = (struct datahead *) cache_search (req->type, key, req->key_len,
+						 db, uid);
       if (cached != NULL)
 	{
 	  /* Hurray it's in the cache.  */
-	  if (TEMP_FAILURE_RETRY (write (fd, cached->packet, cached->total))
-	      != cached->total
+	  if (TEMP_FAILURE_RETRY (write (fd, cached->data, cached->recsize))
+	      != cached->recsize
 	      && __builtin_expect (debug_level, 0) > 0)
 	    {
 	      /* We have problems sending the result.  */
@@ -349,45 +615,43 @@ cannot handle old request version %d; current version is %d"),
     {
       if (req->type == INVALIDATE)
 	dbg_log ("\t%s (%s)", serv2str[req->type], (char *)key);
-      else if (req->type > LASTDBREQ && req->type < LASTREQ)
-	dbg_log ("\t%s", serv2str[req->type]);
       else
-	dbg_log (_("\tinvalid request type %d"), req->type);
+	dbg_log ("\t%s", serv2str[req->type]);
     }
 
   /* Handle the request.  */
   switch (req->type)
     {
     case GETPWBYNAME:
-      addpwbyname (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addpwbyname (db, fd, req, key, uid);
       break;
 
     case GETPWBYUID:
-      addpwbyuid (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addpwbyuid (db, fd, req, key, uid);
       break;
 
     case GETGRBYNAME:
-      addgrbyname (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addgrbyname (db, fd, req, key, uid);
       break;
 
     case GETGRBYGID:
-      addgrbygid (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addgrbygid (db, fd, req, key, uid);
       break;
 
     case GETHOSTBYNAME:
-      addhstbyname (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addhstbyname (db, fd, req, key, uid);
       break;
 
     case GETHOSTBYNAMEv6:
-      addhstbynamev6 (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addhstbynamev6 (db, fd, req, key, uid);
       break;
 
     case GETHOSTBYADDR:
-      addhstbyaddr (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addhstbyaddr (db, fd, req, key, uid);
       break;
 
     case GETHOSTBYADDRv6:
-      addhstbyaddrv6 (&dbs[serv2db[req->type]], fd, req, key, uid);
+      addhstbyaddrv6 (db, fd, req, key, uid);
       break;
 
     case GETSTAT:
@@ -484,6 +748,7 @@ nscd_run (void *p)
 	      prune_cache (&dbs[my_number], time(NULL));
 	      now = time (NULL);
 	      next_prune = now + CACHE_PRUNE_INTERVAL;
+
 	      goto try_get;
 	    }
 	}
@@ -538,7 +803,7 @@ nscd_run (void *p)
 	    }
 
 	  if (req.type < GETPWBYNAME || req.type > LASTDBREQ
-	      || secure[serv2db[req.type]])
+	      || serv2db[req.type]->secure)
 	    uid = caller.uid;
 
 	  pid = caller.pid;
@@ -646,9 +911,7 @@ start_threads (void)
 static void
 begin_drop_privileges (void)
 {
-  struct passwd *pwd;
-
-  pwd = getpwnam (server_user);
+  struct passwd *pwd = getpwnam (server_user);
 
   if (pwd == NULL)
     {
@@ -660,14 +923,14 @@ begin_drop_privileges (void)
   server_uid = pwd->pw_uid;
   server_gid = pwd->pw_gid;
 
-  server_groups = (gid_t *) xmalloc (server_ngroups * sizeof (gid_t));
-
-  if (getgrouplist (server_user, server_gid, server_groups, &server_ngroups)
-      == 0)
-    return;
+  if (getgrouplist (server_user, server_gid, NULL, &server_ngroups) == 0)
+    {
+      /* This really must never happen.  */
+      dbg_log (_("Failed to run nscd as user '%s'"), server_user);
+      error (EXIT_FAILURE, errno, _("initial getgrouplist failed"));
+    }
 
-  server_groups = (gid_t *) xrealloc (server_groups,
-				      server_ngroups * sizeof (gid_t));
+  server_groups = (gid_t *) xmalloc (server_ngroups * sizeof (gid_t));
 
   if (getgrouplist (server_user, server_gid, server_groups, &server_ngroups)
       == -1)