Added Symbolic Link Support (#285)

Created readlink() implementation.
Separated, corrected lstat() implementation.
Added symlink file type indicator to returned request data.
This commit is contained in:
Bryan Berns 2018-03-15 20:13:13 -04:00 committed by Manoj Ampalam
parent 0dc5a971bd
commit d43856a300
4 changed files with 232 additions and 51 deletions

View File

@ -1,6 +1,9 @@
/*
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
*
* Author: Bryan Berns <berns@uwalumni.com>
* 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;
}

View File

@ -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

View File

@ -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 */

View File

@ -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);