Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 127 additions & 39 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 = &current->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());
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down