Skip to content

Commit 7e7fb49

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

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
@@ -988,10 +988,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
988988
return 1;
989989
}
990990

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

10081012
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
1009-
/* for reparse points, use FindFirstFile to get the reparse tag */
1013+
/* for reparse points, get the link tag and length */
10101014
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1011-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
1012-
if (handle == INVALID_HANDLE_VALUE)
1013-
goto error;
1014-
FindClose(handle);
1015+
char tmpbuf[MAX_LONG_PATH];
1016+
1017+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
1018+
&reparse_tag) < 0)
1019+
return -1;
10151020
}
10161021
buf->st_ino = 0;
10171022
buf->st_gid = 0;
10181023
buf->st_uid = 0;
10191024
buf->st_nlink = 1;
10201025
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
1021-
findbuf.dwReserved0);
1022-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
1026+
reparse_tag);
1027+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
10231028
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
10241029
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
10251030
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
10261031
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
10271032
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
10281033
return 0;
10291034
}
1030-
error:
1035+
10311036
switch (GetLastError()) {
10321037
case ERROR_ACCESS_DENIED:
10331038
case ERROR_SHARING_VIOLATION:
@@ -2998,17 +3003,13 @@ typedef struct _REPARSE_DATA_BUFFER {
29983003
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
29993004
#endif
30003005

3001-
int readlink(const char *path, char *buf, size_t bufsiz)
3006+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
3007+
char *tmpbuf, int *plen, DWORD *ptag)
30023008
{
30033009
HANDLE handle;
3004-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
3010+
WCHAR *wbuf;
30053011
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
30063012
DWORD dummy;
3007-
char tmpbuf[MAX_LONG_PATH];
3008-
int len;
3009-
3010-
if (xutftowcs_long_path(wpath, path) < 0)
3011-
return -1;
30123013

30133014
/* read reparse point data */
30143015
handle = CreateFileW(wpath, 0,
@@ -3028,7 +3029,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
30283029
CloseHandle(handle);
30293030

30303031
/* get target path for symlinks or mount points (aka 'junctions') */
3031-
switch (b->ReparseTag) {
3032+
switch ((*ptag = b->ReparseTag)) {
30323033
case IO_REPARSE_TAG_SYMLINK:
30333034
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
30343035
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -3042,19 +3043,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
30423043
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
30433044
break;
30443045
default:
3045-
errno = EINVAL;
3046-
return -1;
3046+
if (fail_on_unknown_tag) {
3047+
errno = EINVAL;
3048+
return -1;
3049+
} else {
3050+
*plen = MAX_LONG_PATH;
3051+
return 0;
3052+
}
30473053
}
30483054

3055+
if ((*plen =
3056+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
3057+
return -1;
3058+
return 0;
3059+
}
3060+
3061+
int readlink(const char *path, char *buf, size_t bufsiz)
3062+
{
3063+
WCHAR wpath[MAX_LONG_PATH];
3064+
char tmpbuf[MAX_LONG_PATH];
3065+
int len;
3066+
DWORD tag;
3067+
3068+
if (xutftowcs_long_path(wpath, path) < 0)
3069+
return -1;
3070+
3071+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
3072+
return -1;
3073+
30493074
/*
30503075
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
30513076
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
30523077
* condition. There is no conversion function that produces invalid UTF-8,
30533078
* so convert to a (hopefully large enough) temporary buffer, then memcpy
30543079
* the requested number of bytes (including '\0' for robustness).
30553080
*/
3056-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
3057-
return -1;
30583081
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
30593082
return min(bufsiz, len);
30603083
}

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)