diff --git a/compat/mingw.c b/compat/mingw.c index 92946af422770c..0f50074995c71f 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -9,6 +9,7 @@ #include "dir.h" #include "environment.h" #include "gettext.h" +#include "hashmap.h" #include "repository.h" #include "run-command.h" #include "strbuf.h" @@ -448,43 +449,117 @@ process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink) return PHANTOM_SYMLINK_RETRY; } -/* keep track of newly created symlinks to non-existing targets */ +/* + * Newly created symlinks to non-existing targets are indexed by a + * hashmap keyed by their (canonicalized, absolute) target path, so + * that mkdir() creating that path -- or a symlink at that path turning + * out to be a directory symlink -- wakes only the entries actually + * waiting on it, instead of re-probing every phantom symlink created + * so far. + */ struct phantom_symlink_info { - struct phantom_symlink_info *next; wchar_t *wlink; +}; + +struct phantom_symlink_target { + struct hashmap_entry ent; wchar_t *wtarget; + size_t nr, alloc; + struct phantom_symlink_info *items; + char target[FLEX_ARRAY]; }; -static struct phantom_symlink_info *phantom_symlinks = NULL; +static int phantom_symlink_target_cmp(const void *cmp_data UNUSED, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct phantom_symlink_target *e = + container_of(eptr, const struct phantom_symlink_target, ent); + + return !fspatheq(e->target, keydata ? keydata : + container_of(entry_or_key, const struct phantom_symlink_target, + ent)->target); +} + +static struct hashmap phantom_symlinks = + HASHMAP_INIT(phantom_symlink_target_cmp, NULL); static CRITICAL_SECTION phantom_symlinks_cs; -static void process_phantom_symlinks(void) +static wchar_t *xwcsdup(const wchar_t *s) { - struct phantom_symlink_info *current, **psi; + size_t size = sizeof(wchar_t) * (wcslen(s) + 1); + return memcpy(xmalloc(size), s, size); +} + +/* + * Returns the canonicalized, absolute UTF-8 form of wpath. If wout is + * non-NULL, also fills it with the canonicalized, absolute wide form + * (must have room for at least MAX_LONG_PATH wchar_t). + */ +static char *canonicalize_path(const wchar_t *wpath, wchar_t *wout) +{ + wchar_t wfullpath[MAX_LONG_PATH]; + char utf8[MAX_LONG_PATH * 3]; + int len = GetFullPathNameW(wpath, ARRAY_SIZE(wfullpath), wfullpath, NULL); + + if (!len || len >= ARRAY_SIZE(wfullpath) || + xwcstoutf(utf8, wfullpath, sizeof(utf8)) < 0) + return NULL; + if (wout) + wcscpy(wout, wfullpath); + return xstrdup(utf8); +} + +/* + * Wakes every phantom symlink whose target is exactly wpath: mkdir() + * creating that path, or a symlink at that path turning out to be a + * directory symlink, may let them resolve. + */ +static void process_phantom_symlinks(const wchar_t *wpath) +{ + char *target_key = canonicalize_path(wpath, NULL); + struct hashmap_entry key; + struct phantom_symlink_target *e; + size_t i; + + if (!target_key) + return; + EnterCriticalSection(&phantom_symlinks_cs); - /* process phantom symlinks list */ - psi = &phantom_symlinks; - while ((current = *psi)) { - enum phantom_symlink_result result = process_phantom_symlink( - current->wtarget, current->wlink); + hashmap_entry_init(&key, fspathhash(target_key)); + e = hashmap_get_entry_from_hash(&phantom_symlinks, key.hash, target_key, + struct phantom_symlink_target, ent); + + for (i = 0; e && i < e->nr; ) { + enum phantom_symlink_result result = + process_phantom_symlink(e->wtarget, e->items[i].wlink); + wchar_t *wlink = e->items[i].wlink; + if (result == PHANTOM_SYMLINK_RETRY) { - psi = ¤t->next; - } else { - /* symlink was processed, remove from list */ - *psi = current->next; - free(current); - /* if symlink was a directory, start over */ - if (result == PHANTOM_SYMLINK_DIRECTORY) - psi = &phantom_symlinks; + i++; + continue; } + + e->items[i] = e->items[--e->nr]; + if (result == PHANTOM_SYMLINK_DIRECTORY) + process_phantom_symlinks(wlink); + free(wlink); + } + + if (e && !e->nr) { + hashmap_remove(&phantom_symlinks, &e->ent, target_key); + free(e->wtarget); + free(e->items); + free(e); } LeaveCriticalSection(&phantom_symlinks_cs); + + free(target_key); } static int create_phantom_symlink(wchar_t *wtarget, wchar_t *wlink) { - int len; - /* create file symlink */ if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) { errno = err_win_to_posix(GetLastError()); @@ -494,34 +569,47 @@ static int create_phantom_symlink(wchar_t *wtarget, wchar_t *wlink) /* convert to directory symlink if target exists */ switch (process_phantom_symlink(wtarget, wlink)) { case PHANTOM_SYMLINK_RETRY: { - /* if target doesn't exist, add to phantom symlinks list */ - wchar_t wfullpath[MAX_LONG_PATH]; - struct phantom_symlink_info *psi; + /* the path process_phantom_symlink() itself probes */ + wchar_t relative[MAX_LONG_PATH], wfulltarget[MAX_LONG_PATH]; + wchar_t wfulllink[MAX_LONG_PATH]; + const wchar_t *rel = make_relative_to(wtarget, wlink, relative, + ARRAY_SIZE(relative)); + char *target_key = rel ? canonicalize_path(rel, wfulltarget) : NULL; + struct hashmap_entry key; + struct phantom_symlink_target *e; + int len; + + if (!target_key) + break; /* convert to absolute path to be independent of cwd */ - len = GetFullPathNameW(wlink, MAX_LONG_PATH, wfullpath, NULL); - if (!len || len >= MAX_LONG_PATH) { + len = GetFullPathNameW(wlink, ARRAY_SIZE(wfulllink), wfulllink, NULL); + if (!len || len >= ARRAY_SIZE(wfulllink)) { errno = err_win_to_posix(GetLastError()); + free(target_key); return -1; } - /* over-allocate and fill phantom_symlink_info structure */ - psi = xmalloc(sizeof(struct phantom_symlink_info) + - sizeof(wchar_t) * (len + wcslen(wtarget) + 2)); - psi->wlink = (wchar_t *)(psi + 1); - wcscpy(psi->wlink, wfullpath); - psi->wtarget = psi->wlink + len + 1; - wcscpy(psi->wtarget, wtarget); - EnterCriticalSection(&phantom_symlinks_cs); - psi->next = phantom_symlinks; - phantom_symlinks = psi; + hashmap_entry_init(&key, fspathhash(target_key)); + e = hashmap_get_entry_from_hash(&phantom_symlinks, key.hash, + target_key, + struct phantom_symlink_target, ent); + if (!e) { + FLEX_ALLOC_STR(e, target, target_key); + e->wtarget = xwcsdup(wfulltarget); + hashmap_entry_init(&e->ent, key.hash); + hashmap_add(&phantom_symlinks, &e->ent); + } + ALLOC_GROW(e->items, e->nr + 1, e->alloc); + e->items[e->nr++].wlink = xwcsdup(wfulllink); LeaveCriticalSection(&phantom_symlinks_cs); + free(target_key); break; } case PHANTOM_SYMLINK_DIRECTORY: - /* if we created a dir symlink, process other phantom symlinks */ - process_phantom_symlinks(); + /* if we created a dir symlink, wake others waiting on it */ + process_phantom_symlinks(wlink); break; default: break; @@ -759,7 +847,7 @@ int mingw_mkdir(const char *path, int mode UNUSED) ret = _wmkdir(wpath); if (!ret) - process_phantom_symlinks(); + process_phantom_symlinks(wpath); if (!ret && needs_hiding(path)) return set_hidden_flag(wpath, 1); return ret; @@ -3495,7 +3583,7 @@ int mingw_create_symlink(struct index_state *index, const char *target, const ch break; /* There may be dangling phantom symlinks that point at this * one, which should now morph into directory symlinks. */ - process_phantom_symlinks(); + process_phantom_symlinks(wlink); return 0; default: BUG("unhandled symlink type");