Skip to content

Commit 2d3b20e

Browse files
billziss-ghdscho
authored andcommitted
mingw: lstat: compute correct size for symlinks
This commit fixes mingw_lstat by computing the proper size for symlinks according to POSIX. POSIX specifies that upon successful return from lstat: "the value of the st_size member shall be set to the length of the pathname contained in the symbolic link not including any terminating null byte". Prior to this commit the mingw_lstat function returned a fixed size of 4096. This caused problems in git repositories that were accessed by git for Cygwin or git for WSL. For example, doing `git reset --hard` using git for Windows would update the size of symlinks in the index to be 4096; at a later time git for Cygwin or git for WSL would find that symlinks have changed size during `git status`. Vice versa doing `git reset --hard` in git for Cygwin or git for WSL would update the size of symlinks in the index with the correct value, only for git for Windows to find incorrectly at a later time that the size had changed. Additional fixup by: Johannes Schindelin <[email protected]> Signed-off-by: Bill Zissimopoulos <[email protected]>
1 parent 977d6e0 commit 2d3b20e

File tree

1 file changed

+44
-21
lines changed

1 file changed

+44
-21
lines changed

compat/mingw.c

+44-21
Original file line numberDiff line numberDiff line change
@@ -907,10 +907,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
907907
return 1;
908908
}
909909

910+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
911+
char *tmpbuf, int *plen, DWORD *ptag);
912+
910913
int mingw_lstat(const char *file_name, struct stat *buf)
911914
{
912915
WIN32_FILE_ATTRIBUTE_DATA fdata;
913-
WIN32_FIND_DATAW findbuf = { 0 };
916+
DWORD reparse_tag = 0;
917+
int link_len = 0;
914918
wchar_t wfilename[MAX_LONG_PATH];
915919
int wlen = xutftowcs_long_path(wfilename, file_name);
916920
if (wlen < 0)
@@ -925,28 +929,29 @@ int mingw_lstat(const char *file_name, struct stat *buf)
925929
}
926930

927931
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
928-
/* for reparse points, use FindFirstFile to get the reparse tag */
932+
/* for reparse points, get the link tag and length */
929933
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
930-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
931-
if (handle == INVALID_HANDLE_VALUE)
932-
goto error;
933-
FindClose(handle);
934+
char tmpbuf[MAX_LONG_PATH];
935+
936+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
937+
&reparse_tag) < 0)
938+
return -1;
934939
}
935940
buf->st_ino = 0;
936941
buf->st_gid = 0;
937942
buf->st_uid = 0;
938943
buf->st_nlink = 1;
939944
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
940-
findbuf.dwReserved0, file_name);
941-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
945+
reparse_tag, file_name);
946+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
942947
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
943948
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
944949
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
945950
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
946951
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
947952
return 0;
948953
}
949-
error:
954+
950955
switch (GetLastError()) {
951956
case ERROR_ACCESS_DENIED:
952957
case ERROR_SHARING_VIOLATION:
@@ -2783,17 +2788,13 @@ typedef struct _REPARSE_DATA_BUFFER {
27832788
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
27842789
#endif
27852790

2786-
int readlink(const char *path, char *buf, size_t bufsiz)
2791+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
2792+
char *tmpbuf, int *plen, DWORD *ptag)
27872793
{
27882794
HANDLE handle;
2789-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
2795+
WCHAR *wbuf;
27902796
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
27912797
DWORD dummy;
2792-
char tmpbuf[MAX_LONG_PATH];
2793-
int len;
2794-
2795-
if (xutftowcs_long_path(wpath, path) < 0)
2796-
return -1;
27972798

27982799
/* read reparse point data */
27992800
handle = CreateFileW(wpath, 0,
@@ -2813,7 +2814,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
28132814
CloseHandle(handle);
28142815

28152816
/* get target path for symlinks or mount points (aka 'junctions') */
2816-
switch (b->ReparseTag) {
2817+
switch ((*ptag = b->ReparseTag)) {
28172818
case IO_REPARSE_TAG_SYMLINK:
28182819
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
28192820
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -2827,19 +2828,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
28272828
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
28282829
break;
28292830
default:
2830-
errno = EINVAL;
2831-
return -1;
2831+
if (fail_on_unknown_tag) {
2832+
errno = EINVAL;
2833+
return -1;
2834+
} else {
2835+
*plen = MAX_LONG_PATH;
2836+
return 0;
2837+
}
28322838
}
28332839

2840+
if ((*plen =
2841+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2842+
return -1;
2843+
return 0;
2844+
}
2845+
2846+
int readlink(const char *path, char *buf, size_t bufsiz)
2847+
{
2848+
WCHAR wpath[MAX_LONG_PATH];
2849+
char tmpbuf[MAX_LONG_PATH];
2850+
int len;
2851+
DWORD tag;
2852+
2853+
if (xutftowcs_long_path(wpath, path) < 0)
2854+
return -1;
2855+
2856+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
2857+
return -1;
2858+
28342859
/*
28352860
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
28362861
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
28372862
* condition. There is no conversion function that produces invalid UTF-8,
28382863
* so convert to a (hopefully large enough) temporary buffer, then memcpy
28392864
* the requested number of bytes (including '\0' for robustness).
28402865
*/
2841-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2842-
return -1;
28432866
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
28442867
return min(bufsiz, len);
28452868
}

0 commit comments

Comments
 (0)