One way to solve slow logins with NT-domains

Jani Jaakkola jjaakkol at cs.Helsinki.FI
Thu May 6 17:28:25 GMT 1999


When i was experimenting with the NT-domain code, it turned out to be too
slow for our environment. We have 2300 users in our /etc/passwd and 200
groups in our /etc/group. It turned out that samba does hundreds of
getpwnam() and getpwuid() calls when someone logins to domain, which will
take time, since Linux glibc-2.0 C-library doesn't have any caching
for passwords and our passwd file is 160K in size.

So I wrote a simple cache wrapper around sambas getpwnam() and getpwuid()
calls. The cache is built the first time when either of them is called and
is kept around for 15 seconds, after which it will be rebuilt. 

Oh yes.. If you happen to have multiple user names sharing the same uid or
multiple uids sharing the same user name, the cache will return the last
one in /etc/passwd instead of the first one. If you think this is a
promlem, it can be easily fixed.

I'm including the patch in this email. It is agains the current CVS
version (well, current version at least an hour ago). 
I hope no one will get upset, since it is 355 lines. It isn't tested very 
throughly (i just got it ready an hour ago). Anyway, I can log on to NT
domain in 1 second instead of 15-20 seconds and I won't get the annoying
slow network dialog :)


--- samba/source/include/proto.h	Tue May  4 01:00:31 1999
+++ samba.patched/source/include/proto.h	Thu May  6 13:16:07 1999
@@ -382,6 +382,7 @@
 
 /*The following definitions come from  lib/username.c  */
 
+struct passwd *hashed_getpwnam(const char *name) ;
 char *get_home_dir(char *user);
 BOOL map_username(char *user);
 struct passwd *Get_Pwnam(char *user,BOOL allow_change);
diff -x CVS -ru samba/source/lib/username.c samba.patched/source/lib/username.c
--- samba/source/lib/username.c	Mon Dec 14 22:21:39 1998
+++ samba.patched/source/lib/username.c	Thu May  6 19:21:37 1999
@@ -27,6 +27,276 @@
 static struct passwd *uname_string_combinations2(char *s, int offset, struct passwd * (*fn) (char *), int N);
 
 /****************************************************************************
+  Since getpwnam() makes samba really slow with the NT-domain code
+  (reading /etc/passwd again and again and again), here is an implementation
+  of very simple passwd cache
+****************************************************************************/
+#define PASSWD_HASH_SIZE 1009
+/* The hashtable is rebuild every 15 seconds */
+#define PASSWD_HASH_AGE 15
+struct passwd_hash_entry {
+  int entry;
+  int next;
+};
+
+struct passwd_hash_table_s {
+  struct passwd *passwds;
+  int passwds_size;  
+  int *names;
+  int *uids;
+  struct passwd_hash_entry *entries;
+  int entries_size;
+  struct timeval build_time;
+} passwd_hash_table = {
+  NULL,0,NULL,NULL,NULL,0,{0,0}
+};
+
+int name_hash_function(const char *name) 
+{
+  /* I guess that there must be better hash functions. This one was the
+   * first to come into mind :) */
+  unsigned int value=0;
+  while (*name) {
+    value=(value<<8)|(unsigned char)(*name);
+    if (value>1048576) value=value%PASSWD_HASH_SIZE;
+    name++;
+  }
+  value=value%PASSWD_HASH_SIZE;
+  return value;
+}
+    
+int uid_hash_function(uid_t uid) 
+{
+  return uid%PASSWD_HASH_SIZE;
+}
+
+
+BOOL build_passwd_hash_table() 
+{
+  struct passwd_hash_table_s *pht=&passwd_hash_table; /* Convenience */
+  int num_passwds=0;
+  int num_entries=0;
+  struct passwd *pass;
+  int i;
+  int name_i,uid_i;
+
+  DEBUG(3,("Building passwd hash table\n"));
+  /* Free the allocated strings in old hash table */
+  for (i=0;i<pht->passwds_size;i++) {
+    free(pht->passwds[i].pw_name);
+    free(pht->passwds[i].pw_passwd);
+    free(pht->passwds[i].pw_gecos);
+    free(pht->passwds[i].pw_dir);
+    free(pht->passwds[i].pw_shell);    
+  }
+
+  /* Initialize hash table if first table build */
+  if (pht->passwds_size==0) {
+    DEBUG(3,("Building passwd hash table for the first time\n"));
+    pht->passwds=malloc(sizeof(struct passwd)*64); /* A reasonable default */
+    pht->passwds_size=64;
+  }
+  if (pht->names==NULL) {
+    pht->names=malloc(sizeof(struct passwd_hash_entry *)*PASSWD_HASH_SIZE);
+  }
+  if (pht->uids==NULL) {
+    pht->uids=malloc(sizeof(struct passwd_hash_entry *)*PASSWD_HASH_SIZE);
+  }
+  if (pht->entries==NULL) {
+    pht->entries=malloc(sizeof(struct passwd_hash_entry)*128);
+    pht->entries_size=128;
+  }
+  if (pht->passwds==NULL || pht->names==NULL || 
+      pht->uids==NULL || pht->entries==NULL) {
+    goto fail;
+  }
+  
+  /* Clear out the hash table */
+  for(i=0;i<PASSWD_HASH_SIZE;i++) pht->uids[i]=-1;
+  for(i=0;i<PASSWD_HASH_SIZE;i++) pht->names[i]=-1;
+
+  /* Now do the build */
+  setpwent();
+
+  while((pass=getpwent())) {
+
+    /* Check that we have enough space */
+    if (num_passwds==pht->passwds_size) {
+      struct passwd *new_passwds=NULL;
+      pht->passwds_size+=pht->passwds_size/2;
+      new_passwds=realloc(pht->passwds,
+			   sizeof(struct passwd)*pht->passwds_size);
+      if (new_passwds==NULL) goto fail;
+      pht->passwds=new_passwds;
+    }
+    if (num_entries+1>=pht->entries_size) {
+      pht->entries_size+=pht->entries_size/2;
+      pht->entries=realloc(pht->entries,
+			   sizeof(struct passwd_hash_entry)*pht->entries_size);
+      if (pht->entries==NULL) goto fail;
+    }
+
+    /* Copy the passwd struct */
+    memset(&pht->passwds[num_passwds],0,sizeof(struct passwd));
+    pht->passwds[num_passwds].pw_uid=pass->pw_uid;
+    pht->passwds[num_passwds].pw_gid=pass->pw_gid;  
+    if (
+	(pht->passwds[num_passwds].pw_name=strdup(pass->pw_name))==NULL ||
+	(pht->passwds[num_passwds].pw_passwd=strdup(pass->pw_passwd))==NULL ||
+	(pht->passwds[num_passwds].pw_gecos=strdup(pass->pw_gecos))==NULL ||
+	(pht->passwds[num_passwds].pw_dir=strdup(pass->pw_dir))==NULL ||
+	(pht->passwds[num_passwds].pw_shell=strdup(pass->pw_shell))==NULL ) {
+      num_passwds++;
+      goto fail;
+    }
+    
+    /* Add to the hash table */
+    /* Add the name */
+    pht->entries[num_entries].entry=num_passwds;
+    name_i=name_hash_function(pass->pw_name);
+    pht->entries[num_entries].next=pht->names[name_i];
+    pht->names[name_i]=num_entries;
+    num_entries++;
+    /* Add the uid */
+    pht->entries[num_entries].entry=num_passwds;
+    uid_i=uid_hash_function(pass->pw_uid);
+    pht->entries[num_entries].next=pht->uids[uid_i];
+    pht->uids[uid_i]=num_entries;
+    num_entries++;
+
+    /* This entry has been done */
+    num_passwds++;
+  }    
+  endpwent();
+  
+  if (pht->passwds_size>num_passwds) {
+    struct passwd *passwds;
+    passwds=realloc(pht->passwds,sizeof(pht->passwds[0])*num_passwds);
+    if (passwds==NULL) goto fail;
+    pht->passwds=passwds;
+    pht->passwds_size=num_passwds;
+  }
+  if (pht->entries_size>num_entries) {
+    struct passwd_hash_entry *entries;
+    entries=realloc(pht->entries,sizeof(pht->entries[0])*num_entries);
+    if (entries==NULL) goto fail;
+    pht->entries=entries;
+    pht->entries_size=num_entries;
+  }
+
+  /* Mark the creation time */
+  GetTimeOfDay(&pht->build_time);
+  /* Everything went smoothly. */
+  return True;
+
+ fail:
+  DEBUG(0,("Failed to create passwd hash table: %s",strerror(errno)));
+  /* OK: now the untested part. Normally this should never happen:
+   * Only running out of memory could cause this and even then
+   * we have enough trouble already. */
+  while (num_passwds>0) {
+    num_passwds--;
+    free(pht->passwds[num_passwds].pw_name);
+    free(pht->passwds[num_passwds].pw_passwd);
+    free(pht->passwds[num_passwds].pw_gecos);
+    free(pht->passwds[num_passwds].pw_dir);
+    free(pht->passwds[num_passwds].pw_shell);    
+  }
+  free(pht->entries);
+  free(pht->uids);
+  free(pht->names);
+  free(pht->passwds);
+  pht->passwds_size=0;
+  pht->entries_size=0;    
+  /* Also mark fail time, so that retry will happen after PASSWD_HASH_AGE */
+  GetTimeOfDay(&pht->build_time);
+  return False;
+}
+
+BOOL have_passwd_hash() {
+  struct passwd_hash_table_s *pht=&passwd_hash_table;
+  struct timeval tv;
+  GetTimeOfDay(&tv);
+  /* I'm ignoring microseconds. If you think they matter, go ahead
+   * and implement them */
+  if (tv.tv_sec - pht->build_time.tv_sec > PASSWD_HASH_AGE) {
+    return build_passwd_hash_table();
+  }
+  return pht->passwds_size>0;
+}
+
+struct passwd *hashed_getpwnam(const char *name)
+{
+  struct passwd_hash_table_s *pht=&passwd_hash_table;
+
+  DEBUG(5,("getpwnam(%s)\n", name));
+
+  if (have_passwd_hash()) {
+    int name_i=name_hash_function(name);
+    int index=pht->names[name_i];
+    while(index!=-1) {
+      struct passwd *pass=&pht->passwds[pht->entries[index].entry];
+      if (strcmp(name,pass->pw_name)==0) {
+	DEBUG(5,("Found: %s:%s:%d:%d:%s:%s:%s\n",
+		 pass->pw_name,
+		 pass->pw_passwd,
+		 pass->pw_uid,
+		 pass->pw_gid,
+		 pass->pw_gecos,
+		 pass->pw_dir,
+		 pass->pw_shell));
+	return pass;      
+      }
+      index=pht->entries[index].next;
+    }
+
+    /* Not found */
+    DEBUG(5,("%s not found\n",name));
+    return NULL;
+  } 
+  /* Fall back to real getpwnam() */
+  return getpwnam(name);
+}
+
+/*******************************************************************
+turn a uid into a user name
+********************************************************************/
+char *uidtoname(uid_t uid)
+{
+  static char name[40];
+  struct passwd_hash_table_s *pht=&passwd_hash_table;
+  struct passwd *pass=NULL;
+
+  DEBUG(5,("uidtoname(%d)\n",uid));
+  if (have_passwd_hash()) {
+    int index=pht->uids[uid_hash_function(uid)];
+    while(index!=-1) {
+      pass=&pht->passwds[pht->entries[index].entry];
+      if (pass->pw_uid==uid) {
+	DEBUG(5,("Found: %s:%s:%d:%d:%s:%s:%s\n",
+		 pass->pw_name,
+		 pass->pw_passwd,
+		 pass->pw_uid,
+		 pass->pw_gid,
+		 pass->pw_gecos,
+		 pass->pw_dir,
+		 pass->pw_shell));
+	return pass->pw_name;      
+      }
+      index=pht->entries[index].next;
+    }
+    DEBUG(5,("Hash miss"));
+    pass=NULL;
+  } else {
+    /* No hash table, fall back to getpwuid */
+    pass = getpwuid(uid);
+  }
+  if (pass) return(pass->pw_name);
+  slprintf(name, sizeof(name) - 1, "%d",(int)uid);
+  return(name);
+}
+
+/****************************************************************************
 get a users home directory.
 ****************************************************************************/
 char *get_home_dir(char *user)
@@ -154,7 +424,7 @@
 {
   struct passwd *ret;
 
-  ret = getpwnam(s);
+  ret = hashed_getpwnam(s);
   if (ret)
     {
 #ifdef HAVE_GETPWANAM
diff -x CVS -ru samba/source/lib/util.c samba.patched/source/lib/util.c
--- samba/source/lib/util.c	Mon Apr 12 04:39:46 1999
+++ samba.patched/source/lib/util.c	Thu May  6 17:49:40 1999
@@ -2456,19 +2456,6 @@
 }
 
 /*******************************************************************
-turn a uid into a user name
-********************************************************************/
-char *uidtoname(uid_t uid)
-{
-  static char name[40];
-  struct passwd *pass = getpwuid(uid);
-  if (pass) return(pass->pw_name);
-  slprintf(name, sizeof(name) - 1, "%d",(int)uid);
-  return(name);
-}
-
-
-/*******************************************************************
 turn a gid into a group name
 ********************************************************************/
 
diff -x CVS -ru samba/source/passdb/sampassdb.c samba.patched/source/passdb/sampassdb.c
--- samba/source/passdb/sampassdb.c	Thu Mar 25 23:32:04 1999
+++ samba.patched/source/passdb/sampassdb.c	Thu May  6 12:18:05 1999
@@ -678,7 +678,7 @@
 
 	if (sam->unix_gid == (gid_t)-1 && sam->group_rid == 0xffffffff)
 	{
-		struct passwd *pass = getpwnam(unix_name);
+		struct passwd *pass = hashed_getpwnam(unix_name);
 		if (pass != NULL)
 		{
 			sam->unix_gid = pass->pw_gid;
diff -x CVS -ru samba/source/smbd/password.c samba.patched/source/smbd/password.c
--- samba/source/smbd/password.c	Thu Mar 25 15:54:31 1999
+++ samba.patched/source/smbd/password.c	Thu May  6 19:15:13 1999
@@ -224,7 +224,7 @@
   DEBUG(3, ("Clearing default real name\n"));
   fstrcpy(vuser->real_name, "<Full Name>\0");
   if (lp_unix_realname()) {
-    if ((pwfile=getpwnam(vuser->name))!= NULL)
+    if ((pwfile=hashed_getpwnam(vuser->name))!= NULL)
       {
       DEBUG(3, ("User name: %s\tReal name: %s\n",vuser->name,pwfile->pw_gecos));
       fstrcpy(vuser->real_name, pwfile->pw_gecos);
@@ -586,7 +586,7 @@
 	{
 	  struct passwd *pwd;
 	  static fstring tm;
-	  
+
 	  setpwent ();
 	  while (pwd = getpwent ()) {
 	    if (*(pwd->pw_passwd) && pwd->pw_gid == gptr->gr_gid) {



More information about the samba-technical mailing list