|
|
|
@ -87,6 +87,7 @@ int
|
|
|
|
|
errno_from_Win32Error(int win32_error)
|
|
|
|
|
{
|
|
|
|
|
switch (win32_error) {
|
|
|
|
|
case ERROR_PRIVILEGE_NOT_HELD:
|
|
|
|
|
case ERROR_ACCESS_DENIED:
|
|
|
|
|
return EACCES;
|
|
|
|
|
case ERROR_OUTOFMEMORY:
|
|
|
|
@ -752,124 +753,12 @@ fileio_fstat(struct w32_io* pio, struct _stat64 *buf)
|
|
|
|
|
return _fstat64(fd, buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wchar_t *
|
|
|
|
|
fileio_readlink_internal(wchar_t * wpath)
|
|
|
|
|
{
|
|
|
|
|
/* 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 = (int)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_s(linkpath, linkpath_len, 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;
|
|
|
|
|
char link_test = L'\0';
|
|
|
|
|
HANDLE link_handle = INVALID_HANDLE_VALUE;
|
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 };
|
|
|
|
|
int ret = -1;
|
|
|
|
|
int is_link = 0;
|
|
|
|
@ -897,15 +786,29 @@ fileio_stat_or_lstat_internal(const char *path, struct _stat64 *buf, int do_lsta
|
|
|
|
|
|
|
|
|
|
/* try to see if it is a symlink */
|
|
|
|
|
is_link = (attributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
|
|
|
|
|
(resolved_link = fileio_readlink_internal(wpath)) != NULL);
|
|
|
|
|
fileio_readlink(path, &link_test, 1) == 1);
|
|
|
|
|
|
|
|
|
|
/* 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) {
|
|
|
|
|
|
|
|
|
|
/* obtain a file handle to the destination file (not the source link) */
|
|
|
|
|
BY_HANDLE_FILE_INFORMATION link_attributes;
|
|
|
|
|
if ((link_handle = CreateFileW(wpath, 0, 0, NULL, OPEN_EXISTING,
|
|
|
|
|
FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE
|
|
|
|
|
|| GetFileInformationByHandle(link_handle, &link_attributes) == 0)
|
|
|
|
|
{
|
|
|
|
|
errno = errno_from_Win32LastError();
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy attributes from handle structure to normal structure */
|
|
|
|
|
attributes.ftCreationTime = link_attributes.ftCreationTime;
|
|
|
|
|
attributes.ftLastAccessTime = link_attributes.ftLastAccessTime;
|
|
|
|
|
attributes.ftLastWriteTime = link_attributes.ftLastWriteTime;
|
|
|
|
|
attributes.nFileSizeHigh = link_attributes.nFileSizeHigh;
|
|
|
|
|
attributes.nFileSizeLow = link_attributes.nFileSizeLow;
|
|
|
|
|
attributes.dwFileAttributes = link_attributes.dwFileAttributes;
|
|
|
|
|
is_link = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
buf->st_ino = 0; /* Has no meaning in the FAT, HPFS, or NTFS file systems*/
|
|
|
|
@ -931,8 +834,8 @@ fileio_stat_or_lstat_internal(const char *path, struct _stat64 *buf, int do_lsta
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
if (resolved_link)
|
|
|
|
|
free(resolved_link);
|
|
|
|
|
if (link_handle != INVALID_HANDLE_VALUE)
|
|
|
|
|
CloseHandle(link_handle);
|
|
|
|
|
if (wpath)
|
|
|
|
|
free(wpath);
|
|
|
|
|
return ret;
|
|
|
|
@ -1085,19 +988,54 @@ fileio_is_io_available(struct w32_io* pio, BOOL rd)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ssize_t
|
|
|
|
|
ssize_t
|
|
|
|
|
fileio_readlink(const char *path, char *buf, size_t bufsiz)
|
|
|
|
|
{
|
|
|
|
|
/* 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
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
debug4("readlink - io:%p", pio);
|
|
|
|
|
|
|
|
|
|
wchar_t *wpath = NULL;
|
|
|
|
|
wchar_t *resolved_link = NULL;
|
|
|
|
|
char* output = NULL;
|
|
|
|
|
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 */
|
|
|
|
|
ssize_t ret = -1;
|
|
|
|
|
wchar_t *wpath = NULL;
|
|
|
|
|
wchar_t *linkpath = NULL;
|
|
|
|
|
char *output = NULL;
|
|
|
|
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
|
|
|
|
PREPARSE_DATA_BUFFER_SYMLINK reparse_buffer = NULL;
|
|
|
|
|
|
|
|
|
|
/* sanity check */
|
|
|
|
|
if (buf == NULL) {
|
|
|
|
|
errno = EFAULT;
|
|
|
|
|
if (path == NULL || buf == NULL || bufsiz == 0) {
|
|
|
|
|
errno = EINVAL;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1106,39 +1044,161 @@ fileio_readlink(const char *path, char *buf, size_t bufsiz)
|
|
|
|
|
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 */
|
|
|
|
|
/* obtain a handle to send to deviceioctl */
|
|
|
|
|
handle = CreateFileW(wpath, 0, 0, NULL, OPEN_EXISTING,
|
|
|
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
|
|
|
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
|
|
|
errno = errno_from_Win32LastError();
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((output = utf16_to_utf8(resolved_link)) == NULL) {
|
|
|
|
|
/* 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 = EINVAL;
|
|
|
|
|
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)];
|
|
|
|
|
|
|
|
|
|
/* allocate area to hold a null terminated version of the string */
|
|
|
|
|
if ((linkpath = malloc(symlink_nonnull_size + sizeof(wchar_t))) == NULL) {
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy the data out of the reparse buffer and add null terminator */
|
|
|
|
|
memcpy_s(linkpath, symlink_nonnull_size + sizeof(wchar_t), symlink_nonnull, symlink_nonnull_size);
|
|
|
|
|
linkpath[symlink_nonnull_size / sizeof(wchar_t)] = L'\0';
|
|
|
|
|
|
|
|
|
|
/* convert link path to utf8 */
|
|
|
|
|
if ((output = utf16_to_utf8(linkpath)) == NULL) {
|
|
|
|
|
errno = ENOMEM;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ensure output buffer is large enough forward slash and the string */
|
|
|
|
|
ssize_t out_size = (ssize_t) strlen(output);
|
|
|
|
|
if (1 + out_size > bufsiz) {
|
|
|
|
|
errno = ENAMETOOLONG;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
/* determine if we need to prepend a forward slash to make this look like
|
|
|
|
|
* an absolute path C:\Path\Target --> /C:/Path/Target
|
|
|
|
|
*/
|
|
|
|
|
int abs_chars = is_absolute_path(output) ? 1 : 0;
|
|
|
|
|
if (abs_chars)
|
|
|
|
|
buf[0] = '/';
|
|
|
|
|
|
|
|
|
|
/* copy to output buffer in the forward-slash format: /C:/Path/Target */
|
|
|
|
|
/* copy link data to output buffer; per specification, truncation is okay */
|
|
|
|
|
convertToForwardslash(output);
|
|
|
|
|
buf[0] = '/';
|
|
|
|
|
memcpy(buf + 1, output, out_size);
|
|
|
|
|
ret = out_size + 1;
|
|
|
|
|
size_t out_size = strlen(output);
|
|
|
|
|
memcpy(buf + abs_chars, output, min(out_size, bufsiz - abs_chars));
|
|
|
|
|
ret = (ssize_t) min(out_size + abs_chars, bufsiz);
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
|
|
|
|
|
if (linkpath)
|
|
|
|
|
free(linkpath);
|
|
|
|
|
if (reparse_buffer)
|
|
|
|
|
free(reparse_buffer);
|
|
|
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
|
|
|
CloseHandle(handle);
|
|
|
|
|
if (wpath)
|
|
|
|
|
free(wpath);
|
|
|
|
|
if (output)
|
|
|
|
|
free(output);
|
|
|
|
|
if (resolved_link)
|
|
|
|
|
free(resolved_link);
|
|
|
|
|
|
|
|
|
|
return (ssize_t) ret;
|
|
|
|
|
}
|
|
|
|
|
return (ssize_t)ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
fileio_symlink(const char *target, const char *linkpath)
|
|
|
|
|
{
|
|
|
|
|
if (target == NULL || linkpath == NULL) {
|
|
|
|
|
errno = EFAULT;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD ret = 0;
|
|
|
|
|
wchar_t *target_utf16 = utf8_to_utf16(resolved_path(target));
|
|
|
|
|
wchar_t *linkpath_utf16 = utf8_to_utf16(resolved_path(linkpath));
|
|
|
|
|
wchar_t *resolved_utf16 = _wcsdup(target_utf16);
|
|
|
|
|
if (target_utf16 == NULL || linkpath_utf16 == NULL || resolved_utf16 == NULL) {
|
|
|
|
|
errno = ENOMEM;
|
|
|
|
|
ret = -1;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Relative targets are relative to the link and not our current directory
|
|
|
|
|
* so attempt to calculate a resolvable path by removing the link file name
|
|
|
|
|
* leaving only the parent path and then append the relative link:
|
|
|
|
|
* C:\Path\Link with Link->SubDir\Target to C:\Path\SubDir\Target
|
|
|
|
|
*/
|
|
|
|
|
if (!is_absolute_path(target)) {
|
|
|
|
|
|
|
|
|
|
/* allocate area to hold the total possible path */
|
|
|
|
|
free(resolved_utf16);
|
|
|
|
|
size_t resolved_len = (wcslen(target_utf16) + wcslen(linkpath_utf16) + 1);
|
|
|
|
|
resolved_utf16 = malloc(resolved_len * sizeof(wchar_t));
|
|
|
|
|
if (resolved_utf16 == NULL) {
|
|
|
|
|
errno = ENOMEM;
|
|
|
|
|
ret = -1;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* copy the relative target to the end of the link's parent */
|
|
|
|
|
wcscpy_s(resolved_utf16, resolved_len, linkpath_utf16);
|
|
|
|
|
convertToBackslashW(resolved_utf16);
|
|
|
|
|
wchar_t * ptr = wcsrchr(resolved_utf16, L'\\');
|
|
|
|
|
if (ptr == NULL) wcscpy_s(resolved_utf16, resolved_len, target_utf16);
|
|
|
|
|
else wcscpy_s(ptr + 1, resolved_len - (ptr + 1 - resolved_utf16), target_utf16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* unlike other platforms, we need to know whether the symbolic link target is
|
|
|
|
|
* a file or a directory. the only way we can confidently do this is to
|
|
|
|
|
* get the attributes of the target. therefore, our symlink() has the
|
|
|
|
|
* limitation of only creating symlink with valid targets
|
|
|
|
|
*/
|
|
|
|
|
WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 };
|
|
|
|
|
if (GetFileAttributesExW(resolved_utf16, GetFileExInfoStandard, &attributes) == FALSE) {
|
|
|
|
|
errno = errno_from_Win32LastError();
|
|
|
|
|
ret = -1;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* use the attribute of the file to determine the proper flag to send */
|
|
|
|
|
DWORD create_flags = (attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ?
|
|
|
|
|
SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
|
|
|
|
|
|
|
|
|
|
/* symlink creation on earlier versions of windows were a privileged op
|
|
|
|
|
* and then an option was added to create symlink using from an unprivileged
|
|
|
|
|
* context so we try both operations, attempting privileged version first.
|
|
|
|
|
* note: 0x2 = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
|
|
|
|
*/
|
|
|
|
|
if (CreateSymbolicLinkW(linkpath_utf16, target_utf16, create_flags) == 0) {
|
|
|
|
|
if (CreateSymbolicLinkW(linkpath_utf16, target_utf16, create_flags | 0x2) == 0) {
|
|
|
|
|
errno = errno_from_Win32LastError();
|
|
|
|
|
ret = -1;
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
|
|
|
|
|
if (target_utf16)
|
|
|
|
|
free(target_utf16);
|
|
|
|
|
if (linkpath_utf16)
|
|
|
|
|
free(linkpath_utf16);
|
|
|
|
|
if (resolved_utf16)
|
|
|
|
|
free(resolved_utf16);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|