r23784: use the GPLv3 boilerplate as recommended by the FSF and the license text
[tprouty/samba.git] / source / smbd / statcache.c
index 7b8701337ff61b7d4e99ef52cd3407ddb7252ecf..51e8c0417a3fa6ed0452d67d51eabc5d8150002d 100644 (file)
@@ -1,14 +1,13 @@
 /* 
-   Unix SMB/Netbios implementation.
-   Version 3.0
+   Unix SMB/CIFS implementation.
    stat cache code
    Copyright (C) Andrew Tridgell 1992-2000
-   Copyright (C) Jeremy Allison 1999-200
-   
+   Copyright (C) Jeremy Allison 1999-2004
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
 
-extern BOOL case_sensitive;
-
-
 /****************************************************************************
  Stat cache code used in unix_convert.
 *****************************************************************************/
 
-static int global_stat_cache_lookups;
-static int global_stat_cache_misses;
-static int global_stat_cache_hits;
+static TDB_CONTEXT *tdb_stat_cache;
 
-/****************************************************************************
- Stat cache statistics code.
-*****************************************************************************/
+/**
+ * Add an entry into the stat cache.
+ *
+ * @param full_orig_name       The original name as specified by the client
+ * @param orig_translated_path The name on our filesystem.
+ * 
+ * @note Only the first strlen(orig_translated_path) characters are stored 
+ *       into the cache.  This means that full_orig_name will be internally
+ *       truncated.
+ *
+ */
 
-void print_stat_cache_statistics(void)
+void stat_cache_add( const char *full_orig_name, const char *orig_translated_path, BOOL case_sensitive)
 {
-  double eff;
+       char *translated_path;
+       size_t translated_path_length;
+       TDB_DATA data_val;
+       char *original_path;
+       size_t original_path_length;
+       size_t sc_size = lp_max_stat_cache_size();
+
+       if (!lp_stat_cache())
+               return;
+
+       if (sc_size && (tdb_map_size(tdb_stat_cache) > sc_size*1024)) {
+               reset_stat_cache();
+       }
 
-  if(global_stat_cache_lookups == 0)
-    return;
+       ZERO_STRUCT(data_val);
 
-  eff = (100.0* (double)global_stat_cache_hits)/(double)global_stat_cache_lookups;
+       /*
+        * Don't cache trivial valid directory entries such as . and ..
+        */
 
-  DEBUG(0,("stat cache stats: lookups = %d, hits = %d, misses = %d, \
-stat cache was %f%% effective.\n", global_stat_cache_lookups,
-       global_stat_cache_hits, global_stat_cache_misses, eff ));
-}
+       if((*full_orig_name == '\0') || (full_orig_name[0] == '.' && 
+                               ((full_orig_name[1] == '\0') ||
+                                (full_orig_name[1] == '.' && full_orig_name[2] == '\0'))))
+               return;
 
-typedef struct {
-  int name_len;
-  char names[2]; /* This is extended via malloc... */
-} stat_cache_entry;
+       /*
+        * If we are in case insentive mode, we don't need to
+        * store names that need no translation - else, it
+        * would be a waste.
+        */
 
-#define INIT_STAT_CACHE_SIZE 512
-static hash_table stat_cache;
+       if(case_sensitive && (strcmp(full_orig_name, orig_translated_path) == 0))
+               return;
 
-/****************************************************************************
- Add an entry into the stat cache.
-*****************************************************************************/
+       /*
+        * Remove any trailing '/' characters from the
+        * translated path.
+        */
 
-void stat_cache_add( char *full_orig_name, char *orig_translated_path)
-{
-  stat_cache_entry *scp;
-  stat_cache_entry *found_scp;
-  pstring orig_name;
-  pstring translated_path;
-  int namelen;
-  hash_element *hash_elem;
-
-  if (!lp_stat_cache()) return;
-
-  namelen = strlen(orig_translated_path);
-
-  /*
-   * Don't cache trivial valid directory entries.
-   */
-  if((*full_orig_name == '\0') || (strcmp(full_orig_name, ".") == 0) ||
-     (strcmp(full_orig_name, "..") == 0))
-    return;
-
-  /*
-   * If we are in case insentive mode, we need to
-   * store names that need no translation - else, it
-   * would be a waste.
-   */
-
-  if(case_sensitive && (strcmp(full_orig_name, orig_translated_path) == 0))
-    return;
-
-  /*
-   * Remove any trailing '/' characters from the
-   * translated path.
-   */
-
-  pstrcpy(translated_path, orig_translated_path);
-  if(translated_path[namelen-1] == '/') {
-    translated_path[namelen-1] = '\0';
-    namelen--;
-  }
-
-  /*
-   * We will only replace namelen characters 
-   * of full_orig_name.
-   * StrnCpy always null terminates.
-   */
-
-  StrnCpy(orig_name, full_orig_name, namelen);
-  if(!case_sensitive)
-    strupper( orig_name );
-
-  /*
-   * Check this name doesn't exist in the cache before we 
-   * add it.
-   */
-
-  if ((hash_elem = hash_lookup(&stat_cache, orig_name))) {
-    found_scp = (stat_cache_entry *)(hash_elem->value);
-    if (strcmp((found_scp->names+found_scp->name_len+1), translated_path) == 0) {
-      return;
-    } else {
-      hash_remove(&stat_cache, hash_elem);
-      if((scp = (stat_cache_entry *)malloc(sizeof(stat_cache_entry)+2*namelen)) == NULL) {
-        DEBUG(0,("stat_cache_add: Out of memory !\n"));
-        return;
-      }
-      pstrcpy(scp->names, orig_name);
-      pstrcpy((scp->names+namelen+1), translated_path);
-      scp->name_len = namelen;
-      hash_insert(&stat_cache, (char *)scp, orig_name);
-    }
-    return;
-  } else {
-
-    /*
-     * New entry.
-     */
-
-    if((scp = (stat_cache_entry *)malloc(sizeof(stat_cache_entry)+2*namelen)) == NULL) {
-      DEBUG(0,("stat_cache_add: Out of memory !\n"));
-      return;
-    }
-    pstrcpy(scp->names, orig_name);
-    pstrcpy(scp->names+namelen+1, translated_path);
-    scp->name_len = namelen;
-    hash_insert(&stat_cache, (char *)scp, orig_name);
-  }
-
-  DEBUG(5,("stat_cache_add: Added entry %s -> %s\n", scp->names, (scp->names+scp->name_len+1)));
+       translated_path = SMB_STRDUP(orig_translated_path);
+       if (!translated_path)
+               return;
+
+       translated_path_length = strlen(translated_path);
+
+       if(translated_path[translated_path_length-1] == '/') {
+               translated_path[translated_path_length-1] = '\0';
+               translated_path_length--;
+       }
+
+       if(case_sensitive) {
+               original_path = SMB_STRDUP(full_orig_name);
+       } else {
+               original_path = strdup_upper(full_orig_name);
+       }
+
+       if (!original_path) {
+               SAFE_FREE(translated_path);
+               return;
+       }
+
+       original_path_length = strlen(original_path);
+
+       if(original_path[original_path_length-1] == '/') {
+               original_path[original_path_length-1] = '\0';
+               original_path_length--;
+       }
+
+       if (original_path_length != translated_path_length) {
+               if (original_path_length < translated_path_length) {
+                       DEBUG(0, ("OOPS - tried to store stat cache entry for weird length paths [%s] %lu and [%s] %lu)!\n",
+                                 original_path, (unsigned long)original_path_length, translated_path, (unsigned long)translated_path_length));
+                       SAFE_FREE(original_path);
+                       SAFE_FREE(translated_path);
+                       return;
+               }
+
+               /* we only want to index by the first part of original_path,
+                       up to the length of translated_path */
+
+               original_path[translated_path_length] = '\0';
+               original_path_length = translated_path_length;
+       }
+
+       /*
+        * New entry or replace old entry.
+        */
+  
+       data_val.dsize = translated_path_length + 1;
+       data_val.dptr = (uint8 *)translated_path;
+
+       if (tdb_store_bystring(tdb_stat_cache, original_path, data_val, TDB_REPLACE) != 0) {
+               DEBUG(0,("stat_cache_add: Error storing entry %s -> %s\n", original_path, translated_path));
+       } else {
+               DEBUG(5,("stat_cache_add: Added entry (%lx:size%x) %s -> %s\n",
+                       (unsigned long)data_val.dptr, (unsigned int)data_val.dsize, original_path, translated_path));
+       }
+
+       SAFE_FREE(original_path);
+       SAFE_FREE(translated_path);
 }
 
-/****************************************************************************
- Look through the stat cache for an entry - promote it to the top if found.
- Return True if we translated (and did a scuccessful stat on) the entire name.
-*****************************************************************************/
+/**
+ * Look through the stat cache for an entry
+ *
+ * @param conn    A connection struct to do the stat() with.
+ * @param name    The path we are attempting to cache, modified by this routine
+ *                to be correct as far as the cache can tell us
+ * @param dirpath The path as far as the stat cache told us.
+ * @param start   A pointer into name, for where to 'start' in fixing the rest of the name up.
+ * @param psd     A stat buffer, NOT from the cache, but just a side-effect.
+ *
+ * @return True if we translated (and did a scuccessful stat on) the entire name.
+ *
+ */
 
-BOOL stat_cache_lookup(connection_struct *conn, char *name, char *dirpath, 
+BOOL stat_cache_lookup(connection_struct *conn, pstring name, pstring dirpath, 
                       char **start, SMB_STRUCT_STAT *pst)
 {
-  stat_cache_entry *scp;
-  char *trans_name;
-  pstring chk_name;
-  int namelen;
-  hash_element *hash_elem;
-  char *sp;
-
-  if (!lp_stat_cache())
-    return False;
+       char *chk_name;
+       size_t namelen;
+       BOOL sizechanged = False;
+       unsigned int num_components = 0;
+       char *translated_path;
+       size_t translated_path_length;
+       TDB_DATA data_val;
+
+       if (!lp_stat_cache())
+               return False;
  
-  namelen = strlen(name);
-
-  *start = name;
-  global_stat_cache_lookups++;
-
-  /*
-   * Don't lookup trivial valid directory entries.
-   */
-  if((*name == '\0') || (strcmp(name, ".") == 0) || (strcmp(name, "..") == 0)) {
-    global_stat_cache_misses++;
-    return False;
-  }
-
-  pstrcpy(chk_name, name);
-  if(!case_sensitive)
-    strupper( chk_name );
-
-  while (1) {
-    hash_elem = hash_lookup(&stat_cache, chk_name);
-    if(hash_elem == NULL) {
-      /*
-       * Didn't find it - remove last component for next try.
-       */
-      sp = strrchr(chk_name, '/');
-      if (sp) {
-        *sp = '\0';
-      } else {
-        /*
-         * We reached the end of the name - no match.
-         */
-        global_stat_cache_misses++;
-        return False;
-      }
-      if((*chk_name == '\0') || (strcmp(chk_name, ".") == 0)
-                          || (strcmp(chk_name, "..") == 0)) {
-        global_stat_cache_misses++;
-        return False;
-      }
-    } else {
-      scp = (stat_cache_entry *)(hash_elem->value);
-      global_stat_cache_hits++;
-      trans_name = scp->names+scp->name_len+1;
-      if(conn->vfs_ops.stat(dos_to_unix(trans_name,False), pst) != 0) {
-        /* Discard this entry - it doesn't exist in the filesystem.  */
-        hash_remove(&stat_cache, hash_elem);
-        return False;
-      }
-      memcpy(name, trans_name, scp->name_len);
-      *start = &name[scp->name_len];
-      if(**start == '/')
-        ++*start;
-      StrnCpy( dirpath, trans_name, name - (*start));
-      return (namelen == scp->name_len);
-    }
-  }
+       namelen = strlen(name);
+
+       *start = name;
+
+       DO_PROFILE_INC(statcache_lookups);
+
+       /*
+        * Don't lookup trivial valid directory entries.
+        */
+       if((*name == '\0') || (name[0] == '.' && 
+                               ((name[1] == '\0') ||
+                                (name[1] == '.' && name[1] == '\0'))))
+               return False;
+
+       if (conn->case_sensitive) {
+               chk_name = SMB_STRDUP(name);
+               if (!chk_name) {
+                       DEBUG(0, ("stat_cache_lookup: strdup failed!\n"));
+                       return False;
+               }
+
+       } else {
+               chk_name = strdup_upper(name);
+               if (!chk_name) {
+                       DEBUG(0, ("stat_cache_lookup: strdup_upper failed!\n"));
+                       return False;
+               }
+
+               /*
+                * In some language encodings the length changes
+                * if we uppercase. We need to treat this differently
+                * below.
+                */
+               if (strlen(chk_name) != namelen)
+                       sizechanged = True;
+       }
+
+       while (1) {
+               char *sp;
+
+               data_val = tdb_fetch_bystring(tdb_stat_cache, chk_name);
+
+               if (data_val.dptr != NULL && data_val.dsize != 0) {
+                       break;
+               }
+
+               DEBUG(10,("stat_cache_lookup: lookup failed for name [%s]\n", chk_name ));
+               /*
+                * Didn't find it - remove last component for next try.
+                */
+               if (!(sp = strrchr_m(chk_name, '/'))) {
+                       /*
+                        * We reached the end of the name - no match.
+                        */
+                       DO_PROFILE_INC(statcache_misses);
+                       SAFE_FREE(chk_name);
+                       return False;
+               }
+
+               *sp = '\0';
+
+               /*
+                * Count the number of times we have done this, we'll
+                * need it when reconstructing the string.
+                */
+               if (sizechanged)
+                       num_components++;
+
+               if ((*chk_name == '\0')
+                   || ISDOT(chk_name) || ISDOTDOT(chk_name)) {
+                       DO_PROFILE_INC(statcache_misses);
+                       SAFE_FREE(chk_name);
+                       return False;
+               }
+       }
+
+       translated_path = (char *)data_val.dptr;
+       translated_path_length = data_val.dsize - 1;
+
+       DEBUG(10,("stat_cache_lookup: lookup succeeded for name [%s] "
+                 "-> [%s]\n", chk_name, translated_path ));
+       DO_PROFILE_INC(statcache_hits);
+
+       if (SMB_VFS_STAT(conn, translated_path, pst) != 0) {
+               /* Discard this entry - it doesn't exist in the filesystem. */
+               tdb_delete_bystring(tdb_stat_cache, chk_name);
+               SAFE_FREE(chk_name);
+               SAFE_FREE(data_val.dptr);
+               return False;
+       }
+
+       if (!sizechanged) {
+               memcpy(name, translated_path,
+                      MIN(sizeof(pstring)-1, translated_path_length));
+       } else if (num_components == 0) {
+               pstrcpy(name, translated_path);
+       } else {
+               char *sp;
+
+               sp = strnrchr_m(name, '/', num_components);
+               if (sp) {
+                       pstring last_component;
+                       pstrcpy(last_component, sp);
+                       pstrcpy(name, translated_path);
+                       pstrcat(name, last_component);
+               } else {
+                       pstrcpy(name, translated_path);
+               }
+       }
+
+       /* set pointer for 'where to start' on fixing the rest of the name */
+       *start = &name[translated_path_length];
+       if (**start == '/')
+               ++*start;
+
+       pstrcpy(dirpath, translated_path);
+       SAFE_FREE(chk_name);
+       SAFE_FREE(data_val.dptr);
+       return (namelen == translated_path_length);
 }
 
-/*************************************************************************** **
- * Initializes or clears the stat cache.
- *
- *  Input:  none.
- *  Output: none.
- *
- * ************************************************************************** **
- */
+/***************************************************************************
+ Tell all smbd's to delete an entry.
+**************************************************************************/
+
+void send_stat_cache_delete_message(const char *name)
+{
+#ifdef DEVELOPER
+       message_send_all(smbd_messaging_context(),
+                       MSG_SMB_STAT_CACHE_DELETE,
+                       name,
+                       strlen(name)+1,
+                       NULL);
+#endif
+}
+
+/***************************************************************************
+ Delete an entry.
+**************************************************************************/
+
+void stat_cache_delete(const char *name)
+{
+       char *lname = strdup_upper(name);
+
+       if (!lname) {
+               return;
+       }
+       DEBUG(10,("stat_cache_delete: deleting name [%s] -> %s\n",
+                       lname, name ));
+
+       tdb_delete_bystring(tdb_stat_cache, lname);
+       SAFE_FREE(lname);
+}
+
+/***************************************************************
+ Compute a hash value based on a string key value.
+ The function returns the bucket index number for the hashed key.
+ JRA. Use a djb-algorithm hash for speed.
+***************************************************************/
+                                                                                                     
+unsigned int fast_string_hash(TDB_DATA *key)
+{
+        unsigned int n = 0;
+        const char *p;
+        for (p = (const char *)key->dptr; *p != '\0'; p++) {
+                n = ((n << 5) + n) ^ (unsigned int)(*p);
+        }
+        return n;
+}
+
+/***************************************************************************
+ Initializes or clears the stat cache.
+**************************************************************************/
+
 BOOL reset_stat_cache( void )
 {
-       static BOOL initialised;
-       if (!lp_stat_cache()) return True;
+       if (!lp_stat_cache())
+               return True;
 
-       if (!initialised) {
-               initialised = True;
-               return hash_table_init( &stat_cache, INIT_STAT_CACHE_SIZE, (compare_function)(strcmp));
+       if (tdb_stat_cache) {
+               tdb_close(tdb_stat_cache);
        }
-       hash_clear(&stat_cache);
+
+       /* Create the in-memory tdb using our custom hash function. */
+       tdb_stat_cache = tdb_open_ex("statcache", 1031, TDB_INTERNAL,
+                                    (O_RDWR|O_CREAT), 0644, NULL, fast_string_hash);
+
+       if (!tdb_stat_cache)
+               return False;
        return True;
-} /* reset_stat_cache  */
+}