1 #include "../../cache.h"
2 #include "../../hashmap.h"
6 static int initialized
;
7 static volatile long enabled
;
8 static struct hashmap map
;
9 static CRITICAL_SECTION mutex
;
12 * An entry in the file system cache. Used for both entire directory listings
16 struct hashmap_entry ent
;
21 * Name of the entry. For directory listings: relative path of the
22 * directory, without trailing '/' (empty for cwd()). For file entries:
23 * name of the file. Typically points to the end of the structure if
24 * the fsentry is allocated on the heap (see fsentry_alloc), or to a
25 * local variable if on the stack (see fsentry_init).
28 /* Pointer to the directory listing, or NULL for the listing itself. */
30 /* Pointer to the next file entry of the list. */
34 /* Reference count of the directory listing. */
37 /* More stat members (only used for file entries). */
47 * Compares the paths of two fsentry structures for equality.
49 static int fsentry_cmp(const struct fsentry
*fse1
, const struct fsentry
*fse2
)
55 /* compare the list parts first */
56 if (fse1
->list
!= fse2
->list
&& (res
= fsentry_cmp(
57 fse1
->list
? fse1
->list
: fse1
,
58 fse2
->list
? fse2
->list
: fse2
)))
61 /* if list parts are equal, compare len and name */
62 if (fse1
->len
!= fse2
->len
)
63 return fse1
->len
- fse2
->len
;
64 return strnicmp(fse1
->name
, fse2
->name
, fse1
->len
);
68 * Calculates the hash code of an fsentry structure's path.
70 static unsigned int fsentry_hash(const struct fsentry
*fse
)
72 unsigned int hash
= fse
->list
? fse
->list
->ent
.hash
: 0;
73 return hash
^ memihash(fse
->name
, fse
->len
);
77 * Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp.
79 static void fsentry_init(struct fsentry
*fse
, struct fsentry
*list
,
80 const char *name
, size_t len
)
85 hashmap_entry_init(fse
, fsentry_hash(fse
));
89 * Allocate an fsentry structure on the heap.
91 static struct fsentry
*fsentry_alloc(struct fsentry
*list
, const char *name
,
94 /* overallocate fsentry and copy the name to the end */
95 struct fsentry
*fse
= xmalloc(sizeof(struct fsentry
) + len
+ 1);
96 char *nm
= ((char*) fse
) + sizeof(struct fsentry
);
97 memcpy(nm
, name
, len
);
99 /* init the rest of the structure */
100 fsentry_init(fse
, list
, nm
, len
);
107 * Add a reference to an fsentry.
109 inline static void fsentry_addref(struct fsentry
*fse
)
114 InterlockedIncrement(&(fse
->refcnt
));
118 * Release the reference to an fsentry, frees the memory if its the last ref.
120 static void fsentry_release(struct fsentry
*fse
)
125 if (InterlockedDecrement(&(fse
->refcnt
)))
129 struct fsentry
*next
= fse
->next
;
136 * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
138 static struct fsentry
*fseentry_create_entry(struct fsentry
*list
,
139 const WIN32_FIND_DATAW
*fdata
)
141 char buf
[MAX_PATH
* 3];
144 len
= xwcstoutf(buf
, fdata
->cFileName
, ARRAY_SIZE(buf
));
146 fse
= fsentry_alloc(list
, buf
, len
);
148 fse
->st_mode
= file_attr_to_st_mode(fdata
->dwFileAttributes
);
149 fse
->st_size
= (((off64_t
) (fdata
->nFileSizeHigh
)) << 32)
150 | fdata
->nFileSizeLow
;
151 fse
->st_atime
= filetime_to_time_t(&(fdata
->ftLastAccessTime
));
152 fse
->st_mtime
= filetime_to_time_t(&(fdata
->ftLastWriteTime
));
153 fse
->st_ctime
= filetime_to_time_t(&(fdata
->ftCreationTime
));
159 * Create an fsentry-based directory listing (similar to opendir / readdir).
160 * Dir should not contain trailing '/'. Use an empty string for the current
161 * directory (not "."!).
163 static struct fsentry
*fsentry_create_list(const struct fsentry
*dir
)
165 wchar_t pattern
[MAX_PATH
+ 2]; /* + 2 for '/' '*' */
166 WIN32_FIND_DATAW fdata
;
169 struct fsentry
*list
, **phead
;
172 /* convert name to UTF-16 and check length < MAX_PATH */
173 if ((wlen
= xutftowcsn(pattern
, dir
->name
, MAX_PATH
, dir
->len
)) < 0) {
175 errno
= ENAMETOOLONG
;
179 /* append optional '/' and wildcard '*' */
181 pattern
[wlen
++] = '/';
182 pattern
[wlen
++] = '*';
185 /* open find handle */
186 h
= FindFirstFileW(pattern
, &fdata
);
187 if (h
== INVALID_HANDLE_VALUE
) {
188 err
= GetLastError();
189 errno
= (err
== ERROR_DIRECTORY
) ? ENOTDIR
: err_win_to_posix(err
);
193 /* allocate object to hold directory listing */
194 list
= fsentry_alloc(NULL
, dir
->name
, dir
->len
);
196 /* walk directory and build linked list of fsentry structures */
199 *phead
= fseentry_create_entry(list
, &fdata
);
200 phead
= &(*phead
)->next
;
201 } while (FindNextFileW(h
, &fdata
));
203 /* remember result of last FindNextFile, then close find handle */
204 err
= GetLastError();
207 /* return the list if we've got all the files */
208 if (err
== ERROR_NO_MORE_FILES
)
211 /* otherwise free the list and return error */
212 fsentry_release(list
);
213 errno
= err_win_to_posix(err
);
218 * Adds a directory listing to the cache.
220 static void fscache_add(struct fsentry
*fse
)
225 for (; fse
; fse
= fse
->next
)
226 hashmap_add(&map
, fse
);
230 * Removes a directory listing from the cache.
232 static void fscache_remove(struct fsentry
*fse
)
237 for (; fse
; fse
= fse
->next
)
238 hashmap_remove(&map
, fse
, NULL
);
244 static void fscache_clear()
246 struct hashmap_iter iter
;
248 while ((fse
= hashmap_iter_first(&map
, &iter
))) {
250 fsentry_release(fse
);
255 * Checks if the cache is enabled for the given path.
257 static inline int fscache_enabled(const char *path
)
259 return enabled
> 0 && !is_absolute_path(path
);
263 * Looks up or creates a cache entry for the specified key.
265 static struct fsentry
*fscache_get(struct fsentry
*key
)
269 EnterCriticalSection(&mutex
);
270 /* check if entry is in cache */
271 fse
= hashmap_get(&map
, key
, NULL
);
274 LeaveCriticalSection(&mutex
);
277 /* if looking for a file, check if directory listing is in cache */
278 if (!fse
&& key
->list
) {
279 fse
= hashmap_get(&map
, key
->list
, NULL
);
281 LeaveCriticalSection(&mutex
);
282 /* dir entry without file entry -> file doesn't exist */
288 /* create the directory listing (outside mutex!) */
289 LeaveCriticalSection(&mutex
);
290 fse
= fsentry_create_list(key
->list
? key
->list
: key
);
294 EnterCriticalSection(&mutex
);
295 /* add directory listing if it hasn't been added by some other thread */
296 if (!hashmap_get(&map
, key
, NULL
))
299 /* lookup file entry if requested (fse already points to directory) */
301 fse
= hashmap_get(&map
, key
, NULL
);
303 /* return entry or ENOENT */
309 LeaveCriticalSection(&mutex
);
314 * Enables or disables the cache. Note that the cache is read-only, changes to
315 * the working directory are NOT reflected in the cache while enabled.
317 int fscache_enable(int enable
)
322 /* allow the cache to be disabled entirely */
326 InitializeCriticalSection(&mutex
);
327 hashmap_init(&map
, (hashmap_cmp_fn
) fsentry_cmp
, 0);
331 result
= enable
? InterlockedIncrement(&enabled
)
332 : InterlockedDecrement(&enabled
);
334 if (enable
&& result
== 1) {
335 /* redirect opendir and lstat to the fscache implementations */
336 opendir
= fscache_opendir
;
337 lstat
= fscache_lstat
;
338 } else if (!enable
&& !result
) {
339 /* reset opendir and lstat to the original implementations */
340 opendir
= dirent_opendir
;
342 EnterCriticalSection(&mutex
);
344 LeaveCriticalSection(&mutex
);
350 * Lstat replacement, uses the cache if enabled, otherwise redirects to
353 int fscache_lstat(const char *filename
, struct stat
*st
)
355 int dirlen
, base
, len
;
356 struct fsentry key
[2], *fse
;
358 if (!fscache_enabled(filename
))
359 return mingw_lstat(filename
, st
);
361 /* split filename into path + name */
362 len
= strlen(filename
);
363 if (len
&& is_dir_sep(filename
[len
- 1]))
366 while (base
&& !is_dir_sep(filename
[base
- 1]))
368 dirlen
= base
? base
- 1 : 0;
370 /* lookup entry for path + name in cache */
371 fsentry_init(key
, NULL
, filename
, dirlen
);
372 fsentry_init(key
+ 1, key
, filename
+ base
, len
- base
);
373 fse
= fscache_get(key
+ 1);
384 st
->st_mode
= fse
->st_mode
;
385 st
->st_size
= fse
->st_size
;
386 st
->st_atime
= fse
->st_atime
;
387 st
->st_mtime
= fse
->st_mtime
;
388 st
->st_ctime
= fse
->st_ctime
;
390 /* don't forget to release fsentry */
391 fsentry_release(fse
);
395 typedef struct fscache_DIR
{
396 struct DIR base_dir
; /* extend base struct DIR */
397 struct fsentry
*pfsentry
;
398 struct dirent dirent
;
402 * Readdir replacement.
404 static struct dirent
*fscache_readdir(DIR *base_dir
)
406 fscache_DIR
*dir
= (fscache_DIR
*) base_dir
;
407 struct fsentry
*next
= dir
->pfsentry
->next
;
410 dir
->pfsentry
= next
;
411 dir
->dirent
.d_type
= S_ISDIR(next
->st_mode
) ? DT_DIR
: DT_REG
;
412 dir
->dirent
.d_name
= (char*) next
->name
;
413 return &(dir
->dirent
);
417 * Closedir replacement.
419 static int fscache_closedir(DIR *base_dir
)
421 fscache_DIR
*dir
= (fscache_DIR
*) base_dir
;
422 fsentry_release(dir
->pfsentry
);
428 * Opendir replacement, uses a directory listing from the cache if enabled,
429 * otherwise calls original dirent implementation.
431 DIR *fscache_opendir(const char *dirname
)
433 struct fsentry key
, *list
;
437 if (!fscache_enabled(dirname
))
438 return dirent_opendir(dirname
);
440 /* prepare name (strip trailing '/', replace '.') */
441 len
= strlen(dirname
);
442 if ((len
== 1 && dirname
[0] == '.') ||
443 (len
&& is_dir_sep(dirname
[len
- 1])))
446 /* get directory listing from cache */
447 fsentry_init(&key
, NULL
, dirname
, len
);
448 list
= fscache_get(&key
);
452 /* alloc and return DIR structure */
453 dir
= (fscache_DIR
*) xmalloc(sizeof(fscache_DIR
));
454 dir
->base_dir
.preaddir
= fscache_readdir
;
455 dir
->base_dir
.pclosedir
= fscache_closedir
;
456 dir
->pfsentry
= list
;