Skip to content

Commit 79f11dc

Browse files
billziss-ghGit for Windows Build Agent
authored and
Git for Windows Build Agent
committed
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 9e87a69 commit 79f11dc

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
@@ -920,10 +920,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
920920
return 1;
921921
}
922922

923+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
924+
char *tmpbuf, int *plen, DWORD *ptag);
925+
923926
int mingw_lstat(const char *file_name, struct stat *buf)
924927
{
925928
WIN32_FILE_ATTRIBUTE_DATA fdata;
926-
WIN32_FIND_DATAW findbuf = { 0 };
929+
DWORD reparse_tag = 0;
930+
int link_len = 0;
927931
wchar_t wfilename[MAX_LONG_PATH];
928932
int wlen = xutftowcs_long_path(wfilename, file_name);
929933
if (wlen < 0)
@@ -938,28 +942,29 @@ int mingw_lstat(const char *file_name, struct stat *buf)
938942
}
939943

940944
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
941-
/* for reparse points, use FindFirstFile to get the reparse tag */
945+
/* for reparse points, get the link tag and length */
942946
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
943-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
944-
if (handle == INVALID_HANDLE_VALUE)
945-
goto error;
946-
FindClose(handle);
947+
char tmpbuf[MAX_LONG_PATH];
948+
949+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
950+
&reparse_tag) < 0)
951+
return -1;
947952
}
948953
buf->st_ino = 0;
949954
buf->st_gid = 0;
950955
buf->st_uid = 0;
951956
buf->st_nlink = 1;
952957
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
953-
findbuf.dwReserved0, file_name);
954-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
958+
reparse_tag, file_name);
959+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
955960
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
956961
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
957962
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
958963
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
959964
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
960965
return 0;
961966
}
962-
error:
967+
963968
switch (GetLastError()) {
964969
case ERROR_ACCESS_DENIED:
965970
case ERROR_SHARING_VIOLATION:
@@ -2831,17 +2836,13 @@ typedef struct _REPARSE_DATA_BUFFER {
28312836
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
28322837
#endif
28332838

2834-
int readlink(const char *path, char *buf, size_t bufsiz)
2839+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
2840+
char *tmpbuf, int *plen, DWORD *ptag)
28352841
{
28362842
HANDLE handle;
2837-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
2843+
WCHAR *wbuf;
28382844
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
28392845
DWORD dummy;
2840-
char tmpbuf[MAX_LONG_PATH];
2841-
int len;
2842-
2843-
if (xutftowcs_long_path(wpath, path) < 0)
2844-
return -1;
28452846

28462847
/* read reparse point data */
28472848
handle = CreateFileW(wpath, 0,
@@ -2861,7 +2862,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
28612862
CloseHandle(handle);
28622863

28632864
/* get target path for symlinks or mount points (aka 'junctions') */
2864-
switch (b->ReparseTag) {
2865+
switch ((*ptag = b->ReparseTag)) {
28652866
case IO_REPARSE_TAG_SYMLINK:
28662867
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
28672868
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -2875,19 +2876,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
28752876
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
28762877
break;
28772878
default:
2878-
errno = EINVAL;
2879-
return -1;
2879+
if (fail_on_unknown_tag) {
2880+
errno = EINVAL;
2881+
return -1;
2882+
} else {
2883+
*plen = MAX_LONG_PATH;
2884+
return 0;
2885+
}
28802886
}
28812887

2888+
if ((*plen =
2889+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2890+
return -1;
2891+
return 0;
2892+
}
2893+
2894+
int readlink(const char *path, char *buf, size_t bufsiz)
2895+
{
2896+
WCHAR wpath[MAX_LONG_PATH];
2897+
char tmpbuf[MAX_LONG_PATH];
2898+
int len;
2899+
DWORD tag;
2900+
2901+
if (xutftowcs_long_path(wpath, path) < 0)
2902+
return -1;
2903+
2904+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
2905+
return -1;
2906+
28822907
/*
28832908
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
28842909
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
28852910
* condition. There is no conversion function that produces invalid UTF-8,
28862911
* so convert to a (hopefully large enough) temporary buffer, then memcpy
28872912
* the requested number of bytes (including '\0' for robustness).
28882913
*/
2889-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2890-
return -1;
28912914
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
28922915
return min(bufsiz, len);
28932916
}

0 commit comments

Comments
 (0)