Skip to content

Commit 50194dc

Browse files
committed
win32: fix symlinks to relative file targets
When creating a symlink in Windows, one needs to tell Windows whether the symlink should be a file or directory symlink. To determine which flag to pass, we call `GetFileAttributesW` on the target file to see whether it is a directory and then pass the flag accordingly. The problem though is if create a symlink with a relative target path, then we will check that relative path while not necessarily being inside of the working directory where the symlink is to be created. Thus, getting its attributes will either fail or return attributes of the wrong target. Fix this by resolving the target path relative to the directory in which the symlink is to be created.
1 parent 93d37a1 commit 50194dc

File tree

2 files changed

+46
-3
lines changed

2 files changed

+46
-3
lines changed

src/win32/posix_w32.c

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,18 +414,37 @@ int p_readlink(const char *path, char *buf, size_t bufsiz)
414414
return (int)bufsiz;
415415
}
416416

417+
static bool target_is_dir(const char *target, const char *path)
418+
{
419+
git_buf resolved = GIT_BUF_INIT;
420+
git_win32_path resolved_w;
421+
bool isdir = true;
422+
423+
if (git_path_is_absolute(target))
424+
git_win32_path_from_utf8(resolved_w, target);
425+
else if (git_path_dirname_r(&resolved, path) < 0 ||
426+
git_path_apply_relative(&resolved, target) < 0 ||
427+
git_win32_path_from_utf8(resolved_w, resolved.ptr) < 0)
428+
goto out;
429+
430+
isdir = GetFileAttributesW(resolved_w) & FILE_ATTRIBUTE_DIRECTORY;
431+
432+
out:
433+
git_buf_dispose(&resolved);
434+
return isdir;
435+
}
436+
417437
int p_symlink(const char *target, const char *path)
418438
{
419439
git_win32_path target_w, path_w;
420440
DWORD dwFlags;
421441

422442
if (git_win32_path_from_utf8(path_w, path) < 0 ||
423-
git__utf8_to_16(target_w, MAX_PATH, target) < 0)
443+
git__utf8_to_16(target_w, MAX_PATH, target) < 0)
424444
return -1;
425445

426446
dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
427-
428-
if (GetFileAttributesW(target_w) & FILE_ATTRIBUTE_DIRECTORY)
447+
if (target_is_dir(target, path))
429448
dwFlags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
430449

431450
if (!CreateSymbolicLinkW(path_w, target_w, dwFlags))

tests/core/posix.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,27 @@ void test_core_posix__unlink_removes_symlink(void)
285285
cl_must_pass(p_unlink("file"));
286286
cl_must_pass(p_rmdir("dir"));
287287
}
288+
289+
void test_core_posix__symlink_resolves_to_correct_type(void)
290+
{
291+
git_buf contents = GIT_BUF_INIT;
292+
293+
if (!git_path_supports_symlinks(clar_sandbox_path()))
294+
clar__skip();
295+
296+
cl_must_pass(git_futils_mkdir("dir", 0777, 0));
297+
cl_must_pass(git_futils_mkdir("file", 0777, 0));
298+
cl_git_mkfile("dir/file", "symlink target");
299+
300+
cl_git_pass(p_symlink("file", "dir/link"));
301+
302+
cl_git_pass(git_futils_readbuffer(&contents, "dir/file"));
303+
cl_assert_equal_s(contents.ptr, "symlink target");
304+
305+
cl_must_pass(p_unlink("dir/link"));
306+
cl_must_pass(p_unlink("dir/file"));
307+
cl_must_pass(p_rmdir("dir"));
308+
cl_must_pass(p_rmdir("file"));
309+
310+
git_buf_dispose(&contents);
311+
}

0 commit comments

Comments
 (0)