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_LONG_PATH
+ 2]; /* + 2 for "\*" */
166 WIN32_FIND_DATAW fdata
;
169 struct fsentry
*list
, **phead
;
172 /* convert name to UTF-16 and check length */
173 if ((wlen
= xutftowcs_path_ex(pattern
, dir
->name
, MAX_LONG_PATH
,
174 dir
->len
, MAX_PATH
- 2, core_long_paths
)) < 0)
178 * append optional '\' and wildcard '*'. Note: we need to use '\' as
179 * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
182 pattern
[wlen
++] = '\\';
183 pattern
[wlen
++] = '*';
186 /* open find handle */
187 h
= FindFirstFileW(pattern
, &fdata
);
188 if (h
== INVALID_HANDLE_VALUE
) {
189 err
= GetLastError();
190 errno
= (err
== ERROR_DIRECTORY
) ? ENOTDIR
: err_win_to_posix(err
);
194 /* allocate object to hold directory listing */
195 list
= fsentry_alloc(NULL
, dir
->name
, dir
->len
);
197 /* walk directory and build linked list of fsentry structures */
200 *phead
= fseentry_create_entry(list
, &fdata
);
201 phead
= &(*phead
)->next
;
202 } while (FindNextFileW(h
, &fdata
));
204 /* remember result of last FindNextFile, then close find handle */
205 err
= GetLastError();
208 /* return the list if we've got all the files */
209 if (err
== ERROR_NO_MORE_FILES
)
212 /* otherwise free the list and return error */
213 fsentry_release(list
);
214 errno
= err_win_to_posix(err
);
219 * Adds a directory listing to the cache.
221 static void fscache_add(struct fsentry
*fse
)
226 for (; fse
; fse
= fse
->next
)
227 hashmap_add(&map
, fse
);
231 * Removes a directory listing from the cache.
233 static void fscache_remove(struct fsentry
*fse
)
238 for (; fse
; fse
= fse
->next
)
239 hashmap_remove(&map
, fse
, NULL
);
245 static void fscache_clear()
247 struct hashmap_iter iter
;
249 while ((fse
= hashmap_iter_first(&map
, &iter
))) {
251 fsentry_release(fse
);
256 * Checks if the cache is enabled for the given path.
258 static inline int fscache_enabled(const char *path
)
260 return enabled
> 0 && !is_absolute_path(path
);
264 * Looks up or creates a cache entry for the specified key.
266 static struct fsentry
*fscache_get(struct fsentry
*key
)
270 EnterCriticalSection(&mutex
);
271 /* check if entry is in cache */
272 fse
= hashmap_get(&map
, key
, NULL
);
275 LeaveCriticalSection(&mutex
);
278 /* if looking for a file, check if directory listing is in cache */
279 if (!fse
&& key
->list
) {
280 fse
= hashmap_get(&map
, key
->list
, NULL
);
282 LeaveCriticalSection(&mutex
);
283 /* dir entry without file entry -> file doesn't exist */
289 /* create the directory listing (outside mutex!) */
290 LeaveCriticalSection(&mutex
);
291 fse
= fsentry_create_list(key
->list
? key
->list
: key
);
295 EnterCriticalSection(&mutex
);
296 /* add directory listing if it hasn't been added by some other thread */
297 if (!hashmap_get(&map
, key
, NULL
))
300 /* lookup file entry if requested (fse already points to directory) */
302 fse
= hashmap_get(&map
, key
, NULL
);
304 /* return entry or ENOENT */
310 LeaveCriticalSection(&mutex
);
315 * Enables or disables the cache. Note that the cache is read-only, changes to
316 * the working directory are NOT reflected in the cache while enabled.
318 int fscache_enable(int enable
)
323 /* allow the cache to be disabled entirely */
327 InitializeCriticalSection(&mutex
);
328 hashmap_init(&map
, (hashmap_cmp_fn
) fsentry_cmp
, 0);
332 result
= enable
? InterlockedIncrement(&enabled
)
333 : InterlockedDecrement(&enabled
);
335 if (enable
&& result
== 1) {
336 /* redirect opendir and lstat to the fscache implementations */
337 opendir
= fscache_opendir
;
338 lstat
= fscache_lstat
;
339 } else if (!enable
&& !result
) {
340 /* reset opendir and lstat to the original implementations */
341 opendir
= dirent_opendir
;
343 EnterCriticalSection(&mutex
);
345 LeaveCriticalSection(&mutex
);
351 * Lstat replacement, uses the cache if enabled, otherwise redirects to
354 int fscache_lstat(const char *filename
, struct stat
*st
)
356 int dirlen
, base
, len
;
357 struct fsentry key
[2], *fse
;
359 if (!fscache_enabled(filename
))
360 return mingw_lstat(filename
, st
);
362 /* split filename into path + name */
363 len
= strlen(filename
);
364 if (len
&& is_dir_sep(filename
[len
- 1]))
367 while (base
&& !is_dir_sep(filename
[base
- 1]))
369 dirlen
= base
? base
- 1 : 0;
371 /* lookup entry for path + name in cache */
372 fsentry_init(key
, NULL
, filename
, dirlen
);
373 fsentry_init(key
+ 1, key
, filename
+ base
, len
- base
);
374 fse
= fscache_get(key
+ 1);
385 st
->st_mode
= fse
->st_mode
;
386 st
->st_size
= fse
->st_size
;
387 st
->st_atime
= fse
->st_atime
;
388 st
->st_mtime
= fse
->st_mtime
;
389 st
->st_ctime
= fse
->st_ctime
;
391 /* don't forget to release fsentry */
392 fsentry_release(fse
);
396 typedef struct fscache_DIR
{
397 struct DIR base_dir
; /* extend base struct DIR */
398 struct fsentry
*pfsentry
;
399 struct dirent dirent
;
403 * Readdir replacement.
405 static struct dirent
*fscache_readdir(DIR *base_dir
)
407 fscache_DIR
*dir
= (fscache_DIR
*) base_dir
;
408 struct fsentry
*next
= dir
->pfsentry
->next
;
411 dir
->pfsentry
= next
;
412 dir
->dirent
.d_type
= S_ISDIR(next
->st_mode
) ? DT_DIR
: DT_REG
;
413 dir
->dirent
.d_name
= (char*) next
->name
;
414 return &(dir
->dirent
);
418 * Closedir replacement.
420 static int fscache_closedir(DIR *base_dir
)
422 fscache_DIR
*dir
= (fscache_DIR
*) base_dir
;
423 fsentry_release(dir
->pfsentry
);
429 * Opendir replacement, uses a directory listing from the cache if enabled,
430 * otherwise calls original dirent implementation.
432 DIR *fscache_opendir(const char *dirname
)
434 struct fsentry key
, *list
;
438 if (!fscache_enabled(dirname
))
439 return dirent_opendir(dirname
);
441 /* prepare name (strip trailing '/', replace '.') */
442 len
= strlen(dirname
);
443 if ((len
== 1 && dirname
[0] == '.') ||
444 (len
&& is_dir_sep(dirname
[len
- 1])))
447 /* get directory listing from cache */
448 fsentry_init(&key
, NULL
, dirname
, len
);
449 list
= fscache_get(&key
);
453 /* alloc and return DIR structure */
454 dir
= (fscache_DIR
*) xmalloc(sizeof(fscache_DIR
));
455 dir
->base_dir
.preaddir
= fscache_readdir
;
456 dir
->base_dir
.pclosedir
= fscache_closedir
;
457 dir
->pfsentry
= list
;