diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c index 9ebbf170c..f2c271b1d 100644 --- a/contrib/win32/win32compat/fileio.c +++ b/contrib/win32/win32compat/fileio.c @@ -1,6 +1,9 @@ /* * Author: Manoj Ampalam * +* Author: Bryan Berns +* Added symlink support +* * Copyright (c) 2015 Microsoft Corp. * All rights reserved * @@ -749,12 +752,127 @@ fileio_fstat(struct w32_io* pio, struct _stat64 *buf) return _fstat64(fd, buf); } -int -fileio_stat(const char *path, struct _stat64 *buf) +wchar_t * +fileio_readlink_internal(wchar_t * wpath) { - wchar_t* wpath = NULL; + /* note: there are two approaches for resolving a symlink in Windows: + * + * 1) Use CreateFile() to obtain a file handle to the reparse point and + * send using the DeviceIoControl() call to retrieve the link data from the + * reparse point. + * 2) Use CreateFile() to obtain a file handle to the target file followed + * by a call to GetFinalPathNameByHandle() to get the real path on the + * file system. + * + * This approach uses the first method because the second method does not + * work on broken link since the target file cannot be opened. It also + * requires additional I/O to read both the symlink and its target. + */ + + + /* abbreviated REPARSE_DATA_BUFFER data structure for decoding symlinks; + * the full definition can be found in ntifs.h within the Windows DDK. + * we include it here so the DDK does not become prereq to the build. + * for more info: https://msdn.microsoft.com/en-us/library/cc232006.aspx + */ + typedef struct _REPARSE_DATA_BUFFER_SYMLINK { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } REPARSE_DATA_BUFFER_SYMLINK, *PREPARSE_DATA_BUFFER_SYMLINK; + + /* early declarations for cleanup */ + wchar_t *linkpath = NULL; + HANDLE handle = INVALID_HANDLE_VALUE; + PREPARSE_DATA_BUFFER_SYMLINK reparse_buffer = NULL; + + /* obtain a handle to send to deviceioctl */ + handle = CreateFileW(wpath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0); + if (handle == INVALID_HANDLE_VALUE) { + errno = errno_from_Win32LastError(); + goto cleanup; + } + + /* send a request to the file system to get the real path */ + reparse_buffer = (PREPARSE_DATA_BUFFER_SYMLINK) malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + DWORD dwBytesReturned = 0; + if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, + (LPVOID)reparse_buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwBytesReturned, 0) == 0) { + errno = errno_from_Win32LastError(); + goto cleanup; + } + + /* ensure file is actually symlink */ + if (reparse_buffer->ReparseTag != IO_REPARSE_TAG_SYMLINK) { + errno = ENOLINK; + goto cleanup; + } + + /* the symlink structure has a 'Print Name' value that is displayed to the + * user which is different from the actual value it uses for redirection + * called the 'Substitute Name'; since the Substitute Name has an odd format + * that begins with \??\ and it appears that CreateSymbolicLink() always + * formats the PrintName value consistently we will just use that + */ + int symlink_nonnull_size = reparse_buffer->PrintNameLength; + wchar_t * symlink_nonnull = &reparse_buffer->PathBuffer[reparse_buffer->PrintNameOffset / sizeof(WCHAR)]; + + /* since readlink allows a return string larger than MAX_PATH by specifying + * the bufsiz parameter and windows can have paths larger than MAX_PATH, + * dynamically allocate a string to hold the resultant symbolic link path. + * this string could be as large as parent path plus the reparse buffer + * data plus a null terminator. + */ + const int wpath_len = wcslen(wpath); + int linkpath_len = wpath_len + symlink_nonnull_size / sizeof(wchar_t) + 1; + linkpath = calloc(linkpath_len, sizeof(wchar_t)); + if (linkpath == NULL) { + errno = ENOMEM; + goto cleanup; + } + + /* if symlink is relative, copy the truncated parent as the base path*/ + if (reparse_buffer->Flags != 0) { + + /* copy the parent path, convert forward slashes to backslashes, and + * trim off the last entry in the path */ + wcscpy_s(linkpath, linkpath_len, wpath); + convertToBackslashW(linkpath); + for (int i = wpath_len; i >= 0; i--) { + if (linkpath[i] == L'\\') { + linkpath[i+1] = L'\0'; + break; + } + } + } + + /* append the symbolic link data to the output string*/ + wcsncat(linkpath, symlink_nonnull, symlink_nonnull_size / sizeof(wchar_t)); + +cleanup: + + if (reparse_buffer) + free(reparse_buffer); + if (handle != INVALID_HANDLE_VALUE) + CloseHandle(handle); + return linkpath; +} + +int +fileio_stat_or_lstat_internal(const char *path, struct _stat64 *buf, int do_lstat) +{ + wchar_t *wpath = NULL; + wchar_t *resolved_link = NULL; WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 }; - int ret = -1, len = 0; + int ret = -1; + int is_link = 0; memset(buf, 0, sizeof(struct _stat64)); @@ -771,13 +889,24 @@ fileio_stat(const char *path, struct _stat64 *buf) return -1; } + /* get the file attributes (or symlink attributes if symlink) */ if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &attributes) == FALSE) { errno = errno_from_Win32LastError(); - debug3("GetFileAttributesExW with last error %d", GetLastError()); goto cleanup; } - - len = (int)wcslen(wpath); + + /* try to see if it is a symlink */ + is_link = (attributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && + (resolved_link = fileio_readlink_internal(wpath)) != NULL); + + /* if doing a stat() on a link, then lookup attributes on the target of the link */ + if (!do_lstat && is_link) { + is_link = 0; + if (GetFileAttributesExW(resolved_link, GetFileExInfoStandard, &attributes) == FALSE) { + errno = errno_from_Win32LastError(); + goto cleanup; + } + } buf->st_ino = 0; /* Has no meaning in the FAT, HPFS, or NTFS file systems*/ buf->st_gid = 0; /* UNIX - specific; has no meaning on windows */ @@ -785,7 +914,7 @@ fileio_stat(const char *path, struct _stat64 *buf) buf->st_nlink = 1; /* number of hard links. Always 1 on non - NTFS file systems.*/ buf->st_mode |= file_attr_to_st_mode(wpath, attributes.dwFileAttributes); buf->st_size = attributes.nFileSizeLow | (((off_t)attributes.nFileSizeHigh) << 32); - if (len > 1 && __ascii_iswalpha(*wpath) && (*(wpath + 1) == ':')) + if (wcslen(wpath) > 1 && __ascii_iswalpha(*wpath) && (*(wpath + 1) == ':')) buf->st_dev = buf->st_rdev = towupper(*wpath) - L'A'; /* drive num */ else buf->st_dev = buf->st_rdev = _getdrive() - 1; @@ -793,24 +922,34 @@ fileio_stat(const char *path, struct _stat64 *buf) file_time_to_unix_time(&(attributes.ftLastWriteTime), &(buf->st_mtime)); file_time_to_unix_time(&(attributes.ftCreationTime), &(buf->st_ctime)); - if (attributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - WIN32_FIND_DATAW findbuf = { 0 }; - HANDLE handle = FindFirstFileW(wpath, &findbuf); - if (handle != INVALID_HANDLE_VALUE) { - if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && - (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { - buf->st_mode |= S_IFLNK; - } - FindClose(handle); - } - } + /* link type supercedes other file type bits */ + if (is_link) { + buf->st_mode &= ~S_IFMT; + buf->st_mode |= S_IFLNK; + } + ret = 0; + cleanup: + if (resolved_link) + free(resolved_link); if (wpath) - free(wpath); + free(wpath); return ret; } +int +fileio_stat(const char *path, struct _stat64 *buf) +{ + return fileio_stat_or_lstat_internal(path, buf, 0); +} + +int +fileio_lstat(const char *path, struct _stat64 *buf) +{ + return fileio_stat_or_lstat_internal(path, buf, 1); +} + long fileio_lseek(struct w32_io* pio, unsigned __int64 offset, int origin) { @@ -944,4 +1083,62 @@ fileio_is_io_available(struct w32_io* pio, BOOL rd) } else { /* write */ return (pio->write_details.pending == FALSE) ? TRUE : FALSE; } +} + +ssize_t +fileio_readlink(const char *path, char *buf, size_t bufsiz) +{ + debug4("readlink - io:%p", pio); + + wchar_t *wpath = NULL; + wchar_t *resolved_link = NULL; + char* output = NULL; + ssize_t ret = -1; + + /* sanity check */ + if (buf == NULL) { + errno = EFAULT; + goto cleanup; + } + + if ((wpath = utf8_to_utf16(path)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + + /* read the link data from the passed symlink */ + resolved_link = fileio_readlink_internal(wpath); + if (resolved_link == NULL) { + /* errorno will have been set by called function */ + goto cleanup; + } + + if ((output = utf16_to_utf8(resolved_link)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + + /* ensure output buffer is large enough forward slash and the string */ + ssize_t out_size = strlen(output); + if (1 + out_size > bufsiz) { + errno = ENAMETOOLONG; + goto cleanup; + } + + /* copy to output buffer in the forward-slash format: /C:/Path/Target */ + convertToForwardslash(output); + buf[0] = '/'; + memcpy(buf + 1, output, out_size); + ret = out_size + 1; + +cleanup: + + if (wpath) + free(wpath); + if (output) + free(output); + if (resolved_link) + free(resolved_link); + + return (ssize_t) ret; } \ No newline at end of file diff --git a/contrib/win32/win32compat/inc/sys/stat.h b/contrib/win32/win32compat/inc/sys/stat.h index 2375dd93e..e8e960fb5 100644 --- a/contrib/win32/win32compat/inc/sys/stat.h +++ b/contrib/win32/win32compat/inc/sys/stat.h @@ -28,7 +28,9 @@ int w32_fstat(int fd, struct w32_stat *buf); int w32_stat(const char *path, struct w32_stat *buf); #define stat w32_stat -#define lstat w32_stat + +int w32_lstat(const char *path, struct w32_stat *buf); +#define lstat w32_lstat int w32_mkdir(const char *pathname, unsigned short mode); #define mkdir w32_mkdir diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 04867b220..1565b2f9e 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -70,33 +70,6 @@ static char* s_programdir = NULL; #define EPOCH_DELTA_US 116444736000000000ULL #define RATE_DIFF 10000000ULL /* 1000 nsecs */ -typedef struct _REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } SymbolicLinkReparseBuffer; - - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPointReparseBuffer; - - struct { - UCHAR DataBuffer[1]; - } GenericReparseBuffer; - }; -} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; - /* Windows CRT defines error string messages only till 43 in errno.h * This is an extended list that defines messages for EADDRINUSE through EWOULDBLOCK */ @@ -482,6 +455,9 @@ strmode(mode_t mode, char *p) case S_IFREG: /* regular */ *p++ = '-'; break; + case S_IFLNK: /* symbolic link */ + *p++ = 'l'; + break; #ifdef S_IFSOCK case S_IFSOCK: /* socket */ *p++ = 's'; @@ -831,13 +807,17 @@ w32_stat(const char *input_path, struct w32_stat *buf) return fileio_stat(resolved_path(input_path), (struct _stat64*)buf); } +int +w32_lstat(const char *input_path, struct w32_stat *buf) +{ + return fileio_lstat(resolved_path(input_path), (struct _stat64*)buf); +} + /* if file is symbolic link, copy its link into "link" */ int readlink(const char *path, char *link, int linklen) { - if(strcpy_s(link, linklen, resolved_path(path))) - return -1; - return 0; + return fileio_readlink(resolved_path(path), link, linklen); } /* convert forward slash to back slash */ diff --git a/contrib/win32/win32compat/w32fd.h b/contrib/win32/win32compat/w32fd.h index 441304640..d2fdca689 100644 --- a/contrib/win32/win32compat/w32fd.h +++ b/contrib/win32/win32compat/w32fd.h @@ -161,5 +161,7 @@ int fileio_read(struct w32_io* pio, void *dst, size_t max); int fileio_write(struct w32_io* pio, const void *buf, size_t max); int fileio_fstat(struct w32_io* pio, struct _stat64 *buf); int fileio_stat(const char *path, struct _stat64 *buf); +int fileio_lstat(const char *path, struct _stat64 *buf); long fileio_lseek(struct w32_io* pio, unsigned __int64 offset, int origin); FILE* fileio_fdopen(struct w32_io* pio, const char *mode); +ssize_t fileio_readlink(const char *path, char *buf, size_t bufsiz); \ No newline at end of file