Skip to content

Commit 3b2f9e7

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 <billziss@navimatics.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 9029be3 commit 3b2f9e7

File tree

2 files changed

+56
-21
lines changed

2 files changed

+56
-21
lines changed

compat/mingw.c

+44-21
Original file line numberDiff line numberDiff line change
@@ -973,10 +973,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
973973
return 1;
974974
}
975975

976+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
977+
char *tmpbuf, int *plen, DWORD *ptag);
978+
976979
int mingw_lstat(const char *file_name, struct stat *buf)
977980
{
978981
WIN32_FILE_ATTRIBUTE_DATA fdata;
979-
WIN32_FIND_DATAW findbuf = { 0 };
982+
DWORD reparse_tag = 0;
983+
int link_len = 0;
980984
wchar_t wfilename[MAX_LONG_PATH];
981985
int wlen = xutftowcs_long_path(wfilename, file_name);
982986
if (wlen < 0)
@@ -991,28 +995,29 @@ int mingw_lstat(const char *file_name, struct stat *buf)
991995
}
992996

993997
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
994-
/* for reparse points, use FindFirstFile to get the reparse tag */
998+
/* for reparse points, get the link tag and length */
995999
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
996-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
997-
if (handle == INVALID_HANDLE_VALUE)
998-
goto error;
999-
FindClose(handle);
1000+
char tmpbuf[MAX_LONG_PATH];
1001+
1002+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
1003+
&reparse_tag) < 0)
1004+
return -1;
10001005
}
10011006
buf->st_ino = 0;
10021007
buf->st_gid = 0;
10031008
buf->st_uid = 0;
10041009
buf->st_nlink = 1;
10051010
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
1006-
findbuf.dwReserved0);
1007-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
1011+
reparse_tag);
1012+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
10081013
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
10091014
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
10101015
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
10111016
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
10121017
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
10131018
return 0;
10141019
}
1015-
error:
1020+
10161021
switch (GetLastError()) {
10171022
case ERROR_ACCESS_DENIED:
10181023
case ERROR_SHARING_VIOLATION:
@@ -3025,17 +3030,13 @@ typedef struct _REPARSE_DATA_BUFFER {
30253030
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
30263031
#endif
30273032

3028-
int readlink(const char *path, char *buf, size_t bufsiz)
3033+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
3034+
char *tmpbuf, int *plen, DWORD *ptag)
30293035
{
30303036
HANDLE handle;
3031-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
3037+
WCHAR *wbuf;
30323038
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
30333039
DWORD dummy;
3034-
char tmpbuf[MAX_LONG_PATH];
3035-
int len;
3036-
3037-
if (xutftowcs_long_path(wpath, path) < 0)
3038-
return -1;
30393040

30403041
/* read reparse point data */
30413042
handle = CreateFileW(wpath, 0,
@@ -3055,7 +3056,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
30553056
CloseHandle(handle);
30563057

30573058
/* get target path for symlinks or mount points (aka 'junctions') */
3058-
switch (b->ReparseTag) {
3059+
switch ((*ptag = b->ReparseTag)) {
30593060
case IO_REPARSE_TAG_SYMLINK:
30603061
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
30613062
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -3069,19 +3070,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
30693070
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
30703071
break;
30713072
default:
3072-
errno = EINVAL;
3073-
return -1;
3073+
if (fail_on_unknown_tag) {
3074+
errno = EINVAL;
3075+
return -1;
3076+
} else {
3077+
*plen = MAX_LONG_PATH;
3078+
return 0;
3079+
}
30743080
}
30753081

3082+
if ((*plen =
3083+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
3084+
return -1;
3085+
return 0;
3086+
}
3087+
3088+
int readlink(const char *path, char *buf, size_t bufsiz)
3089+
{
3090+
WCHAR wpath[MAX_LONG_PATH];
3091+
char tmpbuf[MAX_LONG_PATH];
3092+
int len;
3093+
DWORD tag;
3094+
3095+
if (xutftowcs_long_path(wpath, path) < 0)
3096+
return -1;
3097+
3098+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
3099+
return -1;
3100+
30763101
/*
30773102
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
30783103
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
30793104
* condition. There is no conversion function that produces invalid UTF-8,
30803105
* so convert to a (hopefully large enough) temporary buffer, then memcpy
30813106
* the requested number of bytes (including '\0' for robustness).
30823107
*/
3083-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
3084-
return -1;
30853108
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
30863109
return min(bufsiz, len);
30873110
}

compat/win32/fscache.c

+12
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,18 @@ int fscache_lstat(const char *filename, struct stat *st)
584584
return -1;
585585
}
586586

587+
/*
588+
* Special case symbolic links: FindFirstFile()/FindNextFile() did not
589+
* provide us with the length of the target path.
590+
*/
591+
if (fse->u.s.st_size == MAX_LONG_PATH && S_ISLNK(fse->st_mode)) {
592+
char buf[MAX_LONG_PATH];
593+
int len = readlink(filename, buf, sizeof(buf) - 1);
594+
595+
if (len > 0)
596+
fse->u.s.st_size = len;
597+
}
598+
587599
/* copy stat data */
588600
st->st_ino = 0;
589601
st->st_gid = 0;

0 commit comments

Comments
 (0)