Jani Jaakkola's "getpwuid() / getpwnam()" hash-cache-hack
authorLuke Leighton <lkcl@samba.org>
Thu, 6 May 1999 18:05:45 +0000 (18:05 +0000)
committerLuke Leighton <lkcl@samba.org>
Thu, 6 May 1999 18:05:45 +0000 (18:05 +0000)
source/lib/username.c
source/lib/util.c
source/passdb/sampassdb.c
source/smbd/password.c

index 8ae55fcc894beb0c3f8ee2174476d8a777864dec..4daf30fdd48cce141d478dc83a3a4bfe4cd2be96 100644 (file)
@@ -26,6 +26,276 @@ extern int DEBUGLEVEL;
 static struct passwd *uname_string_combinations(char *s, struct passwd * (*fn) (char *), int N);
 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.
 ****************************************************************************/
@@ -154,7 +424,7 @@ static struct passwd *_Get_Pwnam(char *s)
 {
   struct passwd *ret;
 
-  ret = getpwnam(s);
+  ret = hashed_getpwnam(s);
   if (ret)
     {
 #ifdef HAVE_GETPWANAM
index dcad289071dc4b2e5f470a633ab29ec169ddf5e6..8852ada0cc714b2da83f233a6ca62e64624795f4 100644 (file)
@@ -2455,19 +2455,6 @@ void free_unix_grps(int ngroups, struct group *p_groups)
        free(p_groups);
 }
 
-/*******************************************************************
-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
 ********************************************************************/
index e8a92d778d2a00c63575ccc0540d81655d0e0d27..d5b395a0a180ba35bd6bfb5923824f82b1154363 100644 (file)
@@ -678,7 +678,7 @@ struct sam_passwd *pwdb_sam_map_names(struct sam_passwd *sam)
 
        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;
index 11fe69b103b995c33219996bc10713987d83c5dc..daead8bb826a3ca05eceac8a47f00155c78d97df 100644 (file)
@@ -224,7 +224,7 @@ uint16 register_vuid(uid_t uid,gid_t gid, char *unix_name, char *requested_name,
   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);