Skip to content

Commit 2d445b7

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 d28caf0 commit 2d445b7

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
@@ -815,10 +815,14 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
815815
return 1;
816816
}
817817

818+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
819+
char *tmpbuf, int *plen, DWORD *ptag);
820+
818821
int mingw_lstat(const char *file_name, struct stat *buf)
819822
{
820823
WIN32_FILE_ATTRIBUTE_DATA fdata;
821-
WIN32_FIND_DATAW findbuf = { 0 };
824+
DWORD reparse_tag = 0;
825+
int link_len = 0;
822826
wchar_t wfilename[MAX_LONG_PATH];
823827
int wlen = xutftowcs_long_path(wfilename, file_name);
824828
if (wlen < 0)
@@ -833,28 +837,29 @@ int mingw_lstat(const char *file_name, struct stat *buf)
833837
}
834838

835839
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
836-
/* for reparse points, use FindFirstFile to get the reparse tag */
840+
/* for reparse points, get the link tag and length */
837841
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
838-
HANDLE handle = FindFirstFileW(wfilename, &findbuf);
839-
if (handle == INVALID_HANDLE_VALUE)
840-
goto error;
841-
FindClose(handle);
842+
char tmpbuf[MAX_LONG_PATH];
843+
844+
if (readlink_1(wfilename, FALSE, tmpbuf, &link_len,
845+
&reparse_tag) < 0)
846+
return -1;
842847
}
843848
buf->st_ino = 0;
844849
buf->st_gid = 0;
845850
buf->st_uid = 0;
846851
buf->st_nlink = 1;
847852
buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes,
848-
findbuf.dwReserved0);
849-
buf->st_size = S_ISLNK(buf->st_mode) ? MAX_LONG_PATH :
853+
reparse_tag);
854+
buf->st_size = S_ISLNK(buf->st_mode) ? link_len :
850855
fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32);
851856
buf->st_dev = buf->st_rdev = 0; /* not used by Git */
852857
filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim));
853858
filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim));
854859
filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim));
855860
return 0;
856861
}
857-
error:
862+
858863
switch (GetLastError()) {
859864
case ERROR_ACCESS_DENIED:
860865
case ERROR_SHARING_VIOLATION:
@@ -2675,17 +2680,13 @@ typedef struct _REPARSE_DATA_BUFFER {
26752680
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
26762681
#endif
26772682

2678-
int readlink(const char *path, char *buf, size_t bufsiz)
2683+
static int readlink_1(const WCHAR *wpath, BOOL fail_on_unknown_tag,
2684+
char *tmpbuf, int *plen, DWORD *ptag)
26792685
{
26802686
HANDLE handle;
2681-
WCHAR wpath[MAX_LONG_PATH], *wbuf;
2687+
WCHAR *wbuf;
26822688
REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
26832689
DWORD dummy;
2684-
char tmpbuf[MAX_LONG_PATH];
2685-
int len;
2686-
2687-
if (xutftowcs_long_path(wpath, path) < 0)
2688-
return -1;
26892690

26902691
/* read reparse point data */
26912692
handle = CreateFileW(wpath, 0,
@@ -2705,7 +2706,7 @@ int readlink(const char *path, char *buf, size_t bufsiz)
27052706
CloseHandle(handle);
27062707

27072708
/* get target path for symlinks or mount points (aka 'junctions') */
2708-
switch (b->ReparseTag) {
2709+
switch ((*ptag = b->ReparseTag)) {
27092710
case IO_REPARSE_TAG_SYMLINK:
27102711
wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer)
27112712
+ b->SymbolicLinkReparseBuffer.SubstituteNameOffset);
@@ -2719,19 +2720,41 @@ int readlink(const char *path, char *buf, size_t bufsiz)
27192720
+ b->MountPointReparseBuffer.SubstituteNameLength) = 0;
27202721
break;
27212722
default:
2722-
errno = EINVAL;
2723-
return -1;
2723+
if (fail_on_unknown_tag) {
2724+
errno = EINVAL;
2725+
return -1;
2726+
} else {
2727+
*plen = MAX_LONG_PATH;
2728+
return 0;
2729+
}
27242730
}
27252731

2732+
if ((*plen =
2733+
xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2734+
return -1;
2735+
return 0;
2736+
}
2737+
2738+
int readlink(const char *path, char *buf, size_t bufsiz)
2739+
{
2740+
WCHAR wpath[MAX_LONG_PATH];
2741+
char tmpbuf[MAX_LONG_PATH];
2742+
int len;
2743+
DWORD tag;
2744+
2745+
if (xutftowcs_long_path(wpath, path) < 0)
2746+
return -1;
2747+
2748+
if (readlink_1(wpath, TRUE, tmpbuf, &len, &tag) < 0)
2749+
return -1;
2750+
27262751
/*
27272752
* Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially
27282753
* cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure
27292754
* condition. There is no conversion function that produces invalid UTF-8,
27302755
* so convert to a (hopefully large enough) temporary buffer, then memcpy
27312756
* the requested number of bytes (including '\0' for robustness).
27322757
*/
2733-
if ((len = xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0)
2734-
return -1;
27352758
memcpy(buf, tmpbuf, min(bufsiz, len + 1));
27362759
return min(bufsiz, len);
27372760
}

compat/win32/fscache.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,18 @@ int fscache_lstat(const char *filename, struct stat *st)
574574
if (!fse)
575575
return -1;
576576

577+
/*
578+
* Special case symbolic links: FindFirstFile()/FindNextFile() did not
579+
* provide us with the length of the target path.
580+
*/
581+
if (fse->u.s.st_size == MAX_LONG_PATH && S_ISLNK(fse->st_mode)) {
582+
char buf[MAX_LONG_PATH];
583+
int len = readlink(filename, buf, sizeof(buf) - 1);
584+
585+
if (len > 0)
586+
fse->u.s.st_size = len;
587+
}
588+
577589
/* copy stat data */
578590
st->st_ino = 0;
579591
st->st_gid = 0;

0 commit comments

Comments
 (0)