fs: add uv_fs_realpath()

Equivalent to realpath(3), returns the full resolved absolute path of a
file or directory.

PR-URL: https://github.com/libuv/libuv/pull/531
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
This commit is contained in:
Yuval Brik 2015-09-19 00:40:34 +03:00 committed by Saúl Ibarra Corretgé
parent 176fdc7352
commit e76b8838e5
9 changed files with 371 additions and 68 deletions

View File

@ -279,6 +279,16 @@ API
Equivalent to :man:`readlink(2)`.
.. c:function:: int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb)
Equivalent to :man:`realpath(3)` on Unix. Windows uses ``GetFinalPathNameByHandle()``.
.. note::
This function is not implemented on Windows XP and Windows Server 2003.
On these systems, UV_ENOSYS is returned.
.. versionadded:: 1.8.0
.. c:function:: int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb)
.. c:function:: int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file file, uv_uid_t uid, uv_gid_t gid, uv_fs_cb cb)

View File

@ -1088,7 +1088,8 @@ typedef enum {
UV_FS_SYMLINK,
UV_FS_READLINK,
UV_FS_CHOWN,
UV_FS_FCHOWN
UV_FS_FCHOWN,
UV_FS_REALPATH
} uv_fs_type;
/* uv_fs_t is a subclass of uv_req_t. */
@ -1240,6 +1241,10 @@ UV_EXTERN int uv_fs_readlink(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
uv_fs_cb cb);
UV_EXTERN int uv_fs_realpath(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
uv_fs_cb cb);
UV_EXTERN int uv_fs_fchmod(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,

View File

@ -374,20 +374,27 @@ out:
}
static ssize_t uv__fs_pathmax_size(const char* path) {
ssize_t pathmax;
pathmax = pathconf(path, _PC_PATH_MAX);
if (pathmax == -1) {
#if defined(PATH_MAX)
return PATH_MAX;
#else
return 4096;
#endif
}
return pathmax;
}
static ssize_t uv__fs_readlink(uv_fs_t* req) {
ssize_t len;
char* buf;
len = pathconf(req->path, _PC_PATH_MAX);
if (len == -1) {
#if defined(PATH_MAX)
len = PATH_MAX;
#else
len = 4096;
#endif
}
len = uv__fs_pathmax_size(req->path);
buf = uv__malloc(len + 1);
if (buf == NULL) {
@ -408,6 +415,27 @@ static ssize_t uv__fs_readlink(uv_fs_t* req) {
return 0;
}
static ssize_t uv__fs_realpath(uv_fs_t* req) {
ssize_t len;
char* buf;
len = uv__fs_pathmax_size(req->path);
buf = uv__malloc(len + 1);
if (buf == NULL) {
errno = ENOMEM;
return -1;
}
if (realpath(req->path, buf) == NULL) {
uv__free(buf);
return -1;
}
req->ptr = buf;
return 0;
}
static ssize_t uv__fs_sendfile_emul(uv_fs_t* req) {
struct pollfd pfd;
@ -874,6 +902,7 @@ static void uv__fs_work(struct uv__work* w) {
X(READ, uv__fs_buf_iter(req, uv__fs_read));
X(SCANDIR, uv__fs_scandir(req));
X(READLINK, uv__fs_readlink(req));
X(REALPATH, uv__fs_realpath(req));
X(RENAME, rename(req->path, req->new_path));
X(RMDIR, rmdir(req->path));
X(SENDFILE, uv__fs_sendfile(req));
@ -1144,6 +1173,16 @@ int uv_fs_readlink(uv_loop_t* loop,
}
int uv_fs_realpath(uv_loop_t* loop,
uv_fs_t* req,
const char * path,
uv_fs_cb cb) {
INIT(REALPATH);
PATH;
POST;
}
int uv_fs_rename(uv_loop_t* loop,
uv_fs_t* req,
const char* path,

View File

@ -110,6 +110,9 @@ const WCHAR JUNCTION_PREFIX_LEN = 4;
const WCHAR LONG_PATH_PREFIX[] = L"\\\\?\\";
const WCHAR LONG_PATH_PREFIX_LEN = 4;
const WCHAR UNC_PATH_PREFIX[] = L"\\\\?\\UNC\\";
const WCHAR UNC_PATH_PREFIX_LEN = 8;
void uv_fs_init() {
_fmode = _O_BINARY;
@ -233,14 +236,61 @@ INLINE static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req,
}
static int fs__wide_to_utf8(WCHAR* w_source_ptr,
DWORD w_source_len,
char** target_ptr,
uint64_t* target_len_ptr) {
int r;
int target_len;
char* target;
target_len = WideCharToMultiByte(CP_UTF8,
0,
w_source_ptr,
w_source_len,
NULL,
0,
NULL,
NULL);
if (target_len == 0) {
return -1;
}
if (target_len_ptr != NULL) {
*target_len_ptr = target_len;
}
if (target_ptr == NULL) {
return 0;
}
target = uv__malloc(target_len + 1);
if (target == NULL) {
SetLastError(ERROR_OUTOFMEMORY);
return -1;
}
r = WideCharToMultiByte(CP_UTF8,
0,
w_source_ptr,
w_source_len,
target,
target_len,
NULL,
NULL);
assert(r == target_len);
target[target_len] = '\0';
*target_ptr = target;
return 0;
}
INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr,
uint64_t* target_len_ptr) {
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
REPARSE_DATA_BUFFER* reparse_data = (REPARSE_DATA_BUFFER*) buffer;
WCHAR *w_target;
WCHAR* w_target;
DWORD w_target_len;
char* target;
int target_len;
DWORD bytes;
if (!DeviceIoControl(handle,
@ -333,50 +383,7 @@ INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr,
return -1;
}
/* If needed, compute the length of the target. */
if (target_ptr != NULL || target_len_ptr != NULL) {
/* Compute the length of the target. */
target_len = WideCharToMultiByte(CP_UTF8,
0,
w_target,
w_target_len,
NULL,
0,
NULL,
NULL);
if (target_len == 0) {
return -1;
}
}
/* If requested, allocate memory and convert to UTF8. */
if (target_ptr != NULL) {
int r;
target = (char*) uv__malloc(target_len + 1);
if (target == NULL) {
SetLastError(ERROR_OUTOFMEMORY);
return -1;
}
r = WideCharToMultiByte(CP_UTF8,
0,
w_target,
w_target_len,
target,
target_len,
NULL,
NULL);
assert(r == target_len);
target[target_len] = '\0';
*target_ptr = target;
}
if (target_len_ptr != NULL) {
*target_len_ptr = target_len;
}
return 0;
return fs__wide_to_utf8(w_target, w_target_len, target_ptr, target_len_ptr);
}
@ -1707,6 +1714,84 @@ static void fs__readlink(uv_fs_t* req) {
}
static size_t fs__realpath_handle(HANDLE handle, char** realpath_ptr) {
int r;
DWORD w_realpath_len;
WCHAR* w_realpath_ptr;
WCHAR* w_finalpath_ptr = NULL;
w_realpath_len = pGetFinalPathNameByHandleW(handle, NULL, 0, VOLUME_NAME_DOS);
if (w_realpath_len == 0) {
return -1;
}
w_realpath_ptr = uv__malloc((w_realpath_len + 1) * sizeof(WCHAR));
if (w_realpath_ptr == NULL) {
SetLastError(ERROR_OUTOFMEMORY);
return -1;
}
if (pGetFinalPathNameByHandleW(handle,
w_realpath_ptr,
w_realpath_len,
VOLUME_NAME_DOS) == 0) {
uv__free(w_realpath_ptr);
SetLastError(ERROR_INVALID_HANDLE);
return -1;
}
/* convert UNC path to long path */
if (wcsncmp(w_realpath_ptr,
UNC_PATH_PREFIX,
UNC_PATH_PREFIX_LEN) == 0) {
w_finalpath_ptr = w_realpath_ptr + 6;
*w_finalpath_ptr = L'\\';
} else if (wcsncmp(w_realpath_ptr,
LONG_PATH_PREFIX,
LONG_PATH_PREFIX_LEN) == 0) {
w_finalpath_ptr = w_realpath_ptr + 4;
} else {
uv__free(w_realpath_ptr);
SetLastError(ERROR_INVALID_HANDLE);
return -1;
}
r = fs__wide_to_utf8(w_finalpath_ptr, w_realpath_len, realpath_ptr, NULL);
uv__free(w_realpath_ptr);
return r;
}
static void fs__realpath(uv_fs_t* req) {
HANDLE handle;
if (!pGetFinalPathNameByHandleW) {
SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
return;
}
handle = CreateFileW(req->file.pathw,
0,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
if (fs__realpath_handle(handle, (char**) &req->ptr) == -1) {
CloseHandle(handle);
SET_REQ_WIN32_ERROR(req, GetLastError());
return;
}
CloseHandle(handle);
req->flags |= UV_FS_FREE_PTR;
SET_REQ_RESULT(req, 0);
}
static void fs__chown(uv_fs_t* req) {
req->result = 0;
@ -1751,6 +1836,7 @@ static void uv__fs_work(struct uv__work* w) {
XX(LINK, link)
XX(SYMLINK, symlink)
XX(READLINK, readlink)
XX(REALPATH, realpath)
XX(CHOWN, chown)
XX(FCHOWN, fchown);
default:
@ -2075,6 +2161,31 @@ int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
}
int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path,
uv_fs_cb cb) {
int err;
if (!req || !path) {
return UV_EINVAL;
}
uv_fs_req_init(loop, req, UV_FS_REALPATH, cb);
err = fs__capture_path(req, path, NULL, cb != NULL);
if (err) {
return uv_translate_sys_error(err);
}
if (cb) {
QUEUE_FS_TP_JOB(loop, req);
return 0;
} else {
fs__realpath(req);
return req->result;
}
}
int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid,
uv_gid_t gid, uv_fs_cb cb) {
int err;

View File

@ -46,6 +46,7 @@ sSleepConditionVariableSRW pSleepConditionVariableSRW;
sWakeAllConditionVariable pWakeAllConditionVariable;
sWakeConditionVariable pWakeConditionVariable;
sCancelSynchronousIo pCancelSynchronousIo;
sGetFinalPathNameByHandleW pGetFinalPathNameByHandleW;
void uv_winapi_init() {
@ -139,4 +140,7 @@ void uv_winapi_init() {
pCancelSynchronousIo = (sCancelSynchronousIo)
GetProcAddress(kernel32_module, "CancelSynchronousIo");
pGetFinalPathNameByHandleW = (sGetFinalPathNameByHandleW)
GetProcAddress(kernel32_module, "GetFinalPathNameByHandleW");
}

View File

@ -4678,6 +4678,12 @@ typedef VOID (WINAPI* sWakeConditionVariable)
typedef BOOL (WINAPI* sCancelSynchronousIo)
(HANDLE hThread);
typedef DWORD (WINAPI* sGetFinalPathNameByHandleW)
(HANDLE hFile,
LPWSTR lpszFilePath,
DWORD cchFilePath,
DWORD dwFlags);
/* Ntdll function pointers */
extern sRtlNtStatusToDosError pRtlNtStatusToDosError;
extern sNtDeviceIoControlFile pNtDeviceIoControlFile;
@ -4699,5 +4705,6 @@ extern sSleepConditionVariableSRW pSleepConditionVariableSRW;
extern sWakeAllConditionVariable pWakeAllConditionVariable;
extern sWakeConditionVariable pWakeConditionVariable;
extern sCancelSynchronousIo pCancelSynchronousIo;
extern sGetFinalPathNameByHandleW pGetFinalPathNameByHandleW;
#endif /* UV_WIN_WINAPI_H_ */

View File

@ -83,6 +83,7 @@ static int fchown_cb_count;
static int link_cb_count;
static int symlink_cb_count;
static int readlink_cb_count;
static int realpath_cb_count;
static int utime_cb_count;
static int futime_cb_count;
@ -168,6 +169,35 @@ static void readlink_cb(uv_fs_t* req) {
}
static void realpath_cb(uv_fs_t* req) {
char test_file_abs_buf[PATHMAX];
size_t test_file_abs_size = sizeof(test_file_abs_buf);
ASSERT(req->fs_type == UV_FS_REALPATH);
#ifdef _WIN32
/*
* Windows XP and Server 2003 don't support GetFinalPathNameByHandleW()
*/
if (req->result == UV_ENOSYS) {
realpath_cb_count++;
uv_fs_req_cleanup(req);
return;
}
#endif
ASSERT(req->result == 0);
uv_cwd(test_file_abs_buf, &test_file_abs_size);
#ifdef _WIN32
strcat(test_file_abs_buf, "\\test_file");
ASSERT(stricmp(req->ptr, test_file_abs_buf) == 0);
#else
strcat(test_file_abs_buf, "/test_file");
ASSERT(strcmp(req->ptr, test_file_abs_buf) == 0);
#endif
realpath_cb_count++;
uv_fs_req_cleanup(req);
}
static void access_cb(uv_fs_t* req) {
ASSERT(req->fs_type == UV_FS_ACCESS);
access_cb_count++;
@ -1565,11 +1595,43 @@ TEST_IMPL(fs_readlink) {
}
TEST_IMPL(fs_realpath) {
uv_fs_t req;
loop = uv_default_loop();
ASSERT(0 == uv_fs_realpath(loop, &req, "no_such_file", dummy_cb));
ASSERT(0 == uv_run(loop, UV_RUN_DEFAULT));
ASSERT(dummy_cb_count == 1);
ASSERT(req.ptr == NULL);
#ifdef _WIN32
/*
* Windows XP and Server 2003 don't support GetFinalPathNameByHandleW()
*/
if (req.result == UV_ENOSYS) {
uv_fs_req_cleanup(&req);
RETURN_SKIP("realpath is not supported on Windows XP");
}
#endif
ASSERT(req.result == UV_ENOENT);
uv_fs_req_cleanup(&req);
ASSERT(UV_ENOENT == uv_fs_realpath(NULL, &req, "no_such_file", NULL));
ASSERT(req.ptr == NULL);
ASSERT(req.result == UV_ENOENT);
uv_fs_req_cleanup(&req);
MAKE_VALGRIND_HAPPY();
return 0;
}
TEST_IMPL(fs_symlink) {
int r;
uv_fs_t req;
uv_file file;
uv_file link;
char test_file_abs_buf[PATHMAX];
size_t test_file_abs_size;
/* Setup. */
unlink("test_file");
@ -1577,6 +1639,14 @@ TEST_IMPL(fs_symlink) {
unlink("test_file_symlink2");
unlink("test_file_symlink_symlink");
unlink("test_file_symlink2_symlink");
test_file_abs_size = sizeof(test_file_abs_buf);
#ifdef _WIN32
uv_cwd(test_file_abs_buf, &test_file_abs_size);
strcat(test_file_abs_buf, "\\test_file");
#else
uv_cwd(test_file_abs_buf, &test_file_abs_size);
strcat(test_file_abs_buf, "/test_file");
#endif
loop = uv_default_loop();
@ -1647,6 +1717,24 @@ TEST_IMPL(fs_symlink) {
ASSERT(strcmp(req.ptr, "test_file_symlink") == 0);
uv_fs_req_cleanup(&req);
r = uv_fs_realpath(NULL, &req, "test_file_symlink_symlink", NULL);
#ifdef _WIN32
/*
* Windows XP and Server 2003 don't support GetFinalPathNameByHandleW()
*/
if (r == UV_ENOSYS) {
uv_fs_req_cleanup(&req);
RETURN_SKIP("realpath is not supported on Windows XP");
}
#endif
ASSERT(r == 0);
#ifdef _WIN32
ASSERT(stricmp(req.ptr, test_file_abs_buf) == 0);
#else
ASSERT(strcmp(req.ptr, test_file_abs_buf) == 0);
#endif
uv_fs_req_cleanup(&req);
/* async link */
r = uv_fs_symlink(loop,
&req,
@ -1687,6 +1775,20 @@ TEST_IMPL(fs_symlink) {
uv_run(loop, UV_RUN_DEFAULT);
ASSERT(readlink_cb_count == 1);
r = uv_fs_realpath(loop, &req, "test_file", realpath_cb);
#ifdef _WIN32
/*
* Windows XP and Server 2003 don't support GetFinalPathNameByHandleW()
*/
if (r == UV_ENOSYS) {
uv_fs_req_cleanup(&req);
RETURN_SKIP("realpath is not supported on Windows XP");
}
#endif
ASSERT(r == 0);
uv_run(loop, UV_RUN_DEFAULT);
ASSERT(realpath_cb_count == 1);
/*
* Run the loop just to check we don't have make any extraneous uv_ref()
* calls. This should drop out immediately.
@ -1710,12 +1812,15 @@ TEST_IMPL(fs_symlink_dir) {
int r;
char* test_dir;
uv_dirent_t dent;
static char test_dir_abs_buf[PATHMAX];
size_t test_dir_abs_size;
/* set-up */
unlink("test_dir/file1");
unlink("test_dir/file2");
rmdir("test_dir");
rmdir("test_dir_symlink");
test_dir_abs_size = sizeof(test_dir_abs_buf);
loop = uv_default_loop();
@ -1723,16 +1828,16 @@ TEST_IMPL(fs_symlink_dir) {
uv_fs_req_cleanup(&req);
#ifdef _WIN32
{
static char src_path_buf[PATHMAX];
size_t size;
size = sizeof(src_path_buf);
strcpy(src_path_buf, "\\\\?\\");
uv_cwd(src_path_buf + 4, &size);
strcat(src_path_buf, "\\test_dir\\");
test_dir = src_path_buf;
}
strcpy(test_dir_abs_buf, "\\\\?\\");
uv_cwd(test_dir_abs_buf + 4, &test_dir_abs_size);
test_dir_abs_size += 4;
strcat(test_dir_abs_buf, "\\test_dir\\");
test_dir_abs_size += strlen("\\test_dir\\");
test_dir = test_dir_abs_buf;
#else
uv_cwd(test_dir_abs_buf, &test_dir_abs_size);
strcat(test_dir_abs_buf, "/test_dir");
test_dir_abs_size += strlen("/test_dir");
test_dir = "test_dir";
#endif
@ -1767,6 +1872,25 @@ TEST_IMPL(fs_symlink_dir) {
#endif
uv_fs_req_cleanup(&req);
r = uv_fs_realpath(NULL, &req, "test_dir_symlink", NULL);
#ifdef _WIN32
/*
* Windows XP and Server 2003 don't support GetFinalPathNameByHandleW()
*/
if (r == UV_ENOSYS) {
uv_fs_req_cleanup(&req);
RETURN_SKIP("realpath is not supported on Windows XP");
}
#endif
ASSERT(r == 0);
#ifdef _WIN32
ASSERT(strlen(req.ptr) == test_dir_abs_size - 5);
ASSERT(strnicmp(req.ptr, test_dir + 4, test_dir_abs_size - 5) == 0);
#else
ASSERT(strcmp(req.ptr, test_dir_abs_buf) == 0);
#endif
uv_fs_req_cleanup(&req);
r = uv_fs_open(NULL, &open_req1, "test_dir/file1", O_WRONLY | O_CREAT,
S_IWUSR | S_IRUSR, NULL);
ASSERT(r >= 0);

View File

@ -254,6 +254,7 @@ TEST_DECLARE (fs_unlink_readonly)
TEST_DECLARE (fs_chown)
TEST_DECLARE (fs_link)
TEST_DECLARE (fs_readlink)
TEST_DECLARE (fs_realpath)
TEST_DECLARE (fs_symlink)
TEST_DECLARE (fs_symlink_dir)
TEST_DECLARE (fs_utime)
@ -675,6 +676,7 @@ TASK_LIST_START
TEST_ENTRY (fs_utime)
TEST_ENTRY (fs_futime)
TEST_ENTRY (fs_readlink)
TEST_ENTRY (fs_realpath)
TEST_ENTRY (fs_symlink)
TEST_ENTRY (fs_symlink_dir)
TEST_ENTRY (fs_stat_missing_path)

View File

@ -276,7 +276,7 @@ TEST_IMPL(threadpool_cancel_work) {
TEST_IMPL(threadpool_cancel_fs) {
struct cancel_info ci;
uv_fs_t reqs[25];
uv_fs_t reqs[26];
uv_loop_t* loop;
unsigned n;
uv_buf_t iov;
@ -305,6 +305,7 @@ TEST_IMPL(threadpool_cancel_fs) {
ASSERT(0 == uv_fs_read(loop, reqs + n++, 0, &iov, 1, 0, fs_cb));
ASSERT(0 == uv_fs_scandir(loop, reqs + n++, "/", 0, fs_cb));
ASSERT(0 == uv_fs_readlink(loop, reqs + n++, "/", fs_cb));
ASSERT(0 == uv_fs_realpath(loop, reqs + n++, "/", fs_cb));
ASSERT(0 == uv_fs_rename(loop, reqs + n++, "/", "/", fs_cb));
ASSERT(0 == uv_fs_mkdir(loop, reqs + n++, "/", 0, fs_cb));
ASSERT(0 == uv_fs_sendfile(loop, reqs + n++, 0, 0, 0, 0, fs_cb));