lstat_cache(): more cache effective symlink/directory detection
[gitweb.git] / symlinks.c
index 5a5e781a15d7d9cb60797958433eca896b31ec85..49fb4d8bc2fc7fba70e18c9d9ec54cb5ba590da5 100644 (file)
 #include "cache.h"
 
-struct pathname {
-       int len;
+static struct cache_def {
        char path[PATH_MAX];
-};
+       int len;
+       int flags;
+} cache;
 
-/* Return matching pathname prefix length, or zero if not matching */
-static inline int match_pathname(int len, const char *name, struct pathname *match)
+/*
+ * Returns the length (on a path component basis) of the longest
+ * common prefix match of 'name' and the cached path string.
+ */
+static inline int longest_match_lstat_cache(int len, const char *name)
 {
-       int match_len = match->len;
-       return (len > match_len &&
-               name[match_len] == '/' &&
-               !memcmp(name, match->path, match_len)) ? match_len : 0;
+       int max_len, match_len = 0, i = 0;
+
+       max_len = len < cache.len ? len : cache.len;
+       while (i < max_len && name[i] == cache.path[i]) {
+               if (name[i] == '/')
+                       match_len = i;
+               i++;
+       }
+       /* Is the cached path string a substring of 'name'? */
+       if (i == cache.len && cache.len < len && name[cache.len] == '/')
+               match_len = cache.len;
+       /* Is 'name' a substring of the cached path string? */
+       else if ((i == len && len < cache.len && cache.path[len] == '/') ||
+                (i == len && len == cache.len))
+               match_len = len;
+       return match_len;
 }
 
-static inline void set_pathname(int len, const char *name, struct pathname *match)
+static inline void reset_lstat_cache(void)
 {
-       if (len < PATH_MAX) {
-               match->len = len;
-               memcpy(match->path, name, len);
-               match->path[len] = 0;
-       }
+       cache.path[0] = '\0';
+       cache.len = 0;
+       cache.flags = 0;
 }
 
-int has_symlink_leading_path(int len, const char *name)
+#define FL_DIR      (1 << 0)
+#define FL_SYMLINK  (1 << 1)
+#define FL_LSTATERR (1 << 2)
+#define FL_ERR      (1 << 3)
+
+/*
+ * Check if name 'name' of length 'len' has a symlink leading
+ * component, or if the directory exists and is real.
+ *
+ * To speed up the check, some information is allowed to be cached.
+ * This can be indicated by the 'track_flags' argument.
+ */
+static int lstat_cache(int len, const char *name,
+                      int track_flags)
 {
-       static struct pathname link, nonlink;
-       char path[PATH_MAX];
+       int match_len, last_slash, last_slash_dir;
+       int match_flags, ret_flags, save_flags, max_len;
        struct stat st;
-       char *sp;
-       int known_dir;
 
        /*
-        * See if the last known symlink cache matches.
+        * Check to see if we have a match from the cache for the
+        * symlink path type.
         */
-       if (match_pathname(len, name, &link))
-               return 1;
-
+       match_len = last_slash = longest_match_lstat_cache(len, name);
+       match_flags = cache.flags & track_flags & FL_SYMLINK;
+       if (match_flags && match_len == cache.len)
+               return match_flags;
        /*
-        * Get rid of the last known directory part
+        * If we now have match_len > 0, we would know that the
+        * matched part will always be a directory.
+        *
+        * Also, if we are tracking directories and 'name' is a
+        * substring of the cache on a path component basis, we can
+        * return immediately.
         */
-       known_dir = match_pathname(len, name, &nonlink);
+       match_flags = track_flags & FL_DIR;
+       if (match_flags && len == match_len)
+               return match_flags;
 
-       while ((sp = strchr(name + known_dir + 1, '/')) != NULL) {
-               int thislen = sp - name ;
-               memcpy(path, name, thislen);
-               path[thislen] = 0;
+       /*
+        * Okay, no match from the cache so far, so now we have to
+        * check the rest of the path components.
+        */
+       ret_flags = FL_DIR;
+       last_slash_dir = last_slash;
+       max_len = len < PATH_MAX ? len : PATH_MAX;
+       while (match_len < max_len) {
+               do {
+                       cache.path[match_len] = name[match_len];
+                       match_len++;
+               } while (match_len < max_len && name[match_len] != '/');
+               if (match_len >= max_len)
+                       break;
+               last_slash = match_len;
+               cache.path[last_slash] = '\0';
 
-               if (lstat(path, &st))
-                       return 0;
-               if (S_ISDIR(st.st_mode)) {
-                       set_pathname(thislen, path, &nonlink);
-                       known_dir = thislen;
+               if (lstat(cache.path, &st)) {
+                       ret_flags = FL_LSTATERR;
+               } else if (S_ISDIR(st.st_mode)) {
+                       last_slash_dir = last_slash;
                        continue;
-               }
-               if (S_ISLNK(st.st_mode)) {
-                       set_pathname(thislen, path, &link);
-                       return 1;
+               } else if (S_ISLNK(st.st_mode)) {
+                       ret_flags = FL_SYMLINK;
+               } else {
+                       ret_flags = FL_ERR;
                }
                break;
        }
-       return 0;
+
+       /*
+        * At the end update the cache.  Note that max 2 different
+        * path types, FL_SYMLINK and FL_DIR, can be cached for the
+        * moment!
+        */
+       save_flags = ret_flags & track_flags & FL_SYMLINK;
+       if (save_flags && last_slash > 0 && last_slash < PATH_MAX) {
+               cache.path[last_slash] = '\0';
+               cache.len = last_slash;
+               cache.flags = save_flags;
+       } else if (track_flags & FL_DIR &&
+                  last_slash_dir > 0 && last_slash_dir < PATH_MAX) {
+               /*
+                * We have a separate test for the directory case,
+                * since it could be that we have found a symlink and
+                * the track_flags says that we cannot cache this
+                * fact, so the cache would then have been left empty
+                * in this case.
+                *
+                * But if we are allowed to track real directories, we
+                * can still cache the path components before the last
+                * one (the found symlink component).
+                */
+               cache.path[last_slash_dir] = '\0';
+               cache.len = last_slash_dir;
+               cache.flags = FL_DIR;
+       } else {
+               reset_lstat_cache();
+       }
+       return ret_flags;
+}
+
+/*
+ * Return non-zero if path 'name' has a leading symlink component
+ */
+int has_symlink_leading_path(int len, const char *name)
+{
+       return lstat_cache(len, name,
+                          FL_SYMLINK|FL_DIR) &
+               FL_SYMLINK;
 }