1#include "cache.h"23/*4* Returns the length (on a path component basis) of the longest5* common prefix match of 'name_a' and 'name_b'.6*/7static int longest_path_match(const char *name_a, int len_a,8const char *name_b, int len_b,9int *previous_slash)10{11int max_len, match_len = 0, match_len_prev = 0, i = 0;1213max_len = len_a < len_b ? len_a : len_b;14while (i < max_len && name_a[i] == name_b[i]) {15if (name_a[i] == '/') {16match_len_prev = match_len;17match_len = i;18}19i++;20}21/*22* Is 'name_b' a substring of 'name_a', the other way around,23* or is 'name_a' and 'name_b' the exact same string?24*/25if (i >= max_len && ((len_a > len_b && name_a[len_b] == '/') ||26(len_a < len_b && name_b[len_a] == '/') ||27(len_a == len_b))) {28match_len_prev = match_len;29match_len = i;30}31*previous_slash = match_len_prev;32return match_len;33}3435static struct cache_def {36char path[PATH_MAX + 1];37int len;38int flags;39int track_flags;40int prefix_len_stat_func;41} cache;4243static inline void reset_lstat_cache(void)44{45cache.path[0] = '\0';46cache.len = 0;47cache.flags = 0;48/*49* The track_flags and prefix_len_stat_func members is only50* set by the safeguard rule inside lstat_cache()51*/52}5354#define FL_DIR (1 << 0)55#define FL_NOENT (1 << 1)56#define FL_SYMLINK (1 << 2)57#define FL_LSTATERR (1 << 3)58#define FL_ERR (1 << 4)59#define FL_FULLPATH (1 << 5)6061/*62* Check if name 'name' of length 'len' has a symlink leading63* component, or if the directory exists and is real, or not.64*65* To speed up the check, some information is allowed to be cached.66* This can be indicated by the 'track_flags' argument, which also can67* be used to indicate that we should check the full path.68*69* The 'prefix_len_stat_func' parameter can be used to set the length70* of the prefix, where the cache should use the stat() function71* instead of the lstat() function to test each path component.72*/73static int lstat_cache(const char *name, int len,74int track_flags, int prefix_len_stat_func)75{76int match_len, last_slash, last_slash_dir, previous_slash;77int match_flags, ret_flags, save_flags, max_len, ret;78struct stat st;7980if (cache.track_flags != track_flags ||81cache.prefix_len_stat_func != prefix_len_stat_func) {82/*83* As a safeguard rule we clear the cache if the84* values of track_flags and/or prefix_len_stat_func85* does not match with the last supplied values.86*/87reset_lstat_cache();88cache.track_flags = track_flags;89cache.prefix_len_stat_func = prefix_len_stat_func;90match_len = last_slash = 0;91} else {92/*93* Check to see if we have a match from the cache for94* the 2 "excluding" path types.95*/96match_len = last_slash =97longest_path_match(name, len, cache.path, cache.len,98&previous_slash);99match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK);100if (match_flags && match_len == cache.len)101return match_flags;102/*103* If we now have match_len > 0, we would know that104* the matched part will always be a directory.105*106* Also, if we are tracking directories and 'name' is107* a substring of the cache on a path component basis,108* we can return immediately.109*/110match_flags = track_flags & FL_DIR;111if (match_flags && len == match_len)112return match_flags;113}114115/*116* Okay, no match from the cache so far, so now we have to117* check the rest of the path components.118*/119ret_flags = FL_DIR;120last_slash_dir = last_slash;121max_len = len < PATH_MAX ? len : PATH_MAX;122while (match_len < max_len) {123do {124cache.path[match_len] = name[match_len];125match_len++;126} while (match_len < max_len && name[match_len] != '/');127if (match_len >= max_len && !(track_flags & FL_FULLPATH))128break;129last_slash = match_len;130cache.path[last_slash] = '\0';131132if (last_slash <= prefix_len_stat_func)133ret = stat(cache.path, &st);134else135ret = lstat(cache.path, &st);136137if (ret) {138ret_flags = FL_LSTATERR;139if (errno == ENOENT)140ret_flags |= FL_NOENT;141} else if (S_ISDIR(st.st_mode)) {142last_slash_dir = last_slash;143continue;144} else if (S_ISLNK(st.st_mode)) {145ret_flags = FL_SYMLINK;146} else {147ret_flags = FL_ERR;148}149break;150}151152/*153* At the end update the cache. Note that max 3 different154* path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached155* for the moment!156*/157save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK);158if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) {159cache.path[last_slash] = '\0';160cache.len = last_slash;161cache.flags = save_flags;162} else if ((track_flags & FL_DIR) &&163last_slash_dir > 0 && last_slash_dir <= PATH_MAX) {164/*165* We have a separate test for the directory case,166* since it could be that we have found a symlink or a167* non-existing directory and the track_flags says168* that we cannot cache this fact, so the cache would169* then have been left empty in this case.170*171* But if we are allowed to track real directories, we172* can still cache the path components before the last173* one (the found symlink or non-existing component).174*/175cache.path[last_slash_dir] = '\0';176cache.len = last_slash_dir;177cache.flags = FL_DIR;178} else {179reset_lstat_cache();180}181return ret_flags;182}183184/*185* Invalidate the given 'name' from the cache, if 'name' matches186* completely with the cache.187*/188void invalidate_lstat_cache(const char *name, int len)189{190int match_len, previous_slash;191192match_len = longest_path_match(name, len, cache.path, cache.len,193&previous_slash);194if (len == match_len) {195if ((cache.track_flags & FL_DIR) && previous_slash > 0) {196cache.path[previous_slash] = '\0';197cache.len = previous_slash;198cache.flags = FL_DIR;199} else200reset_lstat_cache();201}202}203204/*205* Completely clear the contents of the cache206*/207void clear_lstat_cache(void)208{209reset_lstat_cache();210}211212#define USE_ONLY_LSTAT 0213214/*215* Return non-zero if path 'name' has a leading symlink component216*/217int has_symlink_leading_path(const char *name, int len)218{219return lstat_cache(name, len,220FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) &221FL_SYMLINK;222}223224/*225* Return non-zero if path 'name' has a leading symlink component or226* if some leading path component does not exists.227*/228int has_symlink_or_noent_leading_path(const char *name, int len)229{230return lstat_cache(name, len,231FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) &232(FL_SYMLINK|FL_NOENT);233}234235/*236* Return non-zero if all path components of 'name' exists as a237* directory. If prefix_len > 0, we will test with the stat()238* function instead of the lstat() function for a prefix length of239* 'prefix_len', thus we then allow for symlinks in the prefix part as240* long as those points to real existing directories.241*/242int has_dirs_only_path(const char *name, int len, int prefix_len)243{244return lstat_cache(name, len,245FL_DIR|FL_FULLPATH, prefix_len) &246FL_DIR;247}