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:
parent
0dc5a971bd
commit
d43856a300
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
Loading…
Reference in New Issue