Skip to content

Commit c14941d

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. Signed-off-by: Bill Zissimopoulos <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent a8a341b commit c14941d

File tree

2 files changed

+56
-21
lines changed

2 files changed

+56
-21
lines changed

compat/mingw.c

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -987,10 +987,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
987987
return 1;
988988
}
989989

990+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
991+
char *tmpbuf, int *plen, DWORD *ptag);
992+
990993
int mingw_lstat(const char *file_name, struct stat *buf)
991994
{
992995
WIN32_FILE_ATTRIBUTE_DATA fdata;
993-
WIN32_FIND_DATAW findbuf = { 0 };
996+
DWORD reparse_tag = 0;
997+
int link_len = 0;
994998
wchar_t wfilename[MAX_LONG_PATH];
995999
int wlen = xutftowcs_long_path(wfilename, file_name);
9961000
if (wlen < 0)
@@ -1005,28 +1009,29 @@ int mingw_lstat(const char *file_name, struct stat *buf)
10051009
}
10061010

10071011
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
1008-
/* for reparse points, use FindFirstFile to get the reparse tag */
1012+
/* for reparse points, get the link tag and length */
10091013
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1010-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
1011-
if (handle == INVALID_HANDLE_VALUE)
1012-
goto error;
1013-
FindClose(handle);
1014+
char tmpbuf[MAX_LONG_PATH];
1015+
1016+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
1017+
&reparse_tag) < 0)
1018+
return -1;
10141019
}
10151020
buf->st_ino = 0;
10161021
buf->st_gid = 0;
10171022
buf->st_uid = 0;
10181023
buf->st_nlink = 1;
10191024
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
1020-
findbuf.dwReserved0);
1021-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
1025+
reparse_tag);
1026+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
10221027
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
10231028
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
10241029
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
10251030
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
10261031
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
10271032
return 0;
10281033
}
1029-
error:
1034+
10301035
switch (GetLastError()) {
10311036
case ERROR_ACCESS_DENIED:
10321037
case ERROR_SHARING_VIOLATION:
@@ -2991,17 +2996,13 @@ typedef struct _REPARSE_DATA_BUFFER {
29912996
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
29922997
#endif
29932998

2994-
int readlink(const char *path, char *buf, size_t bufsiz)
2999+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
3000+
char *tmpbuf, int *plen, DWORD *ptag)
29953001
{
29963002
HANDLE handle;
2997-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
3003+
WCHAR *wbuf;
29983004
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
29993005
DWORD dummy;
3000-
char tmpbuf[MAX_LONG_PATH];
3001-
int len;
3002-
3003-
if (xutftowcs_long_path(wpath, path) < 0)
3004-
return -1;
30053006

30063007
/* read reparse point data */
30073008
handle = CreateFileW(wpath, 0,
@@ -3021,7 +3022,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
30213022
CloseHandle(handle);
30223023

30233024
/* get target path for symlinks or mount points (aka 'junctions') */
3024-
switch (b->ReparseTag) {
3025+
switch ((*ptag = b->ReparseTag)) {
30253026
case IO_REPARSE_TAG_SYMLINK:
30263027
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
30273028
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -3035,19 +3036,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
30353036
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
30363037
break;
30373038
default:
3038-
errno = EINVAL;
3039-
return -1;
3039+
if (fail_on_unknown_tag) {
3040+
errno = EINVAL;
3041+
return -1;
3042+
} else {
3043+
*plen = MAX_LONG_PATH;
3044+
return 0;
3045+
}
30403046
}
30413047

3048+
if ((*plen =
3049+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
3050+
return -1;
3051+
return 0;
3052+
}
3053+
3054+
int readlink(const char *path, char *buf, size_t bufsiz)
3055+
{
3056+
WCHAR wpath[MAX_LONG_PATH];
3057+
char tmpbuf[MAX_LONG_PATH];
3058+
int len;
3059+
DWORD tag;
3060+
3061+
if (xutftowcs_long_path(wpath, path) < 0)
3062+
return -1;
3063+
3064+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
3065+
return -1;
3066+
30423067
/*
30433068
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
30443069
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
30453070
* condition. There is no conversion function that produces invalid UTF-8,
30463071
* so convert to a (hopefully large enough) temporary buffer, then memcpy
30473072
* the requested number of bytes (including '\0' for robustness).
30483073
*/
3049-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
3050-
return -1;
30513074
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
30523075
return min(bufsiz, len);
30533076
}

compat/win32/fscache.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,18 @@ int fscache_lstat(const char *filename, struct stat *st)
594594
return -1;
595595
}
596596

597+
/*
598+
* Special case symbolic links: FindFirstFile()/FindNextFile() did not
599+
* provide us with the length of the target path.
600+
*/
601+
if (fse->u.s.st_size == MAX_LONG_PATH && S_ISLNK(fse->st_mode)) {
602+
char buf[MAX_LONG_PATH];
603+
int len = readlink(filename, buf, sizeof(buf) - 1);
604+
605+
if (len > 0)
606+
fse->u.s.st_size = len;
607+
}
608+
597609
/* copy stat data */
598610
st->st_ino = 0;
599611
st->st_gid = 0;

0 commit comments

Comments
 (0)