Skip to content

Commit a00842c

Browse files
committed
win32: correctly unlink symlinks to directories
When deleting a symlink on Windows, then the way to delete it depends on whether it is a directory symlink or a file symlink. In the first case, we need to use `DeleteFile`, in the second `RemoveDirectory`. Right now, `p_unlink` will only ever try to use `DeleteFile`, though, and thus fail to remove directory symlinks. This mismatches how unlink(3P) is expected to behave, though, as it shall remove any symlink disregarding whether it is a file or directory symlink. In order to correctly unlink a symlink, we thus need to check what kind of file this is. If we were to first query file attributes of every file upon calling `p_unlink`, then this would penalize the common case though. Instead, we can try to first delete the file with `DeleteFile` and only if the error returned is `ERROR_ACCESS_DENIED` will we query file attributes and determine whether it is a directory symlink to use `RemoveDirectory` instead.
1 parent ded77bb commit a00842c

File tree

1 file changed

+16
-0
lines changed

1 file changed

+16
-0
lines changed

src/win32/posix_w32.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,25 @@ int p_link(const char *old, const char *new)
251251

252252
GIT_INLINE(int) unlink_once(const wchar_t *path)
253253
{
254+
DWORD error;
255+
254256
if (DeleteFileW(path))
255257
return 0;
256258

259+
if ((error = GetLastError()) == ERROR_ACCESS_DENIED) {
260+
WIN32_FILE_ATTRIBUTE_DATA fdata;
261+
if (!GetFileAttributesExW(path, GetFileExInfoStandard, &fdata) ||
262+
!(fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
263+
!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
264+
goto out;
265+
266+
if (RemoveDirectoryW(path))
267+
return 0;
268+
}
269+
270+
out:
271+
SetLastError(error);
272+
257273
if (last_error_retryable())
258274
return GIT_RETRY;
259275

0 commit comments

Comments
 (0)