mirror of
https://github.com/PowerShell/openssh-portable.git
synced 2025-07-31 01:35:11 +02:00
Added symlink() Implementation; Revised readlink() (#289)
- Added implementation of symlink() using native CreateSymbolicLink() function. - Added unit tests for symlink behavior in readlink(), lstat, stat(), and symlink(). - Reworked readlink() to be more inline with POSIX readlink() behavior. - Reworked symlink handling in stat/lstat due to revised readlink(). - Added additional error handling to readlink(). - Added symlink() Implementation - Memory Leak Fix - Modified fileio_readlink() to properly free a temporary buffer it creates.
This commit is contained in:
parent
00b869dbd8
commit
993cce0798
@ -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;
|
||||
}
|
||||
|
@ -20,5 +20,3 @@ FILE* w32_fdopen(int fd, const char *mode);
|
||||
|
||||
int w32_rename(const char *old_name, const char *new_name);
|
||||
#define rename w32_rename
|
||||
|
||||
int is_absolute_path(char *);
|
@ -644,9 +644,7 @@ w32_utimes(const char *filename, struct timeval *tvp)
|
||||
int
|
||||
w32_symlink(const char *target, const char *linkpath)
|
||||
{
|
||||
/* Not supported in windows */
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
return fileio_symlink(target, linkpath);
|
||||
}
|
||||
|
||||
int
|
||||
@ -1443,7 +1441,7 @@ get_program_data_path()
|
||||
|
||||
/* Windows absolute paths - \abc, /abc, c:\abc, c:/abc, __PROGRAMDATA__\openssh\sshd_config */
|
||||
int
|
||||
is_absolute_path(char *path)
|
||||
is_absolute_path(const char *path)
|
||||
{
|
||||
int retVal = 0;
|
||||
if(*path == '\"') /* skip double quote if path is "c:\abc" */
|
||||
|
@ -41,4 +41,5 @@ char* get_program_data_path();
|
||||
HANDLE get_user_token(char* user);
|
||||
int load_user_profile(HANDLE user_token, char* user);
|
||||
int copy_file(char *source, char *destination);
|
||||
int create_directory_withsddl(char *path, char *sddl);
|
||||
int create_directory_withsddl(char *path, char *sddl);
|
||||
int is_absolute_path(const char *);
|
@ -164,4 +164,5 @@ 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);
|
||||
ssize_t fileio_readlink(const char *path, char *buf, size_t bufsiz);
|
||||
int fileio_symlink(const char *target, const char *linkpath);
|
@ -1,5 +1,8 @@
|
||||
/*
|
||||
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
|
||||
*
|
||||
* Author: Bryan Berns <berns@uwalumni.com>
|
||||
* Added tests for symlink(), readlink(), lstat()
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
@ -487,6 +490,117 @@ file_miscellaneous_tests()
|
||||
TEST_DONE();
|
||||
}
|
||||
|
||||
void
|
||||
file_symlink_tests()
|
||||
{
|
||||
/* skip these unit tests if we cannot create symbolic links at all
|
||||
* note: 0x2 = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
||||
*/
|
||||
DeleteFileW(L"admin_check");
|
||||
if (CreateSymbolicLinkW(L"admin_check", L"admin_check", 0) == 0 &&
|
||||
CreateSymbolicLinkW(L"admin_check", L"admin_check", 0x2 == 0)) {
|
||||
return;
|
||||
}
|
||||
DeleteFileW(L"admin_check");
|
||||
|
||||
wchar_t curdir[MAX_PATH];
|
||||
GetCurrentDirectoryW(MAX_PATH, curdir);
|
||||
|
||||
/* perform a variety of symlink tests using unicode, directory targets,
|
||||
* file targets, absolute/relative links, absolute/relative targets
|
||||
*/
|
||||
for (int do_unicode = 0; do_unicode <= 1; do_unicode++)
|
||||
for (int do_dir = 0; do_dir <= 1; do_dir++)
|
||||
for (int do_absolute_lnk = 0; do_absolute_lnk <= 1; do_absolute_lnk++)
|
||||
for (int do_absolute_tgt = 0; do_absolute_tgt <= 1; do_absolute_tgt++)
|
||||
{
|
||||
char test_name[128];
|
||||
sprintf(test_name, "Symlink: %s link, %s %s target, %s",
|
||||
(do_absolute_lnk) ? "relative" : "absolute",
|
||||
(do_absolute_tgt) ? "relative" : "absolute",
|
||||
(do_dir) ? "directory" : "file",
|
||||
(do_unicode) ? "unicode" : "ansi");
|
||||
TEST_START(test_name);
|
||||
|
||||
/* cleanup / setup basic test structure */
|
||||
_wsystem(L"RD /S /Q win32compat-tmp >NUL 2>&1");
|
||||
_wsystem(L"MKDIR win32compat-tmp >NUL 2>&1");
|
||||
|
||||
wchar_t tgt_path[MAX_PATH] = L"";
|
||||
wchar_t lnk_path[MAX_PATH] = L"";
|
||||
|
||||
/* prepend absolute path if doing absolute test */
|
||||
if (do_absolute_tgt) {
|
||||
wcscat(tgt_path, L"/");
|
||||
wcscat(tgt_path, curdir);
|
||||
wcscat(tgt_path, L"/");
|
||||
}
|
||||
if (do_absolute_lnk) {
|
||||
wcscat(lnk_path, L"/");
|
||||
wcscat(lnk_path, curdir);
|
||||
wcscat(lnk_path, L"/");
|
||||
}
|
||||
|
||||
/* append the test paths */
|
||||
wcscat(tgt_path, L"win32compat-tmp/tgt");
|
||||
wcscat(lnk_path, L"win32compat-tmp/lnk");
|
||||
|
||||
/* append unicode char if doing unicode test */
|
||||
if (do_unicode) {
|
||||
wcscat(tgt_path, L"Δ");
|
||||
wcscat(lnk_path, L"Δ");
|
||||
}
|
||||
|
||||
/* ensure target is in forward slash format since this is
|
||||
* required for the readlink test output later *
|
||||
*/
|
||||
for (wchar_t * t = tgt_path; *t; t++) if (*t == '\\') *t = L'/';
|
||||
|
||||
/* create directory or file as target --- we have to offset
|
||||
* the first forward slash so the windows functions operate
|
||||
*/
|
||||
if (do_dir)
|
||||
CreateDirectoryW(&tgt_path[do_absolute_tgt], NULL);
|
||||
else
|
||||
CloseHandle(CreateFileW(&tgt_path[do_absolute_tgt],
|
||||
GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL));
|
||||
|
||||
/* convert to utf8 for test */
|
||||
char * tgt_utf8 = utf16_to_utf8(tgt_path);
|
||||
char * lnk_utf8 = utf16_to_utf8(lnk_path);
|
||||
|
||||
/* for relative link, the target is relative to the link */
|
||||
char * tgt_name_utf8 = tgt_utf8;
|
||||
if (!do_absolute_tgt) tgt_name_utf8 = strrchr(tgt_utf8, '/') + 1;
|
||||
|
||||
/* create symlink */
|
||||
int symlink_ret = symlink(tgt_name_utf8, lnk_utf8);
|
||||
ASSERT_INT_EQ(symlink_ret, 0);
|
||||
|
||||
/* verify readlink() output against symlink() input */
|
||||
char readlink_buf[MAX_PATH] = "";
|
||||
int readlink_ret = readlink(lnk_utf8, readlink_buf, MAX_PATH);
|
||||
ASSERT_INT_EQ(readlink_ret, strlen(tgt_name_utf8));
|
||||
ASSERT_INT_EQ(0, memcmp(readlink_buf, tgt_name_utf8, readlink_ret));
|
||||
|
||||
/* verify lstat() gets the reference to the link */
|
||||
struct w32_stat statbuf;
|
||||
int lstat_ret = lstat(lnk_utf8, &statbuf);
|
||||
ASSERT_INT_EQ(lstat_ret, 0);
|
||||
ASSERT_INT_EQ(1, S_ISLNK(statbuf.st_mode));
|
||||
|
||||
/* verify stat() gets a reference to the dir or file */
|
||||
int stat_ret = stat(lnk_utf8, &statbuf);
|
||||
ASSERT_INT_EQ(stat_ret, 0);
|
||||
ASSERT_INT_EQ(0, S_ISLNK(statbuf.st_mode));
|
||||
ASSERT_INT_EQ(do_dir, S_ISDIR(statbuf.st_mode));
|
||||
|
||||
TEST_DONE();
|
||||
}
|
||||
|
||||
_wsystem(L"RD /S /Q win32compat-tmp >NUL 2>&1");
|
||||
}
|
||||
|
||||
void
|
||||
file_tests()
|
||||
{
|
||||
@ -496,4 +610,5 @@ file_tests()
|
||||
file_nonblocking_io_tests();
|
||||
file_select_tests();
|
||||
file_miscellaneous_tests();
|
||||
file_symlink_tests();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user