Win32: add a cache below mingw's lstat and dirent implementations
[git/mingw/4msysgit.git] / compat / win32 / fscache.c
blob5388d64377b7002635f034661c5d5b22dc581f5a
1 #include "../../cache.h"
2 #include "../../hashmap.h"
3 #include "../win32.h"
4 #include "fscache.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
13 * and file entries.
15 struct fsentry {
16 struct hashmap_entry ent;
17 mode_t st_mode;
18 /* Length of name. */
19 unsigned short len;
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).
27 const char *name;
28 /* Pointer to the directory listing, or NULL for the listing itself. */
29 struct fsentry *list;
30 /* Pointer to the next file entry of the list. */
31 struct fsentry *next;
33 union {
34 /* Reference count of the directory listing. */
35 volatile long refcnt;
36 struct {
37 /* More stat members (only used for file entries). */
38 off64_t st_size;
39 time_t st_atime;
40 time_t st_mtime;
41 time_t st_ctime;
47 * Compares the paths of two fsentry structures for equality.
49 static int fsentry_cmp(const struct fsentry *fse1, const struct fsentry *fse2)
51 int res;
52 if (fse1 == fse2)
53 return 0;
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)))
59 return res;
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)
82 fse->list = list;
83 fse->name = name;
84 fse->len = 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,
92 size_t len)
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);
98 nm[len] = 0;
99 /* init the rest of the structure */
100 fsentry_init(fse, list, nm, len);
101 fse->next = NULL;
102 fse->refcnt = 1;
103 return fse;
107 * Add a reference to an fsentry.
109 inline static void fsentry_addref(struct fsentry *fse)
111 if (fse->list)
112 fse = fse->list;
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)
122 if (fse->list)
123 fse = fse->list;
125 if (InterlockedDecrement(&(fse->refcnt)))
126 return;
128 while (fse) {
129 struct fsentry *next = fse->next;
130 free(fse);
131 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];
142 int len;
143 struct fsentry *fse;
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));
155 return fse;
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;
167 HANDLE h;
168 int wlen;
169 struct fsentry *list, **phead;
170 DWORD err;
172 /* convert name to UTF-16 and check length < MAX_PATH */
173 if ((wlen = xutftowcsn(pattern, dir->name, MAX_PATH, dir->len)) < 0) {
174 if (errno == ERANGE)
175 errno = ENAMETOOLONG;
176 return NULL;
179 /* append optional '/' and wildcard '*' */
180 if (wlen)
181 pattern[wlen++] = '/';
182 pattern[wlen++] = '*';
183 pattern[wlen] = 0;
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);
190 return NULL;
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 */
197 phead = &list->next;
198 do {
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();
205 FindClose(h);
207 /* return the list if we've got all the files */
208 if (err == ERROR_NO_MORE_FILES)
209 return list;
211 /* otherwise free the list and return error */
212 fsentry_release(list);
213 errno = err_win_to_posix(err);
214 return NULL;
218 * Adds a directory listing to the cache.
220 static void fscache_add(struct fsentry *fse)
222 if (fse->list)
223 fse = fse->list;
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)
234 if (fse->list)
235 fse = fse->list;
237 for (; fse; fse = fse->next)
238 hashmap_remove(&map, fse, NULL);
242 * Clears the cache.
244 static void fscache_clear()
246 struct hashmap_iter iter;
247 struct fsentry *fse;
248 while ((fse = hashmap_iter_first(&map, &iter))) {
249 fscache_remove(fse);
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)
267 struct fsentry *fse;
269 EnterCriticalSection(&mutex);
270 /* check if entry is in cache */
271 fse = hashmap_get(&map, key, NULL);
272 if (fse) {
273 fsentry_addref(fse);
274 LeaveCriticalSection(&mutex);
275 return fse;
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);
280 if (fse) {
281 LeaveCriticalSection(&mutex);
282 /* dir entry without file entry -> file doesn't exist */
283 errno = ENOENT;
284 return NULL;
288 /* create the directory listing (outside mutex!) */
289 LeaveCriticalSection(&mutex);
290 fse = fsentry_create_list(key->list ? key->list : key);
291 if (!fse)
292 return NULL;
294 EnterCriticalSection(&mutex);
295 /* add directory listing if it hasn't been added by some other thread */
296 if (!hashmap_get(&map, key, NULL))
297 fscache_add(fse);
299 /* lookup file entry if requested (fse already points to directory) */
300 if (key->list)
301 fse = hashmap_get(&map, key, NULL);
303 /* return entry or ENOENT */
304 if (fse)
305 fsentry_addref(fse);
306 else
307 errno = ENOENT;
309 LeaveCriticalSection(&mutex);
310 return fse;
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)
319 int result;
321 if (!initialized) {
322 /* allow the cache to be disabled entirely */
323 if (!core_fscache)
324 return 0;
326 InitializeCriticalSection(&mutex);
327 hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, 0);
328 initialized = 1;
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;
341 lstat = mingw_lstat;
342 EnterCriticalSection(&mutex);
343 fscache_clear();
344 LeaveCriticalSection(&mutex);
346 return result;
350 * Lstat replacement, uses the cache if enabled, otherwise redirects to
351 * mingw_lstat.
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]))
364 len--;
365 base = len;
366 while (base && !is_dir_sep(filename[base - 1]))
367 base--;
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);
374 if (!fse)
375 return -1;
377 /* copy stat data */
378 st->st_ino = 0;
379 st->st_gid = 0;
380 st->st_uid = 0;
381 st->st_dev = 0;
382 st->st_rdev = 0;
383 st->st_nlink = 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);
392 return 0;
395 typedef struct fscache_DIR {
396 struct DIR base_dir; /* extend base struct DIR */
397 struct fsentry *pfsentry;
398 struct dirent dirent;
399 } fscache_DIR;
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;
408 if (!next)
409 return NULL;
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);
423 free(dir);
424 return 0;
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;
434 fscache_DIR *dir;
435 int len;
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])))
444 len--;
446 /* get directory listing from cache */
447 fsentry_init(&key, NULL, dirname, len);
448 list = fscache_get(&key);
449 if (!list)
450 return NULL;
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;
457 return (DIR*) dir;