diff --git a/include/ntfs-3g/dir.h b/include/ntfs-3g/dir.h index a8e0ba36..f116cd6a 100644 --- a/include/ntfs-3g/dir.h +++ b/include/ntfs-3g/dir.h @@ -75,7 +75,8 @@ extern ntfs_inode *ntfs_create_device(ntfs_inode *dir_ni, le32 securid, extern ntfs_inode *ntfs_create_symlink(ntfs_inode *dir_ni, le32 securid, ntfschar *name, u8 name_len, ntfschar *target, u8 target_len); extern int ntfs_check_empty_dir(ntfs_inode *ni); -extern int ntfs_delete(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, +extern int ntfs_delete(ntfs_volume *vol, const char *path, + ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, u8 name_len); extern int ntfs_link(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, diff --git a/include/ntfs-3g/misc.h b/include/ntfs-3g/misc.h index 67cc1eb6..5928ddda 100644 --- a/include/ntfs-3g/misc.h +++ b/include/ntfs-3g/misc.h @@ -1,6 +1,46 @@ #ifndef _NTFS_MISC_H_ #define _NTFS_MISC_H_ +#include "volume.h" + +struct CACHED_GENERIC { + struct CACHED_GENERIC *next; + char *pathname; + void *fixed[0]; +} ; + +struct CACHED_INODE { + struct CACHED_INODE *next; + char *pathname; + u64 inum; +} ; + +typedef int (*cache_compare)(const struct CACHED_GENERIC *cached, + const struct CACHED_GENERIC *item); + +struct CACHE_HEADER { + const char *name; + struct CACHED_GENERIC *most_recent_entry; + struct CACHED_GENERIC *free_entry; + unsigned long reads; + unsigned long writes; + unsigned long hits; + int fixed_size; + struct CACHED_GENERIC entry[0]; +} ; + + /* cast to generic, avoiding gcc warnings */ +#define GENERIC(pstr) ((const struct CACHED_GENERIC*)(const void*)(pstr)) + +struct CACHED_GENERIC *ntfs_fetch_cache(struct CACHE_HEADER *cache, + const struct CACHED_GENERIC *wanted, cache_compare compare); +struct CACHED_GENERIC *ntfs_enter_cache(struct CACHE_HEADER *cache, + const struct CACHED_GENERIC *item, cache_compare compare); +int ntfs_invalidate_cache(struct CACHE_HEADER *cache, + const struct CACHED_GENERIC *item, cache_compare compare); +void ntfs_create_lru_caches(ntfs_volume *vol); +void ntfs_free_lru_caches(ntfs_volume *vol); + void *ntfs_calloc(size_t size); void *ntfs_malloc(size_t size); diff --git a/include/ntfs-3g/security.h b/include/ntfs-3g/security.h index 070e1c83..88d60426 100644 --- a/include/ntfs-3g/security.h +++ b/include/ntfs-3g/security.h @@ -59,9 +59,10 @@ struct CACHED_PERMISSIONS { */ struct CACHED_PERMISSIONS_LEGACY { - struct CACHED_PERMISSIONS permissions; struct CACHED_PERMISSIONS_LEGACY *next; + char *unused; u64 mft_no; + struct CACHED_PERMISSIONS perm; } ; /* @@ -70,6 +71,7 @@ struct CACHED_PERMISSIONS_LEGACY { struct CACHED_SECURID { struct CACHED_SECURID *next; + void *unused; uid_t uid; gid_t gid; unsigned int dmode; @@ -80,29 +82,20 @@ struct CACHED_SECURID { * Header of the security cache */ -struct SECURITY_HEAD { +struct CACHED_PERMISSIONS_HEADER { unsigned int last; - struct CACHED_SECURID *first_securid; - struct CACHED_SECURID *most_recent_securid; - struct CACHED_PERMISSIONS_LEGACY *first_legacy; - struct CACHED_PERMISSIONS_LEGACY *most_recent_legacy; /* statistics for permissions */ unsigned long p_writes; unsigned long p_reads; unsigned long p_hits; - /* statistics for securids */ - unsigned long s_writes; - unsigned long s_reads; - unsigned long s_hits; - unsigned long s_hops; } ; /* - * The whole security cache + * The whole permissions cache */ -struct SECURITY_CACHE { - struct SECURITY_HEAD head; +struct PERMISSIONS_CACHE { + struct CACHED_PERMISSIONS_HEADER head; struct CACHED_PERMISSIONS *cachetable[1]; /* array of variable size */ } ; @@ -122,7 +115,7 @@ struct SECURITY_CONTEXT { ntfs_volume *vol; struct MAPPING *usermapping; struct MAPPING *groupmapping; - struct SECURITY_CACHE **pseccache; + struct PERMISSIONS_CACHE **pseccache; uid_t uid; /* uid of user requesting (not the mounter) */ gid_t gid; /* gid of user requesting (not the mounter) */ } ; @@ -189,7 +182,7 @@ void ntfs_close_secure(struct SECURITY_CONTEXT *scx); struct SECURITY_API { u32 magic; struct SECURITY_CONTEXT security; - struct SECURITY_CACHE *seccache; + struct PERMISSIONS_CACHE *seccache; } ; /* diff --git a/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h index 8efd6ad0..d9538828 100644 --- a/include/ntfs-3g/volume.h +++ b/include/ntfs-3g/volume.h @@ -42,6 +42,10 @@ #include #endif +#define CACHE_INODE_SIZE 32 /* inode cache, zero or >= 3 and not too big */ +#define CACHE_SECURID_SIZE 16 /* securid cache, zero or >= 3 and not too big */ +#define CACHE_LEGACY_SIZE 8 /* legacy cache size, zero or >= 3 and not too big */ + /* * Under Cygwin, DJGPP and FreeBSD we do not have MS_RDONLY, * so we define them ourselves. @@ -207,6 +211,16 @@ struct _ntfs_volume { greatly improves statfs() performance */ s64 free_mft_records; /* Same for free mft records (see above) */ +#if CACHE_INODE_SIZE + struct CACHE_HEADER *inode_cache; +#endif +#if CACHE_SECURID_SIZE + struct CACHE_HEADER *securid_cache; +#endif +#if CACHE_LEGACY_SIZE + struct CACHE_HEADER *legacy_cache; +#endif + /* Temp: for directory handling */ void *private_data; /* ntfs_dir for . */ void *private_bmp1; /* ntfs_bmp for $MFT/$BITMAP */ diff --git a/libntfs-3g/dir.c b/libntfs-3g/dir.c index 22f33bf2..cdca9141 100644 --- a/libntfs-3g/dir.c +++ b/libntfs-3g/dir.c @@ -5,6 +5,7 @@ * Copyright (c) 2004-2005 Richard Russon * Copyright (c) 2004-2006 Szabolcs Szakacsits * Copyright (c) 2005-2006 Yura Pakhuchiy + * Copyright (c) 2008 Jean-Pierre Andre * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published @@ -77,6 +78,62 @@ ntfschar NTFS_INDEX_Q[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('Q'), ntfschar NTFS_INDEX_R[3] = { const_cpu_to_le16('$'), const_cpu_to_le16('R'), const_cpu_to_le16('\0') }; +#if CACHE_INODE_SIZE + +/* + * Pathname comparing for entering/fetching from cache + */ + +static int inode_cache_compare(const struct CACHED_GENERIC *cached, + const struct CACHED_GENERIC *wanted) +{ + return (strcmp(cached->pathname, wanted->pathname)); +} + +/* + * Pathname comparing for invalidating entries in cache + * + * A partial path is compared in order to invalidate all paths + * related to a renamed directory + */ + +static int inode_cache_inv_compare(const struct CACHED_GENERIC *cached, + const struct CACHED_GENERIC *wanted) +{ + int len; + + len = strlen(wanted->pathname); + return (strncmp(cached->pathname, wanted->pathname,len) + || ((cached->pathname[len] != '\0') + && (cached->pathname[len] != '/'))); +} + +/* + * Normalize file paths for cacheing + * Just remove leading and trailing '/', there should not be any + * non-standard components (such as "/../" or "/./") because + * paths have been rewritten by fuse. + * + * Returns the first non-'/' char in the original path + */ + +static char *path_normalize(char *path) +{ + int len; + char *p; + + /* remove leading and trailing '/' even for root */ + len = strlen(path); + while ((len > 1) && (path[len - 1] == PATH_SEP)) + path[--len] = '\0'; + p = path; + while (*p == PATH_SEP) + p++; + return (p); + } + +#endif + /** * ntfs_inode_lookup_by_name - find an inode in a directory given its name * @dir_ni: ntfs inode of the directory in which to search for the name @@ -426,6 +483,11 @@ ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, ntfs_inode *result = NULL; ntfschar *unicode = NULL; char *ascii = NULL; +#if CACHE_INODE_SIZE + struct CACHED_INODE item; + struct CACHED_INODE *cached; + char *fullname; +#endif if (!vol || !pathname) { errno = EINVAL; @@ -434,36 +496,68 @@ ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, ntfs_log_trace("path: '%s'\n", pathname); - if (parent) { - ni = parent; - } else { - ni = ntfs_inode_open(vol, FILE_root); - if (!ni) { - ntfs_log_debug("Couldn't open the inode of the root " - "directory.\n"); - err = EIO; - goto close; - } - } - unicode = ntfs_calloc(MAX_PATH); ascii = strdup(pathname); if (!unicode || !ascii) { ntfs_log_debug("Out of memory.\n"); err = ENOMEM; - goto close; + goto out; } +#if CACHE_INODE_SIZE + fullname = path_normalize(ascii); + p = fullname; +#else p = ascii; /* Remove leading /'s. */ while (p && *p && *p == PATH_SEP) p++; +#endif + if (parent) { + ni = parent; + } else { +#if CACHE_INODE_SIZE + /* + * fetch inode for full path from cache + */ + if (*fullname) { + item.pathname = fullname; + cached = (struct CACHED_INODE*)ntfs_fetch_cache( + vol->inode_cache, GENERIC(&item), + inode_cache_compare); + } else + cached = (struct CACHED_INODE*)NULL; + if (cached) { + /* + * return opened inode if found in cache + */ + inum = MREF(cached->inum); + ni = ntfs_inode_open(vol, inum); + if (!ni) { + ntfs_log_debug("Cannot open inode %llu: %s.\n", + (unsigned long long)inum, p); + err = EIO; + } + result = ni; + goto out; + } +#endif + ni = ntfs_inode_open(vol, FILE_root); + if (!ni) { + ntfs_log_debug("Couldn't open the inode of the root " + "directory.\n"); + err = EIO; + result = (ntfs_inode*)NULL; + goto out; + } + } + while (p && *p) { /* Find the end of the first token. */ q = strchr(p, PATH_SEP); if (q != NULL) { *q = '\0'; - q++; + /* q++; JPA */ } len = ntfs_mbstoucs(p, &unicode, MAX_PATH); @@ -475,8 +569,33 @@ ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, err = ENAMETOOLONG; goto close; } - +#if CACHE_INODE_SIZE + /* + * fetch inode for partial path from cache + * if not available, compute and store into cache + */ + if (parent) + inum = ntfs_inode_lookup_by_name(ni, unicode, len); + else { + item.pathname = fullname; + cached = (struct CACHED_INODE*)ntfs_fetch_cache( + vol->inode_cache, GENERIC(&item), + inode_cache_compare); + if (cached) { + inum = cached->inum; + } else { + inum = ntfs_inode_lookup_by_name(ni, unicode, len); + if (inum != (u64) -1) { + item.inum = inum; + ntfs_enter_cache(vol->inode_cache, + GENERIC(&item), + inode_cache_compare); + } + } + } +#else inum = ntfs_inode_lookup_by_name(ni, unicode, len); +#endif if (inum == (u64) -1) { ntfs_log_debug("Couldn't find name '%s' in pathname " "'%s'.\n", p, pathname); @@ -499,6 +618,7 @@ ntfs_inode *ntfs_pathname_to_inode(ntfs_volume *vol, ntfs_inode *parent, goto close; } + if (q) *q++ = PATH_SEP; /* JPA */ p = q; while (p && *p && *p == PATH_SEP) p++; @@ -1369,7 +1489,8 @@ no_hardlink: * * Return 0 on success or -1 on error with errno set to the error code. */ -int ntfs_delete(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, u8 name_len) +int ntfs_delete(ntfs_volume *vol, const char *pathname, + ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, u8 name_len) { ntfs_attr_search_ctx *actx = NULL; ntfs_index_context *ictx = NULL; @@ -1377,6 +1498,11 @@ int ntfs_delete(ntfs_inode *ni, ntfs_inode *dir_ni, ntfschar *name, u8 name_len) BOOL looking_for_dos_name = FALSE, looking_for_win32_name = FALSE; BOOL case_sensitive_match = TRUE; int err = 0; +#if CACHE_INODE_SIZE + char *ascii; + struct CACHED_INODE item; + int count; +#endif ntfs_log_trace("Entering.\n"); @@ -1556,6 +1682,22 @@ out: err = errno; if (ntfs_inode_close(ni) && !err) err = errno; +#if CACHE_INODE_SIZE + /* invalide cache entry, even if there was an error */ + ascii = strdup(pathname); + if (ascii) { + char *p; + + item.pathname = path_normalize(ascii); + count = ntfs_invalidate_cache(vol->inode_cache, GENERIC(&item), + inode_cache_inv_compare); + p = ascii; /* do not clear ascii */ + free(p); + } + if (!ascii || !count) + ntfs_log_error("Could not delete inode cache entry for %s\n", + pathname); +#endif if (err) { errno = err; ntfs_log_debug("Could not delete file: %s\n", strerror(errno)); diff --git a/libntfs-3g/misc.c b/libntfs-3g/misc.c index 9e38347e..f44b236f 100644 --- a/libntfs-3g/misc.c +++ b/libntfs-3g/misc.c @@ -1,3 +1,26 @@ +/** + * misc.c : miscellaneous : + * - dealing with errors in memory allocation + * - data caching + * + * Copyright (c) 2008 Jean-Pierre Andre + * + * This program/include file 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 + * (at your option) any later version. + * + * This program/include file is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (in the main directory of the NTFS-3G + * distribution in the file COPYING); if not, write to the Free Software + * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -5,7 +28,12 @@ #ifdef HAVE_STDLIB_H #include #endif +#ifdef HAVE_STRING_H +#include +#endif +#include "types.h" +#include "security.h" #include "misc.h" #include "logging.h" @@ -34,3 +62,297 @@ void *ntfs_malloc(size_t size) return p; } +/* + * General functions to deal with LRU caches + * + * The cached data have to be organized in a structure in which + * the first fields must follow a mandatory pattern and further + * fields may contain any fixed size data. They are stored in an + * LRU list. + * + * A compare function must be provided for finding a wanted entry + * in the cache. Another function may be provided for invalidating + * an entry to facilitate multiple invalidation. + * + * These functions never return error codes. When there is a + * shortage of memory, data is simply not cached. + */ + +/* + * Fetch an entry from cache + * + * returns the cache entry, or NULL if not available + */ + +struct CACHED_GENERIC *ntfs_fetch_cache(struct CACHE_HEADER *cache, + const struct CACHED_GENERIC *wanted, cache_compare compare) +{ + struct CACHED_GENERIC *current; + struct CACHED_GENERIC *previous; + + current = (struct CACHED_GENERIC*)NULL; + if (cache) { + /* + * Search sequentially in LRU list + */ + current = cache->most_recent_entry; + previous = (struct CACHED_GENERIC*)NULL; + while (current + && compare(current, wanted)) { + previous = current; + current = current->next; + } + if (current) + cache->hits++; + if (current && previous) { + /* + * found and not at head of list, unlink from current + * position and relink as head of list + */ + previous->next = current->next; + current->next = cache->most_recent_entry; + cache->most_recent_entry = current; + } + cache->reads++; + } + return (current); +} + +/* + * Enter an inode number into cache + * returns the cache entry or NULL if not possible + */ + +struct CACHED_GENERIC *ntfs_enter_cache(struct CACHE_HEADER *cache, + const struct CACHED_GENERIC *item, cache_compare compare) +{ + struct CACHED_GENERIC *current; + struct CACHED_GENERIC *previous; + struct CACHED_GENERIC *before; + + current = (struct CACHED_GENERIC*)NULL; + if (cache) { + + /* + * Search sequentially in LRU list to locate the end, + * and find out whether the entry is already in list + * As we normally go to the end, no statitics is + * kept. + */ + current = cache->most_recent_entry; + previous = (struct CACHED_GENERIC*)NULL; + before = (struct CACHED_GENERIC*)NULL; + while (current + && (!current->pathname + || compare(current, item))) { + before = previous; + previous = current; + current = current->next; + } + + if (!current) { + /* + * Not in list, get a free entry or reuse the + * last entry, and relink as head of list + * Note : we assume at least three entries, so + * before, previous and first are different when + * an entry is reused. + */ + + if (cache->free_entry) { + current = cache->free_entry; + cache->free_entry = cache->free_entry->next; + if (item->pathname) { + current->pathname = ntfs_malloc( + strlen(item->pathname) + 1); + } else + current->pathname = (char*)NULL; + } else { + before->next = (struct CACHED_GENERIC*)NULL; + current = previous; + if (item->pathname) { + if (current->pathname) + current->pathname = realloc( + current->pathname, + strlen(item->pathname) + 1); + else + current->pathname = ntfs_malloc( + strlen(item->pathname) + 1); + } else { + if (current->pathname) + free(current->pathname); + current->pathname = (char*)NULL; + } + } + current->next = cache->most_recent_entry; + cache->most_recent_entry = current; + memcpy(current->fixed, item->fixed, cache->fixed_size); + if (item->pathname) { + if (current->pathname) { + strcpy(current->pathname, + item->pathname); + } else { + /* + * no more memory for variable part + * recycle entry in free list + * not an error, just uncacheable + */ + cache->most_recent_entry = current->next; + current->next = cache->free_entry; + cache->free_entry = current; + current = (struct CACHED_GENERIC*)NULL; + } + } else + current->pathname = (char*)NULL; + } + cache->writes++; + } + return (current); +} + +/* + * Invalidate entries in cache + * + * Several entries may have to be invalidated (at least for inodes + * associated to directories which have been renamed), a different + * compare function may be provided to select entries to invalidate + * + * Returns the number of deleted entries, this can be used by + * the caller to signal a cache corruption if the entry was + * supposed to be found. + */ + +int ntfs_invalidate_cache(struct CACHE_HEADER *cache, + const struct CACHED_GENERIC *item, cache_compare compare) +{ + struct CACHED_GENERIC *current; + struct CACHED_GENERIC *previous; + int count; + + current = (struct CACHED_GENERIC*)NULL; + count = 0; + if (cache) { + /* + * Search sequentially in LRU list + */ + current = cache->most_recent_entry; + previous = (struct CACHED_GENERIC*)NULL; + while (current) { + if (!compare(current, item)) { + /* + * Relink into free list + */ + if (previous) + previous->next = current->next; + else + cache->most_recent_entry = current->next; + current->next = cache->free_entry; + cache->free_entry = current; + if (current->pathname) + free(current->pathname); + if (previous) + current = previous->next; + else + current = cache->most_recent_entry; + count++; + } else { + previous = current; + current = current->next; + } + } + } + return (count); +} + +/* + * Free memory allocated to a cache + */ + +static void ntfs_free_cache(struct CACHE_HEADER *cache) +{ + struct CACHED_GENERIC *entry; + + if (cache) { + for (entry=cache->most_recent_entry; entry; entry=entry->next) + if (entry->pathname) + free(entry->pathname); + free(cache); + } +} + +/* + * Create a cache + * + * Returns the cache header, or NULL if the cache could not be created + */ + +static struct CACHE_HEADER *ntfs_create_cache(const char *name, + int full_item_size, int item_count) +{ + struct CACHE_HEADER *cache; + struct CACHED_GENERIC *p; + struct CACHED_GENERIC *q; + int i; + + cache = (struct CACHE_HEADER*) + ntfs_malloc(sizeof(struct CACHE_HEADER) + + item_count*full_item_size); + if (cache) { + cache->name = name; + cache->fixed_size = full_item_size - sizeof(struct CACHED_GENERIC); + cache->reads = 0; + cache->writes = 0; + cache->hits = 0; + /* chain the entries, and mark an invalid entry */ + cache->most_recent_entry = (struct CACHED_GENERIC*)NULL; + cache->free_entry = &cache->entry[0]; + p = &cache->entry[0]; + for (i=0; i<(item_count - 1); i++) { + q = (struct CACHED_GENERIC*)((char*)p + full_item_size); + p->next = q; + p->pathname = (char*)NULL; + p = q; + } + /* special for the last entry */ + p->next = (struct CACHED_GENERIC*)NULL; + p->pathname = (char*)NULL; + } + return (cache); +} + +/* + * Create all LRU caches + * + * No error return, if creation is not possible, cacheing will + * just be not available + */ + +void ntfs_create_lru_caches(ntfs_volume *vol) +{ +#if CACHE_INODE_SIZE + /* inode cache */ + vol->inode_cache = ntfs_create_cache("inode", + sizeof(struct CACHED_INODE), CACHE_INODE_SIZE); +#endif + vol->securid_cache = ntfs_create_cache("securid", + sizeof(struct CACHED_SECURID), CACHE_SECURID_SIZE); +#if CACHE_LEGACY_SIZE + vol->legacy_cache = ntfs_create_cache("legacy", + sizeof(struct CACHED_PERMISSIONS_LEGACY), CACHE_LEGACY_SIZE); +#endif +} + +/* + * Free all LRU caches + */ + +void ntfs_free_lru_caches(ntfs_volume *vol) +{ +#if CACHE_INODE_SIZE + ntfs_free_cache(vol->inode_cache); +#endif + ntfs_free_cache(vol->securid_cache); +#if CACHE_LEGACY_SIZE + ntfs_free_cache(vol->legacy_cache); +#endif +} diff --git a/libntfs-3g/security.c b/libntfs-3g/security.c index 39e0b583..03182f27 100644 --- a/libntfs-3g/security.c +++ b/libntfs-3g/security.c @@ -32,10 +32,8 @@ #define BUFSZ 1024 /* buffer size to read mapping file */ #define MAPPINGFILE "/NTFS-3G/UserMapping" /* name of mapping file */ #define LINESZ 120 /* maximum useful size of a mapping line */ -#define CACHE_SECURID_SIZE 16 /* securid cache, size >= 3 and not too big */ #define CACHE_PERMISSIONS_BITS 6 /* log2 of unitary allocation of permissions */ #define CACHE_PERMISSIONS_SIZE 262144 /* max cacheable permissions */ -#define CACHE_LEGACY_SIZE 8 /* legacy cache size, zero or >= 3 and not too big */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -1631,75 +1629,28 @@ static BOOL groupmember(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid) * and 30% if the cache is disabled. */ -static struct SECURITY_CACHE *create_caches(struct SECURITY_CONTEXT *scx, +static struct PERMISSIONS_CACHE *create_caches(struct SECURITY_CONTEXT *scx, u32 securindex) { - struct CACHED_SECURID *cachesecurid; - struct SECURITY_CACHE *cache; -#if CACHE_LEGACY_SIZE - struct CACHED_PERMISSIONS_LEGACY *cachelegacy; -#endif + struct PERMISSIONS_CACHE *cache; unsigned int index1; unsigned int i; - cache = (struct SECURITY_CACHE*)NULL; - /* create the securid cache first */ - cachesecurid = (struct CACHED_SECURID*) - ntfs_malloc(CACHE_SECURID_SIZE*sizeof(struct CACHED_SECURID)); - if (cachesecurid) { - /* chain the entries, and mark an invalid mode */ - for (i=0; i<(CACHE_SECURID_SIZE - 1); i++) { - cachesecurid[i].next = &cachesecurid[i+1]; - cachesecurid[i].dmode = -1; - } - /* special for the last entry */ - cachesecurid[CACHE_SECURID_SIZE - 1].next = - (struct CACHED_SECURID*)NULL; - cachesecurid[CACHE_SECURID_SIZE - 1].dmode = -1; -#if CACHE_LEGACY_SIZE - /* create the legacy cache if needed */ - cachelegacy = (struct CACHED_PERMISSIONS_LEGACY*) - ntfs_malloc(CACHE_LEGACY_SIZE* - sizeof(struct CACHED_PERMISSIONS_LEGACY)); - if (cachelegacy) { - /* chain the entries, and mark an invalid entry */ - for (i=0; i<(CACHE_LEGACY_SIZE - 1); i++) { - cachelegacy[i].next = &cachelegacy[i+1]; - cachelegacy[i].permissions.valid = 0; - } - /* special for the last entry */ - cachelegacy[CACHE_LEGACY_SIZE - 1].next = - (struct CACHED_PERMISSIONS_LEGACY*)NULL; - cachelegacy[CACHE_LEGACY_SIZE - 1].permissions.valid = 0;; -#endif - /* create the first permissions blocks */ - index1 = securindex >> CACHE_PERMISSIONS_BITS; - cache = (struct SECURITY_CACHE*) - ntfs_malloc(sizeof(struct SECURITY_CACHE) - + index1*sizeof(struct CACHED_PERMISSIONS*)); - if (cache) { - cache->head.last = index1; - cache->head.p_reads = 0; - cache->head.p_hits = 0; - cache->head.p_writes = 0; - cache->head.s_reads = 0; - cache->head.s_hits = 0; - cache->head.s_writes = 0; - cache->head.s_hops = 0; - *scx->pseccache = cache; - cache->head.first_securid = cachesecurid; - cache->head.most_recent_securid = cachesecurid; - for (i=0; i<=index1; i++) - cache->cachetable[i] - = (struct CACHED_PERMISSIONS*)NULL; -#if CACHE_LEGACY_SIZE - cache->head.first_legacy = cachelegacy; - cache->head.most_recent_legacy = cachelegacy; -#endif - } -#if CACHE_LEGACY_SIZE - } -#endif + cache = (struct PERMISSIONS_CACHE*)NULL; + /* create the first permissions blocks */ + index1 = securindex >> CACHE_PERMISSIONS_BITS; + cache = (struct PERMISSIONS_CACHE*) + ntfs_malloc(sizeof(struct PERMISSIONS_CACHE) + + index1*sizeof(struct CACHED_PERMISSIONS*)); + if (cache) { + cache->head.last = index1; + cache->head.p_reads = 0; + cache->head.p_hits = 0; + cache->head.p_writes = 0; + *scx->pseccache = cache; + for (i=0; i<=index1; i++) + cache->cachetable[i] + = (struct CACHED_PERMISSIONS*)NULL; } return (cache); } @@ -1712,14 +1663,10 @@ static struct SECURITY_CACHE *create_caches(struct SECURITY_CONTEXT *scx, static void free_caches(struct SECURITY_CONTEXT *scx) { unsigned int index1; - struct SECURITY_CACHE *pseccache; + struct PERMISSIONS_CACHE *pseccache; pseccache = *scx->pseccache; if (pseccache) { - free(pseccache->head.first_securid); -#if CACHE_LEGACY_SIZE - free(pseccache->head.first_legacy); -#endif for (index1=0; index1<=pseccache->head.last; index1++) if (pseccache->cachetable[index1]) free(pseccache->cachetable[index1]); @@ -1727,244 +1674,20 @@ static void free_caches(struct SECURITY_CONTEXT *scx) } } -/* - * Fetch a securid from cache - * returns the cache entry, or NULL if not available - */ - -static const struct CACHED_SECURID *fetch_securid(struct SECURITY_CONTEXT *scx, - uid_t uid, gid_t gid, mode_t mode, BOOL isdir) +static int compare(const struct CACHED_SECURID *cached, + const struct CACHED_SECURID *item) { - struct SECURITY_CACHE *cache; - struct CACHED_SECURID *current; - struct CACHED_SECURID *previous; - unsigned int dmode; /* mode and directory flag */ - - cache = *scx->pseccache; - if (cache) { - /* - * Search sequentially in LRU list - */ - dmode = (isdir ? mode | 010000 : mode); - current = cache->head.most_recent_securid; - previous = (struct CACHED_SECURID*)NULL; - while (current - && ((current->uid != uid) - || (current->gid != gid) - || (current->dmode != dmode))) { - cache->head.s_hops++; - previous = current; - current = current->next; - } - if (current) - cache->head.s_hits++; - if (current && previous) { - /* - * found and not at head of list, unlink from current - * position and relink as head of list - */ - previous->next = current->next; - current->next = cache->head.most_recent_securid; - cache->head.most_recent_securid = current; - } - cache->head.s_reads++; - } else /* cache not ready */ - current = (struct CACHED_SECURID*)NULL; - return (current); + return (((cached->uid != item->uid) + || (cached->gid != item->gid) + || (cached->dmode != item->dmode))); } -/* - * Enter a securid into cache - * returns the cache entry - */ - -static const struct CACHED_SECURID *enter_securid(struct SECURITY_CONTEXT *scx, - uid_t uid, gid_t gid, mode_t mode, - BOOL isdir, le32 securid) +static int leg_compare(const struct CACHED_PERMISSIONS_LEGACY *cached, + const struct CACHED_PERMISSIONS_LEGACY *item) { - struct SECURITY_CACHE *cache; - struct CACHED_SECURID *current; - struct CACHED_SECURID *previous; - struct CACHED_SECURID *before; - unsigned int dmode; - - dmode = mode & 07777; - if (isdir) dmode |= 010000; - cache = *scx->pseccache; - if (cache || (cache = create_caches(scx, le32_to_cpu(securid)))) { - - /* - * Search sequentially in LRU list to locate the end, - * and find out whether the entry is already in list - * As we normally go to the end, no statitics is - * kept. - */ - current = cache->head.most_recent_securid; - previous = (struct CACHED_SECURID*)NULL; - before = (struct CACHED_SECURID*)NULL; - while (current - && ((current->uid != uid) - || (current->gid != gid) - || (current->dmode != dmode))) { - before = previous; - previous = current; - current = current->next; - } - - if (!current) { - /* - * Not in list, reuse the last entry, - * and relink as head of list - * Note : we assume at least three entries, so - * before, previous and first are always different - */ - before->next = (struct CACHED_SECURID*)NULL; - previous->next = cache->head.most_recent_securid; - cache->head.most_recent_securid = previous; - current = previous; - current->uid = uid; - current->gid = gid; - current->dmode = dmode; - current->securid = securid; - } - cache->head.s_writes++; - } else /* cache not available */ - current = (struct CACHED_SECURID*)NULL; - return (current); + return (cached->mft_no != item->mft_no); } - -#if CACHE_LEGACY_SIZE - -/* - * Fetch a legacy directory from cache - * returns the cache entry, or NULL if not available - */ - -static struct CACHED_PERMISSIONS *fetch_legacy(struct SECURITY_CONTEXT *scx, - ntfs_inode *ni) -{ - struct SECURITY_CACHE *cache; - struct CACHED_PERMISSIONS_LEGACY *current; - struct CACHED_PERMISSIONS_LEGACY *previous; - struct CACHED_PERMISSIONS *cacheentry; - - cacheentry = (struct CACHED_PERMISSIONS*)NULL; - cache = *scx->pseccache; - if (cache - && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { - /* - * Search sequentially in LRU list - */ - current = cache->head.most_recent_legacy; - previous = (struct CACHED_PERMISSIONS_LEGACY*)NULL; - while (current - && (!current->permissions.valid - || (current->mft_no != ni->mft_no))) { - previous = current; - current = current->next; - } - if (current) { - cache->head.p_hits++; - cacheentry = ¤t->permissions; - } - if (current && previous) { - /* - * found and not at head of list, unlink from current - * position and relink as head of list - */ - previous->next = current->next; - current->next = cache->head.most_recent_legacy; - cache->head.most_recent_legacy = current; - } - cache->head.p_reads++; - } - return (cacheentry); -} - -/* - * Enter a legacy directory into cache - * returns the cache entry - */ - -static struct CACHED_PERMISSIONS *enter_legacy(struct SECURITY_CONTEXT *scx, - ntfs_inode *ni, uid_t uid, gid_t gid, - mode_t mode) -{ - struct SECURITY_CACHE *cache; - struct CACHED_PERMISSIONS_LEGACY *current; - struct CACHED_PERMISSIONS_LEGACY *previous; - struct CACHED_PERMISSIONS_LEGACY *before; - struct CACHED_PERMISSIONS *cacheentry; - - mode &= 07777; - cacheentry = (struct CACHED_PERMISSIONS*)NULL; - cache = *scx->pseccache; - if ((cache || (cache = create_caches(scx, FIRST_SECURITY_ID))) - && (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)) { - - /* - * Search sequentially in LRU list to locate the end, - * and find out whether the entry is already in list - * As we normally go to the end, no statitics is - * kept. - */ - current = cache->head.most_recent_legacy; - previous = (struct CACHED_PERMISSIONS_LEGACY*)NULL; - before = (struct CACHED_PERMISSIONS_LEGACY*)NULL; - while (current - && (!current->permissions.valid - || (current->mft_no != ni->mft_no))) { - before = previous; - previous = current; - current = current->next; - } - - if (!current) { - /* - * Not in list, reuse the last entry, - * and relink as head of list - * Note : we assume at least three entries, so - * before, previous and first are always different - */ - before->next = (struct CACHED_PERMISSIONS_LEGACY*)NULL; - previous->next = cache->head.most_recent_legacy; - cache->head.most_recent_legacy = previous; - current = previous; - current->mft_no = ni->mft_no; - cacheentry = ¤t->permissions; - cacheentry->uid = uid; - cacheentry->gid = gid; - cacheentry->mode = mode; - cacheentry->inh_fileid = cpu_to_le32(0); - cacheentry->inh_dirid = cpu_to_le32(0); - cacheentry->valid = 1; - } - cache->head.p_writes++; - } - return (cacheentry); -} - -/* - * Invalidate a legacy directory entry in cache - * This is needed after a change in directory protection, when - * no security id has been assigned (only for NTFS 1.x) - * - * returns the cache entry, or NULL if not available - */ - -static struct CACHED_PERMISSIONS *invalidate_legacy(struct SECURITY_CONTEXT *scx, - ntfs_inode *ni) -{ - struct CACHED_PERMISSIONS *cached; - - cached = fetch_legacy(scx, ni); - if (cached) cached->valid = 0; - return (cached); -} - -#endif - /* * Resize permission cache table * do not call unless resizing is needed @@ -1977,8 +1700,8 @@ static struct CACHED_PERMISSIONS *invalidate_legacy(struct SECURITY_CONTEXT *scx static void resize_cache(struct SECURITY_CONTEXT *scx, u32 securindex) { - struct SECURITY_CACHE *oldcache; - struct SECURITY_CACHE *newcache; + struct PERMISSIONS_CACHE *oldcache; + struct PERMISSIONS_CACHE *newcache; int newcnt; int oldcnt; unsigned int index1; @@ -1993,13 +1716,13 @@ static void resize_cache(struct SECURITY_CONTEXT *scx, /* expand cache beyond current end, do not use realloc() */ /* to avoid losing data when there is no more memory */ oldcnt = oldcache->head.last + 1; - newcache = (struct SECURITY_CACHE*) + newcache = (struct PERMISSIONS_CACHE*) ntfs_malloc( - sizeof(struct SECURITY_CACHE) + sizeof(struct PERMISSIONS_CACHE) + (newcnt - 1)*sizeof(struct CACHED_PERMISSIONS*)); if (newcache) { memcpy(newcache,oldcache, - sizeof(struct SECURITY_CACHE) + sizeof(struct PERMISSIONS_CACHE) + (oldcnt - 1)*sizeof(struct CACHED_PERMISSIONS*)); free(oldcache); /* mark new entries as not valid */ @@ -2025,7 +1748,7 @@ static struct CACHED_PERMISSIONS *enter_cache(struct SECURITY_CONTEXT *scx, { struct CACHED_PERMISSIONS *cacheentry; struct CACHED_PERMISSIONS *cacheblock; - struct SECURITY_CACHE *pcache; + struct PERMISSIONS_CACHE *pcache; u32 securindex; unsigned int index1; unsigned int index2; @@ -2084,12 +1807,27 @@ static struct CACHED_PERMISSIONS *enter_cache(struct SECURITY_CONTEXT *scx, } else cacheentry = (struct CACHED_PERMISSIONS*)NULL; } - } else -#if CACHE_LEGACY_SIZE - cacheentry = enter_legacy(scx, ni, uid, gid, mode); -#else + } else { cacheentry = (struct CACHED_PERMISSIONS*)NULL; +#if CACHE_LEGACY_SIZE + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + struct CACHED_PERMISSIONS_LEGACY wanted; + struct CACHED_PERMISSIONS_LEGACY *legacy; + + wanted.perm.uid = uid; + wanted.perm.gid = gid; + wanted.perm.mode = mode & 07777; + wanted.perm.inh_fileid = cpu_to_le32(0); + wanted.perm.inh_dirid = cpu_to_le32(0); + wanted.mft_no = ni->mft_no; + wanted.unused = (char*)NULL; + legacy = (struct CACHED_PERMISSIONS_LEGACY*)ntfs_enter_cache( + scx->vol->legacy_cache, GENERIC(&wanted), + (cache_compare)leg_compare); + if (legacy) cacheentry = &legacy->perm; + } #endif + } return (cacheentry); } @@ -2106,7 +1844,7 @@ static struct CACHED_PERMISSIONS *fetch_cache(struct SECURITY_CONTEXT *scx, ntfs_inode *ni) { struct CACHED_PERMISSIONS *cacheentry; - struct SECURITY_CACHE *pcache; + struct PERMISSIONS_CACHE *pcache; u32 securindex; unsigned int index1; unsigned int index2; @@ -2133,8 +1871,20 @@ static struct CACHED_PERMISSIONS *fetch_cache(struct SECURITY_CONTEXT *scx, } } #if CACHE_LEGACY_SIZE - else - cacheentry = fetch_legacy(scx, ni); + else { + cacheentry = (struct CACHED_PERMISSIONS*)NULL; + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + struct CACHED_PERMISSIONS_LEGACY wanted; + struct CACHED_PERMISSIONS_LEGACY *legacy; + + wanted.mft_no = ni->mft_no; + wanted.unused = (char*)NULL; + legacy = (struct CACHED_PERMISSIONS_LEGACY*)ntfs_fetch_cache( + scx->vol->legacy_cache, GENERIC(&wanted), + (cache_compare)leg_compare); + if (legacy) cacheentry = &legacy->perm; + } + } #endif return (cacheentry); } @@ -3310,6 +3060,7 @@ le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx, uid_t uid, gid_t gid, mode_t mode, BOOL isdir) { const struct CACHED_SECURID *cached; + struct CACHED_SECURID wanted; char *newattr; int newattrsz; const SID *usid; @@ -3321,7 +3072,14 @@ le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx, #if !FORCE_FORMAT_v1x /* check whether target securid is known in cache */ - cached = fetch_securid(scx, uid, gid, mode & 07777, isdir); + wanted.uid = uid; + wanted.gid = gid; + wanted.dmode = mode & 07777; + if (isdir) wanted.dmode |= 0x10000; + wanted.unused = (char*)NULL; + cached = (const struct CACHED_SECURID*)ntfs_fetch_cache( + scx->vol->securid_cache, GENERIC(&wanted), + (cache_compare)compare); /* quite simple, if we are lucky */ if (cached) securid = cached->securid; @@ -3344,9 +3102,10 @@ le32 ntfs_alloc_securid(struct SECURITY_CONTEXT *scx, newattrsz); if (securid) { /* update cache, for subsequent use */ - enter_securid(scx, uid, - gid, mode, isdir, - securid); + wanted.securid = securid; + ntfs_enter_cache(scx->vol->securid_cache, + GENERIC(&wanted), + (cache_compare)compare); } free(newattr); } else { @@ -3373,6 +3132,7 @@ int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, { int res; const struct CACHED_SECURID *cached; + struct CACHED_SECURID wanted; char *newattr; const SID *usid; const SID *gsid; @@ -3384,7 +3144,14 @@ int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, isdir = (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) != 0; if (test_nino_flag(ni, v3_Extensions)) { - cached = fetch_securid(scx, uid, gid, mode & 07777, isdir); + wanted.uid = uid; + wanted.gid = gid; + wanted.dmode = mode & 07777; + if (isdir) wanted.dmode |= 0x10000; + wanted.unused = (char*)NULL; + cached = (const struct CACHED_SECURID*)ntfs_fetch_cache( + scx->vol->securid_cache, GENERIC(&wanted), + (cache_compare)compare); /* quite simple, if we are lucky */ if (cached) { ni->security_id = cached->securid; @@ -3411,14 +3178,23 @@ int ntfs_set_owner_mode(struct SECURITY_CONTEXT *scx, ntfs_inode *ni, res = update_secur_descr(scx->vol, newattr, ni); if (!res) { /* update cache, for subsequent use */ - if (test_nino_flag(ni, v3_Extensions)) - enter_securid(scx, uid, - gid, mode, isdir, - ni->security_id); + if (test_nino_flag(ni, v3_Extensions)) { + wanted.securid = ni->security_id; + ntfs_enter_cache(scx->vol->securid_cache, + GENERIC(&wanted), + (cache_compare)compare); + } #if CACHE_LEGACY_SIZE /* also invalidate legacy cache */ - if (isdir && !ni->security_id) - invalidate_legacy(scx, ni); + if (isdir && !ni->security_id) { + struct CACHED_PERMISSIONS_LEGACY legacy; + + legacy.mft_no = ni->mft_no; + legacy.unused = (char*)NULL; + ntfs_invalidate_cache(scx->vol->legacy_cache, + GENERIC(&legacy), + (cache_compare)leg_compare); + } #endif } free(newattr); diff --git a/libntfs-3g/unistr.c b/libntfs-3g/unistr.c index 4eb9dee9..73502212 100644 --- a/libntfs-3g/unistr.c +++ b/libntfs-3g/unistr.c @@ -123,7 +123,10 @@ BOOL ntfs_names_are_equal(const ntfschar *s1, size_t s1_len, * @err_val if an invalid character is found in @name1 during the comparison. * * The following characters are considered invalid: '"', '*', '<', '>' and '?'. + * + * A few optimizations made by JPA */ + int ntfs_names_collate(const ntfschar *name1, const u32 name1_len, const ntfschar *name2, const u32 name2_len, const int err_val __attribute__((unused)), @@ -139,21 +142,29 @@ int ntfs_names_collate(const ntfschar *name1, const u32 name1_len, exit(1); } #endif - for (cnt = 0; cnt < min(name1_len, name2_len); ++cnt) { - c1 = le16_to_cpu(*name1); - name1++; - c2 = le16_to_cpu(*name2); - name2++; - if (ic) { - if (c1 < upcase_len) - c1 = le16_to_cpu(upcase[c1]); - if (c2 < upcase_len) - c2 = le16_to_cpu(upcase[c2]); - } -#if 0 - if (c1 < 64 && legal_ansi_char_array[c1] & 8) - return err_val; -#endif + cnt = min(name1_len, name2_len); + /* JPA average loop count is 8 */ + if (cnt > 0) { + if (ic) + /* JPA this loop in 76% cases */ + do { + c1 = le16_to_cpu(*name1); + name1++; + c2 = le16_to_cpu(*name2); + name2++; + if (c1 < upcase_len) + c1 = le16_to_cpu(upcase[c1]); + if (c2 < upcase_len) + c2 = le16_to_cpu(upcase[c2]); + } while ((c1 == c2) && --cnt); + else + do { + /* JPA this loop in 24% cases */ + c1 = le16_to_cpu(*name1); + name1++; + c2 = le16_to_cpu(*name2); + name2++; + } while ((c1 == c2) && --cnt); if (c1 < c2) return -1; if (c1 > c2) @@ -163,12 +174,6 @@ int ntfs_names_collate(const ntfschar *name1, const u32 name1_len, return -1; if (name1_len == name2_len) return 0; - /* name1_len > name2_len */ -#if 0 - c1 = le16_to_cpu(*name1); - if (c1 < 64 && legal_ansi_char_array[c1] & 8) - return err_val; -#endif return 1; } diff --git a/libntfs-3g/volume.c b/libntfs-3g/volume.c index 699c2dd2..72ecd154 100644 --- a/libntfs-3g/volume.c +++ b/libntfs-3g/volume.c @@ -152,6 +152,7 @@ static int __ntfs_volume_release(ntfs_volume *v) ntfs_error_set(&err); } + ntfs_free_lru_caches(v); free(v->vol_name); free(v->upcase); free(v->attrdef); @@ -1166,6 +1167,7 @@ ntfs_volume *ntfs_mount(const char *name __attribute__((unused)), ntfs_device_free(dev); errno = eo; } + ntfs_create_lru_caches(vol); return vol; #else /* diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index c413c62c..c6c4b810 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -131,7 +131,7 @@ typedef struct { struct fuse_chan *fc; BOOL inherit; BOOL addsecurids; - struct SECURITY_CACHE *seccache; + struct PERMISSIONS_CACHE *seccache; struct SECURITY_CONTEXT security; } ntfs_fuse_context_t; @@ -842,7 +842,7 @@ static int ntfs_fuse_truncate(const char *org_path, off_t size) } static int ntfs_fuse_ftruncate(const char *org_path, off_t size, - struct fuse_file_info *fi) + struct fuse_file_info *fi __attribute__((unused))) { /* * in ->ftruncate() the file handle is guaranteed @@ -1290,7 +1290,8 @@ static int ntfs_fuse_rm(const char *org_path) || ntfs_allowed_access(&security, path, dir_ni, S_IEXEC + S_IWRITE + S_ISVTX)) { - if (ntfs_delete(ni, dir_ni, uname, uname_len)) + if (ntfs_delete(ctx->vol, org_path, ni, dir_ni, + uname, uname_len)) res = -errno; /* ntfs_delete() always closes ni and dir_ni */ ni = dir_ni = NULL; @@ -1857,21 +1858,6 @@ static void ntfs_close(void) 1000 * ctx->seccache->head.p_hits / ctx->seccache->head.p_reads % 10); } - if (ctx->seccache && ctx->seccache->head.s_reads) { - ntfs_log_info("Security id cache : %lu writes " - "%lu reads %lu.%1lu%% hits " - "%lu.%1lu mean hops\n", - ctx->seccache->head.s_writes, - ctx->seccache->head.s_reads, - 100 * ctx->seccache->head.s_hits - / ctx->seccache->head.s_reads, - 1000 * ctx->seccache->head.s_hits - / ctx->seccache->head.s_reads % 10, - ctx->seccache->head.s_hops - / ctx->seccache->head.s_reads, - 10 * ctx->seccache->head.s_hops - / ctx->seccache->head.s_reads % 10); - } } ntfs_close_secure(&security); } @@ -2529,6 +2515,15 @@ static struct fuse *mount_fuse(char *parsed_options) goto err; if (fuse_opt_add_arg(&args, "-ouse_ino,kernel_cache") == -1) goto err; +#if CACHE_INODE_SIZE + /* + * JPA fuse attribute cacheing is not useful if we + * cache inodes, and this avoids hard link problems + */ + if (fuse_opt_add_arg(&args, + "-oattr_timeout=0,ac_attr_timeout=0") == -1) + goto err; +#endif if (ctx->debug) if (fuse_opt_add_arg(&args, "-odebug") == -1) goto err; @@ -2631,7 +2626,7 @@ int main(int argc, char *argv[]) } } - ctx->seccache = (struct SECURITY_CACHE*)NULL; + ctx->seccache = (struct PERMISSIONS_CACHE*)NULL; ntfs_log_info("Version %s\n", VERSION); ntfs_log_info("Mounted %s (%s, label \"%s\", NTFS %d.%d)\n",