/* * Create an fsentry-based directory listing (similar to opendir / readdir). * Dir should not contain trailing '/'. Use an empty string for the current * directory (not "."!). */ static struct fsentry *fsentry_create_list(const struct fsentry *dir) { wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */ WIN32_FIND_DATAW fdata; HANDLE h; int wlen; struct fsentry *list, **phead; DWORD err; /* convert name to UTF-16 and check length */ if ((wlen = xutftowcs_path_ex(pattern, dir->name, MAX_LONG_PATH, dir->len, MAX_PATH - 2, core_long_paths)) < 0) return NULL; /* * append optional '\' and wildcard '*'. Note: we need to use '\' as * Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths. */ if (wlen) pattern[wlen++] = '\\'; pattern[wlen++] = '*'; pattern[wlen] = 0; /* open find handle */ h = FindFirstFileW(pattern, &fdata); if (h == INVALID_HANDLE_VALUE) { err = GetLastError(); errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); return NULL; } /* allocate object to hold directory listing */ list = fsentry_alloc(NULL, dir->name, dir->len); /* walk directory and build linked list of fsentry structures */ phead = &list->next; do { *phead = fseentry_create_entry(list, &fdata); phead = &(*phead)->next; } while (FindNextFileW(h, &fdata)); /* remember result of last FindNextFile, then close find handle */ err = GetLastError(); FindClose(h); /* return the list if we've got all the files */ if (err == ERROR_NO_MORE_FILES) return list; /* otherwise free the list and return error */ fsentry_release(list); errno = err_win_to_posix(err); return NULL; }
/* * Allocate and initialize an fsentry from a FILE_FULL_DIR_INFORMATION structure. */ static struct fsentry *fseentry_create_entry(struct fscache *cache, struct fsentry *list, PFILE_FULL_DIR_INFORMATION fdata) { char buf[MAX_PATH * 3]; int len; struct fsentry *fse; len = xwcstoutfn(buf, ARRAY_SIZE(buf), fdata->FileName, fdata->FileNameLength / sizeof(wchar_t)); fse = fsentry_alloc(cache, list, buf, len); /* * On certain Windows versions, host directories mapped into * Windows Containers ("Volumes", see https://docs.docker.com/storage/volumes/) * look like symbolic links, but their targets are paths that * are valid only in kernel mode. * * Let's work around this by detecting that situation and * telling Git that these are *not* symbolic links. */ if (fdata->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && fdata->EaSize == IO_REPARSE_TAG_SYMLINK && sizeof(buf) > (list ? list->len + 1 : 0) + fse->len + 1 && is_inside_windows_container()) { size_t off = 0; if (list) { memcpy(buf, list->name, list->len); buf[list->len] = '/'; off = list->len + 1; } memcpy(buf + off, fse->name, fse->len); buf[off + fse->len] = '\0'; } fse->st_mode = file_attr_to_st_mode(fdata->FileAttributes, fdata->EaSize, buf); fse->st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH : fdata->EndOfFile.LowPart | (((off_t)fdata->EndOfFile.HighPart) << 32); filetime_to_timespec((FILETIME *)&(fdata->LastAccessTime), &(fse->st_atim)); filetime_to_timespec((FILETIME *)&(fdata->LastWriteTime), &(fse->st_mtim)); filetime_to_timespec((FILETIME *)&(fdata->CreationTime), &(fse->st_ctim)); return fse; }
/* * Allocate and initialize an fsentry from a WIN32_FIND_DATA structure. */ static struct fsentry *fseentry_create_entry(struct fsentry *list, const WIN32_FIND_DATAW *fdata) { char buf[MAX_PATH * 3]; int len; struct fsentry *fse; len = xwcstoutf(buf, fdata->cFileName, ARRAY_SIZE(buf)); fse = fsentry_alloc(list, buf, len); fse->st_mode = file_attr_to_st_mode(fdata->dwFileAttributes, fdata->dwReserved0); fse->st_size = S_ISLNK(fse->st_mode) ? MAX_LONG_PATH : fdata->nFileSizeLow | (((off_t) fdata->nFileSizeHigh) << 32); filetime_to_timespec(&(fdata->ftLastAccessTime), &(fse->st_atim)); filetime_to_timespec(&(fdata->ftLastWriteTime), &(fse->st_mtim)); filetime_to_timespec(&(fdata->ftCreationTime), &(fse->st_ctim)); return fse; }
/* * Looks up or creates a cache entry for the specified key. */ static struct fsentry *fscache_get(struct fscache *cache, struct fsentry *key) { struct fsentry *fse; int dir_not_found; cache->fscache_requests++; /* check if entry is in cache */ fse = hashmap_get(&cache->map, key, NULL); if (fse) { if (fse->st_mode) fsentry_addref(fse); else fse = NULL; /* non-existing directory */ return fse; } /* if looking for a file, check if directory listing is in cache */ if (!fse && key->list) { fse = hashmap_get(&cache->map, key->list, NULL); if (fse) { /* * dir entry without file entry, or dir does not * exist -> file doesn't exist */ errno = ENOENT; return NULL; } } /* create the directory listing */ fse = fsentry_create_list(cache, key->list ? key->list : key, &dir_not_found); /* leave on error (errno set by fsentry_create_list) */ if (!fse) { if (dir_not_found && key->list) { /* * Record that the directory does not exist (or is * empty, which for all practical matters is the same * thing as far as fscache is concerned). */ fse = fsentry_alloc(cache, key->list->list, key->list->name, key->list->len); fse->st_mode = 0; hashmap_add(&cache->map, fse); } return NULL; } /* add directory listing to the cache */ cache->fscache_misses++; fscache_add(cache, fse); /* lookup file entry if requested (fse already points to directory) */ if (key->list) fse = hashmap_get(&cache->map, key, NULL); if (fse && !fse->st_mode) fse = NULL; /* non-existing directory */ /* return entry or ENOENT */ if (fse) fsentry_addref(fse); else errno = ENOENT; return fse; }
/* * Create an fsentry-based directory listing (similar to opendir / readdir). * Dir should not contain trailing '/'. Use an empty string for the current * directory (not "."!). */ static struct fsentry *fsentry_create_list(struct fscache *cache, const struct fsentry *dir, int *dir_not_found) { wchar_t pattern[MAX_LONG_PATH]; NTSTATUS status; IO_STATUS_BLOCK iosb; PFILE_FULL_DIR_INFORMATION di; HANDLE h; int wlen; struct fsentry *list, **phead; DWORD err; *dir_not_found = 0; /* convert name to UTF-16 and check length */ if ((wlen = xutftowcs_path_ex(pattern, dir->name, MAX_LONG_PATH, dir->len, MAX_PATH - 2, core_long_paths)) < 0) return NULL; /* handle CWD */ if (!wlen) { wlen = GetCurrentDirectoryW(ARRAY_SIZE(pattern), pattern); if (!wlen || wlen >= ARRAY_SIZE(pattern)) { errno = wlen ? ENAMETOOLONG : err_win_to_posix(GetLastError()); return NULL; } } h = CreateFileW(pattern, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (h == INVALID_HANDLE_VALUE) { err = GetLastError(); *dir_not_found = 1; /* or empty directory */ errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err); trace_printf_key(&trace_fscache, "fscache: error(%d) '%.*s'\n", errno, dir->len, dir->name); return NULL; } /* allocate object to hold directory listing */ list = fsentry_alloc(cache, NULL, dir->name, dir->len); list->st_mode = S_IFDIR; /* walk directory and build linked list of fsentry structures */ phead = &list->next; status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer, sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE); if (!NT_SUCCESS(status)) { /* * NtQueryDirectoryFile returns STATUS_INVALID_PARAMETER when * asked to enumerate an invalid directory (ie it is a file * instead of a directory). Verify that is the actual cause * of the error. */ if (status == STATUS_INVALID_PARAMETER) { DWORD attributes = GetFileAttributesW(pattern); if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) status = ERROR_DIRECTORY; } goto Error; } di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer); for (;;) { *phead = fseentry_create_entry(cache, list, di); phead = &(*phead)->next; /* If there is no offset in the entry, the buffer has been exhausted. */ if (di->NextEntryOffset == 0) { status = NtQueryDirectoryFile(h, NULL, 0, 0, &iosb, cache->buffer, sizeof(cache->buffer), FileFullDirectoryInformation, FALSE, NULL, FALSE); if (!NT_SUCCESS(status)) { if (status == STATUS_NO_MORE_FILES) break; goto Error; } di = (PFILE_FULL_DIR_INFORMATION)(cache->buffer); continue; } /* Advance to the next entry. */ di = (PFILE_FULL_DIR_INFORMATION)(((PUCHAR)di) + di->NextEntryOffset); } CloseHandle(h); return list; Error: errno = (status == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(status); trace_printf_key(&trace_fscache, "fscache: error(%d) unable to query directory contents '%.*s'\n", errno, dir->len, dir->name); CloseHandle(h); fsentry_release(list); return NULL; }
/* * Looks up or creates a cache entry for the specified key. */ static struct fsentry *fscache_get(struct fsentry *key) { struct fsentry *fse, *future, *waiter; int dir_not_found; EnterCriticalSection(&mutex); /* check if entry is in cache */ fse = fscache_get_wait(key); if (fse) { if (fse->st_mode) fsentry_addref(fse); else fse = NULL; /* non-existing directory */ LeaveCriticalSection(&mutex); return fse; } /* if looking for a file, check if directory listing is in cache */ if (!fse && key->list) { fse = fscache_get_wait(key->list); if (fse) { LeaveCriticalSection(&mutex); /* * dir entry without file entry, or dir does not * exist -> file doesn't exist */ errno = ENOENT; return NULL; } } /* add future entry to indicate that we're loading it */ future = key->list ? key->list : key; future->next = NULL; future->refcnt = 0; hashmap_add(&map, future); /* create the directory listing (outside mutex!) */ LeaveCriticalSection(&mutex); fse = fsentry_create_list(future, &dir_not_found); EnterCriticalSection(&mutex); /* remove future entry and signal waiting threads */ hashmap_remove(&map, future, NULL); waiter = future->next; while (waiter) { HANDLE h = waiter->hwait; waiter = waiter->next; SetEvent(h); } /* leave on error (errno set by fsentry_create_list) */ if (!fse) { if (dir_not_found && key->list) { /* * Record that the directory does not exist (or is * empty, which for all practical matters is the same * thing as far as fscache is concerned). */ fse = fsentry_alloc(key->list->list, key->list->name, key->list->len); fse->st_mode = 0; hashmap_add(&map, fse); } LeaveCriticalSection(&mutex); return NULL; } /* add directory listing to the cache */ fscache_add(fse); /* lookup file entry if requested (fse already points to directory) */ if (key->list) fse = hashmap_get(&map, key, NULL); if (fse && !fse->st_mode) fse = NULL; /* non-existing directory */ /* return entry or ENOENT */ if (fse) fsentry_addref(fse); else errno = ENOENT; LeaveCriticalSection(&mutex); return fse; }