test: Differentiate ability for gnu utils to handle symlinks from git
[git/mingw/4msysgit.git] / compat / win32 / fscache.c
blobda3bb2cae1d05e6041d6fb74d171a89acf13d98a
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_LONG_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 */
173 if ((wlen = xutftowcs_path_ex(pattern, dir->name, MAX_LONG_PATH,
174 dir->len, MAX_PATH - 2, core_long_paths)) < 0)
175 return NULL;
178 * append optional '\' and wildcard '*'. Note: we need to use '\' as
179 * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
181 if (wlen)
182 pattern[wlen++] = '\\';
183 pattern[wlen++] = '*';
184 pattern[wlen] = 0;
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);
191 return NULL;
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 */
198 phead = &list->next;
199 do {
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();
206 FindClose(h);
208 /* return the list if we've got all the files */
209 if (err == ERROR_NO_MORE_FILES)
210 return list;
212 /* otherwise free the list and return error */
213 fsentry_release(list);
214 errno = err_win_to_posix(err);
215 return NULL;
219 * Adds a directory listing to the cache.
221 static void fscache_add(struct fsentry *fse)
223 if (fse->list)
224 fse = fse->list;
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)
235 if (fse->list)
236 fse = fse->list;
238 for (; fse; fse = fse->next)
239 hashmap_remove(&map, fse, NULL);
243 * Clears the cache.
245 static void fscache_clear()
247 struct hashmap_iter iter;
248 struct fsentry *fse;
249 while ((fse = hashmap_iter_first(&map, &iter))) {
250 fscache_remove(fse);
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)
268 struct fsentry *fse;
270 EnterCriticalSection(&mutex);
271 /* check if entry is in cache */
272 fse = hashmap_get(&map, key, NULL);
273 if (fse) {
274 fsentry_addref(fse);
275 LeaveCriticalSection(&mutex);
276 return fse;
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);
281 if (fse) {
282 LeaveCriticalSection(&mutex);
283 /* dir entry without file entry -> file doesn't exist */
284 errno = ENOENT;
285 return NULL;
289 /* create the directory listing (outside mutex!) */
290 LeaveCriticalSection(&mutex);
291 fse = fsentry_create_list(key->list ? key->list : key);
292 if (!fse)
293 return NULL;
295 EnterCriticalSection(&mutex);
296 /* add directory listing if it hasn't been added by some other thread */
297 if (!hashmap_get(&map, key, NULL))
298 fscache_add(fse);
300 /* lookup file entry if requested (fse already points to directory) */
301 if (key->list)
302 fse = hashmap_get(&map, key, NULL);
304 /* return entry or ENOENT */
305 if (fse)
306 fsentry_addref(fse);
307 else
308 errno = ENOENT;
310 LeaveCriticalSection(&mutex);
311 return fse;
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)
320 int result;
322 if (!initialized) {
323 /* allow the cache to be disabled entirely */
324 if (!core_fscache)
325 return 0;
327 InitializeCriticalSection(&mutex);
328 hashmap_init(&map, (hashmap_cmp_fn) fsentry_cmp, 0);
329 initialized = 1;
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;
342 lstat = mingw_lstat;
343 EnterCriticalSection(&mutex);
344 fscache_clear();
345 LeaveCriticalSection(&mutex);
347 return result;
351 * Lstat replacement, uses the cache if enabled, otherwise redirects to
352 * mingw_lstat.
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]))
365 len--;
366 base = len;
367 while (base && !is_dir_sep(filename[base - 1]))
368 base--;
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);
375 if (!fse)
376 return -1;
378 /* copy stat data */
379 st->st_ino = 0;
380 st->st_gid = 0;
381 st->st_uid = 0;
382 st->st_dev = 0;
383 st->st_rdev = 0;
384 st->st_nlink = 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);
393 return 0;
396 typedef struct fscache_DIR {
397 struct DIR base_dir; /* extend base struct DIR */
398 struct fsentry *pfsentry;
399 struct dirent dirent;
400 } fscache_DIR;
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;
409 if (!next)
410 return NULL;
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);
424 free(dir);
425 return 0;
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;
435 fscache_DIR *dir;
436 int len;
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])))
445 len--;
447 /* get directory listing from cache */
448 fsentry_init(&key, NULL, dirname, len);
449 list = fscache_get(&key);
450 if (!list)
451 return NULL;
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;
458 return (DIR*) dir;