Skip to content

Commit

Permalink
make win32_lstat() return the length of the link in st_size
Browse files Browse the repository at this point in the history
This is reflected in the result of lstat() in perl.

This matches POSIX behaviour.

Fixed Perl#20476
  • Loading branch information
tonycoz authored and khwilliamson committed Jul 10, 2023
1 parent b2b88c4 commit bafc4e0
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 31 deletions.
7 changes: 7 additions & 0 deletions t/win32/stat.t
Expand Up @@ -105,6 +105,9 @@ if (system("mklink $tmpfile1 win32\\stat.t") == 0) {
# our hard link no longer exists, check that is reflected in nlink
is($st[3], $nlink-1, "check nlink updated");

is((lstat($tmpfile1))[7], length(readlink($tmpfile1)),
"check size matches length of link");

unlink($tmpfile1);
}

Expand All @@ -129,6 +132,10 @@ if (system("mklink /d $tmpfile1 win32") == 0) {
if (system("mklink /j $tmpfile1 win32") == 0) {
ok(-l $tmpfile1, "lstat sees a symlink on the directory junction");

my @st = lstat($tmpfile1);
is($st[7], length(readlink($tmpfile1)),
"check returned length matches POSIX");

rmdir( $tmpfile1 );
}

Expand Down
93 changes: 62 additions & 31 deletions win32/win32.c
Expand Up @@ -1879,7 +1879,6 @@ translate_to_errno(void)
}
}


static BOOL
is_symlink(HANDLE h) {
MY_REPARSE_DATA_BUFFER linkdata;
Expand Down Expand Up @@ -1916,41 +1915,21 @@ is_symlink_name(const char *name) {
return result;
}

DllExport int
win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
static int
do_readlink_handle(HANDLE hlink, char *buf, size_t bufsiz, bool *is_symlink) {
MY_REPARSE_DATA_BUFFER linkdata;
HANDLE hlink;
DWORD fileattr = GetFileAttributes(pathname);
DWORD linkdata_returned;
int bytes_out;
BOOL used_default;

if (fileattr == INVALID_FILE_ATTRIBUTES) {
translate_to_errno();
return -1;
}

if (!(fileattr & FILE_ATTRIBUTE_REPARSE_POINT)) {
/* not a symbolic link */
errno = EINVAL;
return -1;
}

hlink =
CreateFileA(pathname, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hlink == INVALID_HANDLE_VALUE) {
translate_to_errno();
return -1;
}
if (is_symlink)
*is_symlink = FALSE;

if (!DeviceIoControl(hlink, FSCTL_GET_REPARSE_POINT, NULL, 0, &linkdata, sizeof(linkdata), &linkdata_returned, NULL)) {
translate_to_errno();
CloseHandle(hlink);
return -1;
}
CloseHandle(hlink);

int bytes_out;
BOOL used_default;
switch (linkdata.ReparseTag) {
case IO_REPARSE_TAG_SYMLINK:
{
Expand All @@ -1965,6 +1944,8 @@ win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
sd->PathBuffer + sd->PrintNameOffset/2,
sd->PrintNameLength/2,
buf, (int)bufsiz, NULL, &used_default);
if (is_symlink)
*is_symlink = TRUE;
}
break;
case IO_REPARSE_TAG_MOUNT_POINT:
Expand All @@ -1980,6 +1961,8 @@ win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
rd->PathBuffer + rd->PrintNameOffset/2,
rd->PrintNameLength/2,
buf, (int)bufsiz, NULL, &used_default);
if (is_symlink)
*is_symlink = TRUE;
}
break;

Expand All @@ -1993,6 +1976,47 @@ win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
errno = EINVAL;
return -1;
}

return bytes_out;
}

DllExport int
win32_readlink(const char *pathname, char *buf, size_t bufsiz) {
if (pathname == NULL || buf == NULL) {
errno = EFAULT;
return -1;
}
if (bufsiz <= 0) {
errno = EINVAL;
return -1;
}

DWORD fileattr = GetFileAttributes(pathname);
if (fileattr == INVALID_FILE_ATTRIBUTES) {
translate_to_errno();
return -1;
}

if (!(fileattr & FILE_ATTRIBUTE_REPARSE_POINT)) {
/* not a symbolic link */
errno = EINVAL;
return -1;
}

HANDLE hlink =
CreateFileA(pathname, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hlink == INVALID_HANDLE_VALUE) {
translate_to_errno();
return -1;
}
int bytes_out = do_readlink_handle(hlink, buf, bufsiz, NULL);
CloseHandle(hlink);
if (bytes_out < 0) {
/* errno already set */
return -1;
}

if ((size_t)bytes_out > bufsiz) {
errno = EINVAL;
return -1;
Expand Down Expand Up @@ -2023,18 +2047,25 @@ win32_lstat(const char *path, Stat_t *sbuf)
translate_to_errno();
return -1;
}

if (!is_symlink(f)) {
bool is_symlink;
int size = do_readlink_handle(f, NULL, 0, &is_symlink);
if (!is_symlink) {
/* it isn't a symlink, fallback to normal stat */
CloseHandle(f);
return win32_stat(path, sbuf);
}

else if (size < 0) {
/* some other error, errno already set */
CloseHandle(f);
return -1;
}
result = win32_stat_low(f, NULL, 0, sbuf, 0);
CloseHandle(f);

if (result != -1){
sbuf->st_mode = (sbuf->st_mode & ~_S_IFMT) | _S_IFLNK;
sbuf->st_size = size;
}
CloseHandle(f);

return result;
}
Expand Down

0 comments on commit bafc4e0

Please sign in to comment.