diff options
author | Tony Cook <tony@develop-help.com> | 2022-09-06 15:01:54 +1000 |
---|---|---|
committer | Tony Cook <tony@develop-help.com> | 2022-11-02 09:37:43 +1100 |
commit | 01052a1d77a5e152a908d2e93d8a34736b6391dd (patch) | |
tree | a356e09d4f8ecae9cca6740e937bd72d4c7baf32 /win32 | |
parent | 4f8b3850b207c4a8adaaba6caf82902c90cee8b6 (diff) | |
download | perl-01052a1d77a5e152a908d2e93d8a34736b6391dd.tar.gz |
Win32 stat() didn't handle AF_UNIX socket files
Unfortunately both symbolic links and sockets can only be
"statted" by opening with FILE_FLAG_OPEN_REPARSE_POINT which
obviously doesn't follow symbolic links.
So to find if a chain of symbolic links points to a socket,
is a broken chain, or loops, we need to follow the chain
ourselves.
Diffstat (limited to 'win32')
-rw-r--r-- | win32/win32.c | 275 | ||||
-rw-r--r-- | win32/win32.h | 13 |
2 files changed, 236 insertions, 52 deletions
diff --git a/win32/win32.c b/win32/win32.c index 22cbd5723e..ec975eaa66 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -1494,13 +1494,77 @@ translate_ft_to_time_t(FILETIME ft) { typedef DWORD (__stdcall *pGetFinalPathNameByHandleA_t)(HANDLE, LPSTR, DWORD, DWORD); +/* Adapted from: + +https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer + +Renamed to avoid conflicts, apparently some SDKs define this +structure. + +Hoisted the symlink and mount point data into a new type to allow us +to make a pointer to it, and to avoid C++ scoping issues. + +*/ + +typedef struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[MAX_PATH*3]; +} MY_SYMLINK_REPARSE_BUFFER, *PMY_SYMLINK_REPARSE_BUFFER; + +typedef struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[MAX_PATH*3]; +} MY_MOUNT_POINT_REPARSE_BUFFER; + +typedef struct { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + MY_SYMLINK_REPARSE_BUFFER SymbolicLinkReparseBuffer; + MY_MOUNT_POINT_REPARSE_BUFFER MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } Data; +} MY_REPARSE_DATA_BUFFER, *PMY_REPARSE_DATA_BUFFER; + +#ifndef IO_REPARSE_TAG_SYMLINK +# define IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#endif +#ifndef IO_REPARSE_TAG_AF_UNIX +# define IO_REPARSE_TAG_AF_UNIX 0x80000023 +#endif +#ifndef IO_REPARSE_TAG_LX_FIFO +# define IO_REPARSE_TAG_LX_FIFO 0x80000024 +#endif +#ifndef IO_REPARSE_TAG_LX_CHR +# define IO_REPARSE_TAG_LX_CHR 0x80000025 +#endif +#ifndef IO_REPARSE_TAG_LX_BLK +# define IO_REPARSE_TAG_LX_BLK 0x80000026 +#endif + static int -win32_stat_low(HANDLE handle, const char *path, STRLEN len, Stat_t *sbuf) { +win32_stat_low(HANDLE handle, const char *path, STRLEN len, Stat_t *sbuf, + DWORD reparse_type) { DWORD type = GetFileType(handle); BY_HANDLE_FILE_INFORMATION bhi; Zero(sbuf, 1, Stat_t); + if (reparse_type) { + /* Lie to get to the right place */ + type = FILE_TYPE_DISK; + } + type &= ~FILE_TYPE_REMOTE; switch (type) { @@ -1524,7 +1588,35 @@ win32_stat_low(HANDLE handle, const char *path, STRLEN len, Stat_t *sbuf) { sbuf->st_mtime = translate_ft_to_time_t(bhi.ftLastWriteTime); sbuf->st_ctime = translate_ft_to_time_t(bhi.ftCreationTime); - if (bhi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if (reparse_type) { + /* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 + describes all of these as WSL only, but the AF_UNIX tag + is known to be used for AF_UNIX sockets without WSL. + */ + switch (reparse_type) { + case IO_REPARSE_TAG_AF_UNIX: + sbuf->st_mode = _S_IFSOCK; + break; + + case IO_REPARSE_TAG_LX_FIFO: + sbuf->st_mode = _S_IFIFO; + break; + + case IO_REPARSE_TAG_LX_CHR: + sbuf->st_mode = _S_IFCHR; + break; + + case IO_REPARSE_TAG_LX_BLK: + sbuf->st_mode = _S_IFBLK; + break; + + default: + /* Is there anything else we can do here? */ + errno = EINVAL; + return -1; + } + } + else if (bhi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { sbuf->st_mode = _S_IFDIR | _S_IREAD | _S_IEXEC; /* duplicate the logic from the end of the old win32_stat() */ if (!(bhi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { @@ -1591,6 +1683,120 @@ win32_stat_low(HANDLE handle, const char *path, STRLEN len, Stat_t *sbuf) { return 0; } +/* https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points */ +#define SYMLINK_FOLLOW_LIMIT 63 + +/* + +Given a pathname, required to be a symlink, follow it until we find a +non-symlink path. + +This should only be called when the symlink() chain doesn't lead to a +normal file, which should have been caught earlier. + +On success, returns a HANDLE to the target and sets *reparse_type to +the ReparseTag of the target. + +Returns INVALID_HANDLE_VALUE on error, which might be that the symlink +chain is broken, or requires too many links to resolve. + +*/ + +static HANDLE +S_follow_symlinks_to(pTHX_ const char *pathname, DWORD *reparse_type) { + char link_target[MAX_PATH]; + SV *work_path = newSVpvn(pathname, strlen(pathname)); + int link_count = 0; + int link_len; + HANDLE handle; + + *reparse_type = 0; + + while ((link_len = win32_readlink(SvPVX(work_path), link_target, + sizeof(link_target))) > 0) { + if (link_count++ >= SYMLINK_FOLLOW_LIMIT) { + /* Windows doesn't appear to ever return ELOOP, + let's do better ourselves + */ + SvREFCNT_dec(work_path); + errno = ELOOP; + return INVALID_HANDLE_VALUE; + } + /* Adjust the linktarget based on the link source or current + directory as needed. + */ + if (link_target[0] == '\\' + || link_target[0] == '/' + || (link_len >=2 && link_target[1] == ':')) { + /* link is absolute */ + sv_setpvn(work_path, link_target, link_len); + } + else { + STRLEN work_len; + const char *workp = SvPV(work_path, work_len); + const char *final_bslash = my_memrchr(workp, '\\', work_len); + const char *final_slash = my_memrchr(workp, '/', work_len); + const char *path_sep = NULL; + if (final_bslash && final_slash) + path_sep = final_bslash > final_slash ? final_bslash : final_slash; + else if (final_bslash) + path_sep = final_bslash; + else if (final_slash) + path_sep = final_slash; + + if (path_sep) { + SV *new_path = newSVpv(workp, path_sep - workp + 1); + sv_catpvn(new_path, link_target, link_len); + SvREFCNT_dec(work_path); + work_path = new_path; + } + else { + /* should only get here the first time around */ + assert(link_count == 1); + char path_temp[MAX_PATH]; + DWORD path_len = GetCurrentDirectoryA(sizeof(path_temp), path_temp); + if (!path_len || path_len > sizeof(path_temp)) { + SvREFCNT_dec(work_path); + errno = EINVAL; + return INVALID_HANDLE_VALUE; + } + + SV *new_path = newSVpvn(path_temp, path_len); + if (path_temp[path_len-1] != '\\') { + sv_catpvs(new_path, "\\"); + } + sv_catpvn(new_path, link_target, link_len); + SvREFCNT_dec(work_path); + work_path = new_path; + } + } + } + + handle = + CreateFileA(SvPVX(work_path), GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS, 0); + SvREFCNT_dec(work_path); + if (handle != INVALID_HANDLE_VALUE) { + MY_REPARSE_DATA_BUFFER linkdata; + DWORD linkdata_returned; + + if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, + &linkdata, sizeof(linkdata), + &linkdata_returned, NULL)) { + translate_to_errno(); + CloseHandle(handle); + return INVALID_HANDLE_VALUE; + } + *reparse_type = linkdata.ReparseTag; + return handle; + } + else { + translate_to_errno(); + } + + return handle; +} + DllExport int win32_stat(const char *path, Stat_t *sbuf) { @@ -1598,6 +1804,7 @@ win32_stat(const char *path, Stat_t *sbuf) BOOL expect_dir = FALSE; int result; HANDLE handle; + DWORD reparse_type = 0; path = PerlDir_mapA(path); @@ -1605,8 +1812,21 @@ win32_stat(const char *path, Stat_t *sbuf) CreateFileA(path, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle == INVALID_HANDLE_VALUE) { + /* AF_UNIX sockets need to be opened as a reparse point, but + that will also open symlinks rather than following them. + + There may be other reparse points that need similar + treatment. + */ + handle = S_follow_symlinks_to(aTHX_ path, &reparse_type); + if (handle == INVALID_HANDLE_VALUE) { + /* S_follow_symlinks_to() will set errno */ + return -1; + } + } if (handle != INVALID_HANDLE_VALUE) { - result = win32_stat_low(handle, path, strlen(path), sbuf); + result = win32_stat_low(handle, path, strlen(path), sbuf, reparse_type); CloseHandle(handle); } else { @@ -1659,51 +1879,6 @@ translate_to_errno(void) } } -/* Adapted from: - -https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer - -Renamed to avoid conflicts, apparently some SDKs define this -structure. - -Hoisted the symlink and mount point data into a new type to allow us -to make a pointer to it, and to avoid C++ scoping issues. - -*/ - -typedef struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[MAX_PATH*3]; -} MY_SYMLINK_REPARSE_BUFFER, *PMY_SYMLINK_REPARSE_BUFFER; - -typedef struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[MAX_PATH*3]; -} MY_MOUNT_POINT_REPARSE_BUFFER; - -typedef struct { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - MY_SYMLINK_REPARSE_BUFFER SymbolicLinkReparseBuffer; - MY_MOUNT_POINT_REPARSE_BUFFER MountPointReparseBuffer; - struct { - UCHAR DataBuffer[1]; - } GenericReparseBuffer; - } Data; -} MY_REPARSE_DATA_BUFFER, *PMY_REPARSE_DATA_BUFFER; - -#ifndef IO_REPARSE_TAG_SYMLINK -# define IO_REPARSE_TAG_SYMLINK (0xA000000CL) -#endif static BOOL is_symlink(HANDLE h) { @@ -1854,7 +2029,7 @@ win32_lstat(const char *path, Stat_t *sbuf) return win32_stat(path, sbuf); } - result = win32_stat_low(f, NULL, 0, sbuf); + result = win32_stat_low(f, NULL, 0, sbuf, 0); CloseHandle(f); if (result != -1){ @@ -3263,7 +3438,7 @@ win32_fstat(int fd, Stat_t *sbufptr) { HANDLE handle = (HANDLE)win32_get_osfhandle(fd); - return win32_stat_low(handle, NULL, 0, sbufptr); + return win32_stat_low(handle, NULL, 0, sbufptr, 0); } DllExport int diff --git a/win32/win32.h b/win32/win32.h index be88b540af..499203a5a0 100644 --- a/win32/win32.h +++ b/win32/win32.h @@ -694,11 +694,20 @@ DllExport void *win32_signal_context(void); /* ucrt at least seems to allocate a whole bit per type, just mask off one bit from the mask for our symlink - file type. + and socket file types. */ -#define _S_IFLNK ((unsigned)(_S_IFMT ^ (_S_IFMT & -_S_IFMT))) +#define _S_IFLNK ((unsigned)(_S_IFDIR | _S_IFCHR)) +#define _S_IFSOCK ((unsigned)(_S_IFDIR | _S_IFIFO)) +/* mingw64 defines _S_IFBLK to 0x3000 which is _S_IFDIR | _S_IFIFO */ +#ifndef _S_IFBLK +# define _S_IFBLK ((unsigned)(_S_IFCHR | _S_IFIFO)) +#endif #undef S_ISLNK #define S_ISLNK(mode) (((mode) & _S_IFMT) == _S_IFLNK) +#undef S_ISSOCK +#define S_ISSOCK(mode) (((mode) & _S_IFMT) == _S_IFSOCK) +#undef S_ISBLK +#define S_ISBLK(mode) (((mode) & _S_IFMT) == _S_IFBLK) /* |