msysGit-based Git for Windows 1.x was superseded by Git for Windows 2.x
[git/mingw/4msysgit.git] / compat / win32 / fscache.c
blob9e7e36ed391842b8e031e6fc55157a72b436694f
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 /* Handle to wait on the loading thread. */
37 HANDLE hwait;
38 struct {
39 /* More stat members (only used for file entries). */
40 off64_t st_size;
41 time_t st_atime;
42 time_t st_mtime;
43 time_t st_ctime;
49 * Compares the paths of two fsentry structures for equality.
51 static int fsentry_cmp(const struct fsentry *fse1, const struct fsentry *fse2)
53 int res;
54 if (fse1 == fse2)
55 return 0;
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)))
61 return res;
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)
84 fse->list = list;
85 fse->name = name;
86 fse->len = 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,
94 size_t len)
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);
100 nm[len] = 0;
101 /* init the rest of the structure */
102 fsentry_init(fse, list, nm, len);
103 fse->next = NULL;
104 fse->refcnt = 1;
105 return fse;
109 * Add a reference to an fsentry.
111 inline static void fsentry_addref(struct fsentry *fse)
113 if (fse->list)
114 fse = fse->list;
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)
124 if (fse->list)
125 fse = fse->list;
127 if (InterlockedDecrement(&(fse->refcnt)))
128 return;
130 while (fse) {
131 struct fsentry *next = fse->next;
132 free(fse);
133 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];
144 int len;
145 struct fsentry *fse;
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));
157 return fse;
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;
169 HANDLE h;
170 int wlen;
171 struct fsentry *list, **phead;
172 DWORD err;
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)
177 return NULL;
180 * append optional '\' and wildcard '*'. Note: we need to use '\' as
181 * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
183 if (wlen)
184 pattern[wlen++] = '\\';
185 pattern[wlen++] = '*';
186 pattern[wlen] = 0;
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);
193 return NULL;
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 */
200 phead = &list->next;
201 do {
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();
208 FindClose(h);
210 /* return the list if we've got all the files */
211 if (err == ERROR_NO_MORE_FILES)
212 return list;
214 /* otherwise free the list and return error */
215 fsentry_release(list);
216 errno = err_win_to_posix(err);
217 return NULL;
221 * Adds a directory listing to the cache.
223 static void fscache_add(struct fsentry *fse)
225 if (fse->list)
226 fse = fse->list;
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)
237 if (fse->list)
238 fse = fse->list;
240 for (; fse; fse = fse->next)
241 hashmap_remove(&map, fse, NULL);
245 * Clears the cache.
247 static void fscache_clear()
249 struct hashmap_iter iter;
250 struct fsentry *fse;
251 while ((fse = hashmap_iter_first(&map, &iter))) {
252 fscache_remove(fse);
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)
275 return fse;
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;
280 fse->next = key;
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);
302 if (fse) {
303 fsentry_addref(fse);
304 LeaveCriticalSection(&mutex);
305 return fse;
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);
310 if (fse) {
311 LeaveCriticalSection(&mutex);
312 /* dir entry without file entry -> file doesn't exist */
313 errno = ENOENT;
314 return NULL;
318 /* add future entry to indicate that we're loading it */
319 future = key->list ? key->list : key;
320 future->next = NULL;
321 future->refcnt = 0;
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;
332 while (waiter) {
333 HANDLE h = waiter->hwait;
334 waiter = waiter->next;
335 SetEvent(h);
338 /* leave on error (errno set by fsentry_create_list) */
339 if (!fse) {
340 LeaveCriticalSection(&mutex);
341 return NULL;
344 /* add directory listing to the cache */
345 fscache_add(fse);
347 /* lookup file entry if requested (fse already points to directory) */
348 if (key->list)
349 fse = hashmap_get(&map, key, NULL);
351 /* return entry or ENOENT */
352 if (fse)
353 fsentry_addref(fse);
354 else
355 errno = ENOENT;
357 LeaveCriticalSection(&mutex);
358 return fse;
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)
367 int result;
369 if (!initialized) {
370 /* allow the cache to be disabled entirely */
371 if (!core_fscache)
372 return 0;
374 InitializeCriticalSection(&mutex);
375 hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, 0);
376 initialized = 1;
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;
389 lstat = mingw_lstat;
390 EnterCriticalSection(&mutex);
391 fscache_clear();
392 LeaveCriticalSection(&mutex);
394 return result;
398 * Lstat replacement, uses the cache if enabled, otherwise redirects to
399 * mingw_lstat.
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]))
412 len--;
413 base = len;
414 while (base && !is_dir_sep(filename[base - 1]))
415 base--;
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);
422 if (!fse)
423 return -1;
425 /* copy stat data */
426 st->st_ino = 0;
427 st->st_gid = 0;
428 st->st_uid = 0;
429 st->st_dev = 0;
430 st->st_rdev = 0;
431 st->st_nlink = 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);
440 return 0;
443 typedef struct fscache_DIR {
444 struct DIR base_dir; /* extend base struct DIR */
445 struct fsentry *pfsentry;
446 struct dirent dirent;
447 } fscache_DIR;
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;
456 if (!next)
457 return NULL;
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);
471 free(dir);
472 return 0;
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;
482 fscache_DIR *dir;
483 int len;
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])))
492 len--;
494 /* get directory listing from cache */
495 fsentry_init(&key, NULL, dirname, len);
496 list = fscache_get(&key);
497 if (!list)
498 return NULL;
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;
505 return (DIR*) dir;