Skip to content

Commit ec1e4a3

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 c3aa706 commit ec1e4a3

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
@@ -865,10 +865,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
865865
return 1;
866866
}
867867

868+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
869+
char *tmpbuf, int *plen, DWORD *ptag);
870+
868871
int mingw_lstat(const char *file_name, struct stat *buf)
869872
{
870873
WIN32_FILE_ATTRIBUTE_DATA fdata;
871-
WIN32_FIND_DATAW findbuf = { 0 };
874+
DWORD reparse_tag = 0;
875+
int link_len = 0;
872876
wchar_t wfilename[MAX_LONG_PATH];
873877
int wlen = xutftowcs_long_path(wfilename, file_name);
874878
if (wlen < 0)
@@ -883,28 +887,29 @@ int mingw_lstat(const char *file_name, struct stat *buf)
883887
}
884888

885889
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
886-
/* for reparse points, use FindFirstFile to get the reparse tag */
890+
/* for reparse points, get the link tag and length */
887891
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
888-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
889-
if (handle == INVALID_HANDLE_VALUE)
890-
goto error;
891-
FindClose(handle);
892+
char tmpbuf[MAX_LONG_PATH];
893+
894+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
895+
&reparse_tag) < 0)
896+
return -1;
892897
}
893898
buf->st_ino = 0;
894899
buf->st_gid = 0;
895900
buf->st_uid = 0;
896901
buf->st_nlink = 1;
897902
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
898-
findbuf.dwReserved0);
899-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
903+
reparse_tag);
904+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
900905
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
901906
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
902907
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
903908
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
904909
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
905910
return 0;
906911
}
907-
error:
912+
908913
switch (GetLastError()) {
909914
case ERROR_ACCESS_DENIED:
910915
case ERROR_SHARING_VIOLATION:
@@ -2743,17 +2748,13 @@ typedef struct _REPARSE_DATA_BUFFER {
27432748
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
27442749
#endif
27452750

2746-
int readlink(const char *path, char *buf, size_t bufsiz)
2751+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
2752+
char *tmpbuf, int *plen, DWORD *ptag)
27472753
{
27482754
HANDLE handle;
2749-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
2755+
WCHAR *wbuf;
27502756
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
27512757
DWORD dummy;
2752-
char tmpbuf[MAX_LONG_PATH];
2753-
int len;
2754-
2755-
if (xutftowcs_long_path(wpath, path) < 0)
2756-
return -1;
27572758

27582759
/* read reparse point data */
27592760
handle = CreateFileW(wpath, 0,
@@ -2773,7 +2774,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
27732774
CloseHandle(handle);
27742775

27752776
/* get target path for symlinks or mount points (aka 'junctions') */
2776-
switch (b->ReparseTag) {
2777+
switch ((*ptag = b->ReparseTag)) {
27772778
case IO_REPARSE_TAG_SYMLINK:
27782779
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
27792780
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -2787,19 +2788,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
27872788
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
27882789
break;
27892790
default:
2790-
errno = EINVAL;
2791-
return -1;
2791+
if (fail_on_unknown_tag) {
2792+
errno = EINVAL;
2793+
return -1;
2794+
} else {
2795+
*plen = MAX_LONG_PATH;
2796+
return 0;
2797+
}
27922798
}
27932799

2800+
if ((*plen =
2801+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2802+
return -1;
2803+
return 0;
2804+
}
2805+
2806+
int readlink(const char *path, char *buf, size_t bufsiz)
2807+
{
2808+
WCHAR wpath[MAX_LONG_PATH];
2809+
char tmpbuf[MAX_LONG_PATH];
2810+
int len;
2811+
DWORD tag;
2812+
2813+
if (xutftowcs_long_path(wpath, path) < 0)
2814+
return -1;
2815+
2816+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
2817+
return -1;
2818+
27942819
/*
27952820
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
27962821
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
27972822
* condition. There is no conversion function that produces invalid UTF-8,
27982823
* so convert to a (hopefully large enough) temporary buffer, then memcpy
27992824
* the requested number of bytes (including '\0' for robustness).
28002825
*/
2801-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2802-
return -1;
28032826
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
28042827
return min(bufsiz, len);
28052828
}

compat/win32/fscache.c

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

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

0 commit comments

Comments
 (0)