1
0
mirror of https://github.com/libuv/libuv synced 2025-03-28 21:13:16 +00:00

windows: support junctions with uv_fs_synlink

based on @piscisaureus implementation
This commit is contained in:
Igor Zinkovsky 2012-05-08 15:44:11 -07:00
parent 9efa8b3571
commit 60af28abab
5 changed files with 450 additions and 40 deletions

View File

@ -36,6 +36,10 @@
#define MAX_PIPENAME_LEN 256
#ifndef S_IFLNK
# define S_IFLNK 0xA000
#endif
/*
* Guids and typedefs for winsock extension functions
* Mingw32 doesn't have these :-(

View File

@ -1441,6 +1441,12 @@ UV_EXTERN int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path,
*/
#define UV_FS_SYMLINK_DIR 0x0001
/*
* This flag can be used with uv_fs_symlink on Windows
* to specify whether the symlink is to be created using junction points.
*/
#define UV_FS_SYMLINK_JUNCTION 0x0002
UV_EXTERN int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
const char* new_path, int flags, uv_fs_cb cb);

View File

@ -89,6 +89,16 @@
return; \
}
#define IS_SLASH(c) ((c) == L'\\' || (c) == L'/')
#define IS_LETTER(c) (((c) >= L'a' && (c) <= L'z') || \
((c) >= L'A' && (c) <= L'Z'))
const wchar_t JUNCTION_PREFIX[] = L"\\??\\";
const wchar_t JUNCTION_PREFIX_LEN = 4;
const wchar_t LONG_PATH_PREFIX[] = L"\\\\?\\";
const wchar_t LONG_PATH_PREFIX_LEN = 4;
void uv_fs_init() {
_fmode = _O_BINARY;
@ -128,6 +138,61 @@ static void uv_fs_req_init_sync(uv_loop_t* loop, uv_fs_t* req,
}
static int is_path_dir(const wchar_t* path) {
DWORD attr = GetFileAttributesW(path);
if (attr != INVALID_FILE_ATTRIBUTES) {
return attr & FILE_ATTRIBUTE_DIRECTORY ? 1 : 0;
} else {
return 0;
}
}
static int get_reparse_point(HANDLE handle, int* target_length) {
void* buffer = NULL;
REPARSE_DATA_BUFFER* reparse_data;
DWORD bytes_returned;
int rv = 0;
buffer = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
if (!buffer) {
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
}
if (!DeviceIoControl(handle,
FSCTL_GET_REPARSE_POINT,
NULL,
0,
buffer,
MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
&bytes_returned,
NULL)) {
free(buffer);
return 0;
}
reparse_data = (REPARSE_DATA_BUFFER*)buffer;
if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
rv = 1;
if (target_length) {
*target_length = reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
sizeof(wchar_t);
}
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
rv = 1;
if (target_length) {
*target_length = reparse_data->MountPointReparseBuffer.SubstituteNameLength /
sizeof(wchar_t);
}
}
free(buffer);
return rv;
}
void fs__open(uv_fs_t* req, const wchar_t* path, int flags, int mode) {
DWORD access;
DWORD share;
@ -347,24 +412,57 @@ void fs__write(uv_fs_t* req, uv_file file, void *buf, size_t length,
}
void fs__unlink(uv_fs_t* req, const wchar_t* path) {
int result = _wunlink(path);
void fs__rmdir(uv_fs_t* req, const wchar_t* path) {
int result = _wrmdir(path);
SET_REQ_RESULT(req, result);
}
void fs__unlink(uv_fs_t* req, const wchar_t* path) {
int result;
HANDLE handle;
BY_HANDLE_FILE_INFORMATION info;
int is_dir_symlink;
handle = CreateFileW(path,
0,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (!GetFileInformationByHandle(handle, &info)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
CloseHandle(handle);
return;
}
is_dir_symlink = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY &&
get_reparse_point(handle, NULL);
CloseHandle(handle);
if (is_dir_symlink) {
fs__rmdir(req, path);
} else {
result = _wunlink(path);
SET_REQ_RESULT(req, result);
}
}
void fs__mkdir(uv_fs_t* req, const wchar_t* path, int mode) {
int result = _wmkdir(path);
SET_REQ_RESULT(req, result);
}
void fs__rmdir(uv_fs_t* req, const wchar_t* path) {
int result = _wrmdir(path);
SET_REQ_RESULT(req, result);
}
void fs__readdir(uv_fs_t* req, const wchar_t* path, int flags) {
int result, size;
wchar_t* buf = NULL, *ptr, *name;
@ -472,18 +570,26 @@ void fs__readdir(uv_fs_t* req, const wchar_t* path, int flags) {
}
static void fs__stat(uv_fs_t* req, const wchar_t* path) {
static void fs__stat(uv_fs_t* req, const wchar_t* path, int link) {
HANDLE handle;
int target_length;
int symlink = 0;
BY_HANDLE_FILE_INFORMATION info;
DWORD flags;
req->ptr = NULL;
flags = FILE_FLAG_BACKUP_SEMANTICS;
if (link) {
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
handle = CreateFileW(path,
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
flags,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
@ -500,32 +606,37 @@ static void fs__stat(uv_fs_t* req, const wchar_t* path) {
/* TODO: set st_dev and st_ino? */
if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
req->stat.st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6));
if (link && get_reparse_point(handle, &target_length)) {
req->stat.st_mode = S_IFLNK;
/* Adjust for long path */
req->stat.st_size = target_length - JUNCTION_PREFIX_LEN;
} else {
req->stat.st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3) +
((_S_IREAD|_S_IWRITE) >> 6));
}
if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
req->stat.st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6));
} else {
req->stat.st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3) +
((_S_IREAD|_S_IWRITE) >> 6));
}
if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
req->stat.st_mode |= _S_IFDIR;
} else {
req->stat.st_mode |= _S_IFREG;
if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
req->stat.st_mode |= _S_IFDIR;
} else {
req->stat.st_mode |= _S_IFREG;
}
req->stat.st_size = ((int64_t) info.nFileSizeHigh << 32) +
(int64_t) info.nFileSizeLow;
}
uv_filetime_to_time_t(&info.ftLastWriteTime, &(req->stat.st_mtime));
uv_filetime_to_time_t(&info.ftLastAccessTime, &(req->stat.st_atime));
uv_filetime_to_time_t(&info.ftCreationTime, &(req->stat.st_ctime));
req->stat.st_size = ((int64_t) info.nFileSizeHigh << 32) +
(int64_t) info.nFileSizeLow;
req->stat.st_nlink = (info.nNumberOfLinks <= SHRT_MAX) ?
(short) info.nNumberOfLinks : SHRT_MAX;
req->ptr = &req->stat;
req->result = 0;
CloseHandle(handle);
}
@ -720,23 +831,193 @@ void fs__link(uv_fs_t* req, const wchar_t* path, const wchar_t* new_path) {
}
void fs__create_junction(uv_fs_t* req, const wchar_t* path, const wchar_t* new_path) {
HANDLE handle = INVALID_HANDLE_VALUE;
REPARSE_DATA_BUFFER *buffer = NULL;
int created = 0;
int target_len;
int is_absolute, is_long_path;
int needed_buf_size, used_buf_size, used_data_size, path_buf_len;
int start, len, i;
int add_slash;
DWORD bytes;
wchar_t* path_buf;
target_len = wcslen(path);
is_long_path = wcsncmp(path, LONG_PATH_PREFIX, LONG_PATH_PREFIX_LEN) == 0;
if (is_long_path) {
is_absolute = 1;
} else {
is_absolute = target_len >= 3 && IS_LETTER(path[0]) &&
path[1] == L':' && IS_SLASH(path[2]);
}
if (!is_absolute) {
/* Not supporting relative paths */
SET_REQ_UV_ERROR(req, EINVAL, ERROR_NOT_SUPPORTED);
return;
}
// Do a pessimistic calculation of the required buffer size
needed_buf_size =
FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
JUNCTION_PREFIX_LEN * sizeof(wchar_t) +
2 * (target_len + 2) * sizeof(wchar_t);
// Allocate the buffer
buffer = (REPARSE_DATA_BUFFER*)malloc(needed_buf_size);
if (!buffer) {
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
}
// Grab a pointer to the part of the buffer where filenames go
path_buf = (wchar_t*)&(buffer->MountPointReparseBuffer.PathBuffer);
path_buf_len = 0;
// Copy the substitute (internal) target path
start = path_buf_len;
wcsncpy((wchar_t*)&path_buf[path_buf_len], JUNCTION_PREFIX,
JUNCTION_PREFIX_LEN);
path_buf_len += JUNCTION_PREFIX_LEN;
add_slash = 0;
for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
if (IS_SLASH(path[i])) {
add_slash = 1;
continue;
}
if (add_slash) {
path_buf[path_buf_len++] = L'\\';
add_slash = 0;
}
path_buf[path_buf_len++] = path[i];
}
path_buf[path_buf_len++] = L'\\';
len = path_buf_len - start;
// Set the info about the substitute name
buffer->MountPointReparseBuffer.SubstituteNameOffset = start * sizeof(wchar_t);
buffer->MountPointReparseBuffer.SubstituteNameLength = len * sizeof(wchar_t);
// Insert null terminator
path_buf[path_buf_len++] = L'\0';
// Copy the print name of the target path
start = path_buf_len;
add_slash = 0;
for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
if (IS_SLASH(path[i])) {
add_slash = 1;
continue;
}
if (add_slash) {
path_buf[path_buf_len++] = L'\\';
add_slash = 0;
}
path_buf[path_buf_len++] = path[i];
}
len = path_buf_len - start;
if (len == 2) {
path_buf[path_buf_len++] = L'\\';
len++;
}
// Set the info about the print name
buffer->MountPointReparseBuffer.PrintNameOffset = start * sizeof(wchar_t);
buffer->MountPointReparseBuffer.PrintNameLength = len * sizeof(wchar_t);
// Insert another null terminator
path_buf[path_buf_len++] = L'\0';
// Calculate how much buffer space was actually used
used_buf_size = FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) +
path_buf_len * sizeof(wchar_t);
used_data_size = used_buf_size -
FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer);
// Put general info in the data buffer
buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
buffer->ReparseDataLength = used_data_size;
buffer->Reserved = 0;
// Create a new directory
if (!CreateDirectoryW(new_path, NULL)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto error;
}
created = 1;
// Open the directory
handle = CreateFileW(new_path,
GENERIC_ALL,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS |
FILE_FLAG_OPEN_REPARSE_POINT,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto error;
}
// Create the actual reparse point
if (!DeviceIoControl(handle,
FSCTL_SET_REPARSE_POINT,
buffer,
used_buf_size,
NULL,
0,
&bytes,
NULL)) {
SET_REQ_WIN32_ERROR(req, GetLastError());
goto error;
}
// Clean up
CloseHandle(handle);
free(buffer);
SET_REQ_RESULT(req, 0);
return;
error:
free(buffer);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
if (created) {
RemoveDirectoryW(new_path);
}
}
void fs__symlink(uv_fs_t* req, const wchar_t* path, const wchar_t* new_path,
int flags) {
int result;
if (pCreateSymbolicLinkW) {
if (flags & UV_FS_SYMLINK_JUNCTION) {
fs__create_junction(req, path, new_path);
} else if (pCreateSymbolicLinkW) {
result = pCreateSymbolicLinkW(new_path,
path,
flags & UV_FS_SYMLINK_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) ? 0 : -1;
if (result == -1) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
} else {
SET_REQ_RESULT(req, result);
}
} else {
SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
return;
}
SET_REQ_RESULT(req, result);
}
@ -786,22 +1067,31 @@ void fs__readlink(uv_fs_t* req, const wchar_t* path) {
}
reparse_data = (REPARSE_DATA_BUFFER*)buffer;
if (reparse_data->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
substitute_name = reparse_data->SymbolicLinkReparseBuffer.PathBuffer +
(reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset /
sizeof(wchar_t));
substitute_name_length =
reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength /
sizeof(wchar_t);
} else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
substitute_name = reparse_data->MountPointReparseBuffer.PathBuffer +
(reparse_data->MountPointReparseBuffer.SubstituteNameOffset /
sizeof(wchar_t));
substitute_name_length =
reparse_data->MountPointReparseBuffer.SubstituteNameLength /
sizeof(wchar_t);
} else {
result = -1;
/* something is seriously wrong */
SET_REQ_WIN32_ERROR(req, GetLastError());
goto done;
}
substitute_name = reparse_data->SymbolicLinkReparseBuffer.PathBuffer +
(reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(wchar_t));
substitute_name_length =
reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
/* Strip off the leading \??\ from the substitute name buffer.*/
if (memcmp(substitute_name, L"\\??\\", 8) == 0) {
substitute_name += 4;
substitute_name_length -= 4;
if (wcsncmp(substitute_name, JUNCTION_PREFIX, JUNCTION_PREFIX_LEN) == 0) {
substitute_name += JUNCTION_PREFIX_LEN;
substitute_name_length -= JUNCTION_PREFIX_LEN;
}
utf8size = uv_utf16_to_utf8(substitute_name,
@ -884,8 +1174,10 @@ static DWORD WINAPI uv_fs_thread_proc(void* parameter) {
fs__readdir(req, req->pathw, req->file_flags);
break;
case UV_FS_STAT:
fs__stat(req, req->pathw, 0);
break;
case UV_FS_LSTAT:
fs__stat(req, req->pathw);
fs__stat(req, req->pathw, 1);
break;
case UV_FS_FSTAT:
fs__fstat(req, req->file);
@ -1260,7 +1552,7 @@ int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
} else {
uv_fs_req_init_sync(loop, req, UV_FS_STAT);
UTF8_TO_UTF16(path2 ? path2 : path, pathw);
fs__stat(req, pathw);
fs__stat(req, pathw, 0);
if (path2) {
free(path2);
}
@ -1304,7 +1596,7 @@ int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
} else {
uv_fs_req_init_sync(loop, req, UV_FS_LSTAT);
UTF8_TO_UTF16(path2 ? path2 : path, pathw);
fs__stat(req, pathw);
fs__stat(req, pathw, 1);
if (path2) {
free(path2);
}

View File

@ -46,6 +46,7 @@
#endif
#define TOO_LONG_NAME_LENGTH 65536
#define PATHMAX 1024
typedef struct {
const char* path;
@ -1301,6 +1302,111 @@ TEST_IMPL(fs_symlink) {
}
TEST_IMPL(fs_symlink_dir) {
uv_fs_t req;
int r;
char src_path_buf[PATHMAX];
char* test_dir;
/* set-up */
unlink("test_dir/file1");
unlink("test_dir/file2");
rmdir("test_dir");
rmdir("test_dir_symlink");
loop = uv_default_loop();
uv_fs_mkdir(loop, &req, "test_dir", 0777, NULL);
uv_fs_req_cleanup(&req);
#ifdef _WIN32
strcpy(src_path_buf, "\\\\?\\");
uv_cwd(src_path_buf + 4, sizeof(src_path_buf)/sizeof(src_path_buf[0]));
strcat(src_path_buf, "\\test_dir\\");
test_dir = src_path_buf;
#else
test_dir = "test_dir";
#endif
r = uv_fs_symlink(loop, &req, test_dir, "test_dir_symlink",
UV_FS_SYMLINK_JUNCTION, NULL);
ASSERT(r == 0);
ASSERT(req.result == 0);
uv_fs_req_cleanup(&req);
r = uv_fs_stat(loop, &req, "test_dir_symlink", NULL);
ASSERT(r == 0);
ASSERT(((struct stat*)req.ptr)->st_mode & S_IFDIR);
uv_fs_req_cleanup(&req);
r = uv_fs_lstat(loop, &req, "test_dir_symlink", NULL);
ASSERT(r == 0);
ASSERT(((struct stat*)req.ptr)->st_mode & S_IFLNK);
ASSERT(((struct stat*)req.ptr)->st_size == 22);
uv_fs_req_cleanup(&req);
r = uv_fs_readlink(loop, &req, "test_dir_symlink", NULL);
ASSERT(r == 0);
#ifdef _WIN32
ASSERT(strcmp(req.ptr, test_dir + 4) == 0);
#else
ASSERT(strcmp(req.ptr, test_dir) == 0);
#endif
uv_fs_req_cleanup(&req);
r = uv_fs_open(loop, &open_req1, "test_dir/file1", O_WRONLY | O_CREAT,
S_IWRITE | S_IREAD, NULL);
ASSERT(r != -1);
uv_fs_req_cleanup(&open_req1);
r = uv_fs_close(loop, &close_req, open_req1.result, NULL);
ASSERT(r == 0);
uv_fs_req_cleanup(&close_req);
r = uv_fs_open(loop, &open_req1, "test_dir/file2", O_WRONLY | O_CREAT,
S_IWRITE | S_IREAD, NULL);
ASSERT(r != -1);
uv_fs_req_cleanup(&open_req1);
r = uv_fs_close(loop, &close_req, open_req1.result, NULL);
ASSERT(r == 0);
uv_fs_req_cleanup(&close_req);
r = uv_fs_readdir(loop, &readdir_req, "test_dir_symlink", 0, NULL);
ASSERT(r == 2);
ASSERT(readdir_req.result == 2);
ASSERT(readdir_req.ptr);
ASSERT(memcmp(readdir_req.ptr, "file1\0file2\0", 12) == 0
|| memcmp(readdir_req.ptr, "file2\0file1\0", 12) == 0);
uv_fs_req_cleanup(&readdir_req);
ASSERT(!readdir_req.ptr);
/* unlink will remove the directory symlink */
r = uv_fs_unlink(loop, &req, "test_dir_symlink", NULL);
ASSERT(r == 0);
uv_fs_req_cleanup(&req);
r = uv_fs_readdir(loop, &readdir_req, "test_dir_symlink", 0, NULL);
ASSERT(r == -1);
uv_fs_req_cleanup(&readdir_req);
r = uv_fs_readdir(loop, &readdir_req, "test_dir", 0, NULL);
ASSERT(r == 2);
ASSERT(readdir_req.result == 2);
ASSERT(readdir_req.ptr);
ASSERT(memcmp(readdir_req.ptr, "file1\0file2\0", 12) == 0
|| memcmp(readdir_req.ptr, "file2\0file1\0", 12) == 0);
uv_fs_req_cleanup(&readdir_req);
ASSERT(!readdir_req.ptr);
/* clean-up */
unlink("test_dir/file1");
unlink("test_dir/file2");
rmdir("test_dir");
rmdir("test_dir_symlink");
return 0;
}
TEST_IMPL(fs_utime) {
utime_check_t checkme;
const char* path = "test_file";

View File

@ -131,6 +131,7 @@ TEST_DECLARE (fs_chmod)
TEST_DECLARE (fs_chown)
TEST_DECLARE (fs_link)
TEST_DECLARE (fs_symlink)
TEST_DECLARE (fs_symlink_dir)
TEST_DECLARE (fs_utime)
TEST_DECLARE (fs_futime)
TEST_DECLARE (fs_file_open_append)
@ -346,6 +347,7 @@ TASK_LIST_START
TEST_ENTRY (fs_utime)
TEST_ENTRY (fs_futime)
TEST_ENTRY (fs_symlink)
TEST_ENTRY (fs_symlink_dir)
TEST_ENTRY (fs_stat_missing_path)
TEST_ENTRY (fs_read_file_eof)
TEST_ENTRY (fs_file_open_append)