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. */
36 /* Handle to wait on the loading thread. */
39 /* More stat members (only used for file entries). */
49 * Compares the paths of two fsentry structures for equality.
51 static int fsentry_cmp(const struct fsentry
*fse1
, const struct fsentry
*fse2
)
57 /* compare the list parts first */
58 if (fse1
->list
!= fse2
->list
&& (res
= fsentry_cmp(
59 fse1
->list
? fse1
->list
: fse1
,
60 fse2
->list
? fse2
->list
: fse2
)))
63 /* if list parts are equal, compare len and name */
64 if (fse1
->len
!= fse2
->len
)
65 return fse1
->len
- fse2
->len
;
66 return strnicmp(fse1
->name
, fse2
->name
, fse1
->len
);
70 * Calculates the hash code of an fsentry structure's path.
72 static unsigned int fsentry_hash(const struct fsentry
*fse
)
74 unsigned int hash
= fse
->list
? fse
->list
->ent
.hash
: 0;
75 return hash
^ memihash(fse
->name
, fse
->len
);
79 * Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp.
81 static void fsentry_init(struct fsentry
*fse
, struct fsentry
*list
,
82 const char *name
, size_t len
)
87 hashmap_entry_init(fse
, fsentry_hash(fse
));
91 * Allocate an fsentry structure on the heap.
93 static struct fsentry
*fsentry_alloc(struct fsentry
*list
, const char *name
,
96 /* overallocate fsentry and copy the name to the end */
97 struct fsentry
*fse
= xmalloc(sizeof(struct fsentry
) + len
+ 1);
98 char *nm
= ((char*) fse
) + sizeof(struct fsentry
);
99 memcpy(nm
, name
, len
);
101 /* init the rest of the structure */
102 fsentry_init(fse
, list
, nm
, len
);
109 * Add a reference to an fsentry.
111 inline static void fsentry_addref(struct fsentry
*fse
)
116 InterlockedIncrement(&(fse
->refcnt
));
120 * Release the reference to an fsentry, frees the memory if its the last ref.
122 static void fsentry_release(struct fsentry
*fse
)
127 if (InterlockedDecrement(&(fse
->refcnt
)))
131 struct fsentry
*next
= fse
->next
;
138 * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure.
140 static struct fsentry
*fseentry_create_entry(struct fsentry
*list
,
141 const WIN32_FIND_DATAW
*fdata
)
143 char buf
[MAX_PATH
* 3];
146 len
= xwcstoutf(buf
, fdata
->cFileName
, ARRAY_SIZE(buf
));
148 fse
= fsentry_alloc(list
, buf
, len
);
150 fse
->st_mode
= file_attr_to_st_mode(fdata
->dwFileAttributes
);
151 fse
->st_size
= (((off64_t
) (fdata
->nFileSizeHigh
)) << 32)
152 | fdata
->nFileSizeLow
;
153 fse
->st_atime
= filetime_to_time_t(&(fdata
->ftLastAccessTime
));
154 fse
->st_mtime
= filetime_to_time_t(&(fdata
->ftLastWriteTime
));
155 fse
->st_ctime
= filetime_to_time_t(&(fdata
->ftCreationTime
));
161 * Create an fsentry-based directory listing (similar to opendir / readdir).
162 * Dir should not contain trailing '/'. Use an empty string for the current
163 * directory (not "."!).
165 static struct fsentry
*fsentry_create_list(const struct fsentry
*dir
)
167 wchar_t pattern
[MAX_LONG_PATH
+ 2]; /* + 2 for "\*" */
168 WIN32_FIND_DATAW fdata
;
171 struct fsentry
*list
, **phead
;
174 /* convert name to UTF-16 and check length */
175 if ((wlen
= xutftowcs_path_ex(pattern
, dir
->name
, MAX_LONG_PATH
,
176 dir
->len
, MAX_PATH
- 2, core_long_paths
)) < 0)
180 * append optional '\' and wildcard '*'. Note: we need to use '\' as
181 * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
184 pattern
[wlen
++] = '\\';
185 pattern
[wlen
++] = '*';
188 /* open find handle */
189 h
= FindFirstFileW(pattern
, &fdata
);
190 if (h
== INVALID_HANDLE_VALUE
) {
191 err
= GetLastError();
192 errno
= (err
== ERROR_DIRECTORY
) ? ENOTDIR
: err_win_to_posix(err
);
196 /* allocate object to hold directory listing */
197 list
= fsentry_alloc(NULL
, dir
->name
, dir
->len
);
199 /* walk directory and build linked list of fsentry structures */
202 *phead
= fseentry_create_entry(list
, &fdata
);
203 phead
= &(*phead
)->next
;
204 } while (FindNextFileW(h
, &fdata
));
206 /* remember result of last FindNextFile, then close find handle */
207 err
= GetLastError();
210 /* return the list if we've got all the files */
211 if (err
== ERROR_NO_MORE_FILES
)
214 /* otherwise free the list and return error */
215 fsentry_release(list
);
216 errno
= err_win_to_posix(err
);
221 * Adds a directory listing to the cache.
223 static void fscache_add(struct fsentry
*fse
)
228 for (; fse
; fse
= fse
->next
)
229 hashmap_add(&map
, fse
);
233 * Removes a directory listing from the cache.
235 static void fscache_remove(struct fsentry
*fse
)
240 for (; fse
; fse
= fse
->next
)
241 hashmap_remove(&map
, fse
, NULL
);
247 static void fscache_clear()
249 struct hashmap_iter iter
;
251 while ((fse
= hashmap_iter_first(&map
, &iter
))) {
253 fsentry_release(fse
);
258 * Checks if the cache is enabled for the given path.
260 static inline int fscache_enabled(const char *path
)
262 return enabled
> 0 && !is_absolute_path(path
);
266 * Looks up a cache entry, waits if its being loaded by another thread.
267 * The mutex must be owned by the calling thread.
269 static struct fsentry
*fscache_get_wait(struct fsentry
*key
)
271 struct fsentry
*fse
= hashmap_get(&map
, key
, NULL
);
273 /* return if its a 'real' entry (future entries have refcnt == 0) */
274 if (!fse
|| fse
->list
|| fse
->refcnt
)
277 /* create an event and link our key to the future entry */
278 key
->hwait
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
279 key
->next
= fse
->next
;
282 /* wait for the loading thread to signal us */
283 LeaveCriticalSection(&mutex
);
284 WaitForSingleObject(key
->hwait
, INFINITE
);
285 CloseHandle(key
->hwait
);
286 EnterCriticalSection(&mutex
);
288 /* repeat cache lookup */
289 return hashmap_get(&map
, key
, NULL
);
293 * Looks up or creates a cache entry for the specified key.
295 static struct fsentry
*fscache_get(struct fsentry
*key
)
297 struct fsentry
*fse
, *future
, *waiter
;
299 EnterCriticalSection(&mutex
);
300 /* check if entry is in cache */
301 fse
= fscache_get_wait(key
);
304 LeaveCriticalSection(&mutex
);
307 /* if looking for a file, check if directory listing is in cache */
308 if (!fse
&& key
->list
) {
309 fse
= fscache_get_wait(key
->list
);
311 LeaveCriticalSection(&mutex
);
312 /* dir entry without file entry -> file doesn't exist */
318 /* add future entry to indicate that we're loading it */
319 future
= key
->list
? key
->list
: key
;
322 hashmap_add(&map
, future
);
324 /* create the directory listing (outside mutex!) */
325 LeaveCriticalSection(&mutex
);
326 fse
= fsentry_create_list(future
);
327 EnterCriticalSection(&mutex
);
329 /* remove future entry and signal waiting threads */
330 hashmap_remove(&map
, future
, NULL
);
331 waiter
= future
->next
;
333 HANDLE h
= waiter
->hwait
;
334 waiter
= waiter
->next
;
338 /* leave on error (errno set by fsentry_create_list) */
340 LeaveCriticalSection(&mutex
);
344 /* add directory listing to the cache */
347 /* lookup file entry if requested (fse already points to directory) */
349 fse
= hashmap_get(&map
, key
, NULL
);
351 /* return entry or ENOENT */
357 LeaveCriticalSection(&mutex
);
362 * Enables or disables the cache. Note that the cache is read-only, changes to
363 * the working directory are NOT reflected in the cache while enabled.
365 int fscache_enable(int enable
)
370 /* allow the cache to be disabled entirely */
374 InitializeCriticalSection(&mutex
);
375 hashmap_init(&map
, (hashmap_cmp_fn
) fsentry_cmp
, 0);
379 result
= enable
? InterlockedIncrement(&enabled
)
380 : InterlockedDecrement(&enabled
);
382 if (enable
&& result
== 1) {
383 /* redirect opendir and lstat to the fscache implementations */
384 opendir
= fscache_opendir
;
385 lstat
= fscache_lstat
;
386 } else if (!enable
&& !result
) {
387 /* reset opendir and lstat to the original implementations */
388 opendir
= dirent_opendir
;
390 EnterCriticalSection(&mutex
);
392 LeaveCriticalSection(&mutex
);
398 * Lstat replacement, uses the cache if enabled, otherwise redirects to
401 int fscache_lstat(const char *filename
, struct stat
*st
)
403 int dirlen
, base
, len
;
404 struct fsentry key
[2], *fse
;
406 if (!fscache_enabled(filename
))
407 return mingw_lstat(filename
, st
);
409 /* split filename into path + name */
410 len
= strlen(filename
);
411 if (len
&& is_dir_sep(filename
[len
- 1]))
414 while (base
&& !is_dir_sep(filename
[base
- 1]))
416 dirlen
= base
? base
- 1 : 0;
418 /* lookup entry for path + name in cache */
419 fsentry_init(key
, NULL
, filename
, dirlen
);
420 fsentry_init(key
+ 1, key
, filename
+ base
, len
- base
);
421 fse
= fscache_get(key
+ 1);
432 st
->st_mode
= fse
->st_mode
;
433 st
->st_size
= fse
->st_size
;
434 st
->st_atime
= fse
->st_atime
;
435 st
->st_mtime
= fse
->st_mtime
;
436 st
->st_ctime
= fse
->st_ctime
;
438 /* don't forget to release fsentry */
439 fsentry_release(fse
);
443 typedef struct fscache_DIR
{
444 struct DIR base_dir
; /* extend base struct DIR */
445 struct fsentry
*pfsentry
;
446 struct dirent dirent
;
450 * Readdir replacement.
452 static struct dirent
*fscache_readdir(DIR *base_dir
)
454 fscache_DIR
*dir
= (fscache_DIR
*) base_dir
;
455 struct fsentry
*next
= dir
->pfsentry
->next
;
458 dir
->pfsentry
= next
;
459 dir
->dirent
.d_type
= S_ISDIR(next
->st_mode
) ? DT_DIR
: DT_REG
;
460 dir
->dirent
.d_name
= (char*) next
->name
;
461 return &(dir
->dirent
);
465 * Closedir replacement.
467 static int fscache_closedir(DIR *base_dir
)
469 fscache_DIR
*dir
= (fscache_DIR
*) base_dir
;
470 fsentry_release(dir
->pfsentry
);
476 * Opendir replacement, uses a directory listing from the cache if enabled,
477 * otherwise calls original dirent implementation.
479 DIR *fscache_opendir(const char *dirname
)
481 struct fsentry key
, *list
;
485 if (!fscache_enabled(dirname
))
486 return dirent_opendir(dirname
);
488 /* prepare name (strip trailing '/', replace '.') */
489 len
= strlen(dirname
);
490 if ((len
== 1 && dirname
[0] == '.') ||
491 (len
&& is_dir_sep(dirname
[len
- 1])))
494 /* get directory listing from cache */
495 fsentry_init(&key
, NULL
, dirname
, len
);
496 list
= fscache_get(&key
);
500 /* alloc and return DIR structure */
501 dir
= (fscache_DIR
*) xmalloc(sizeof(fscache_DIR
));
502 dir
->base_dir
.preaddir
= fscache_readdir
;
503 dir
->base_dir
.pclosedir
= fscache_closedir
;
504 dir
->pfsentry
= list
;