fs: add uv_fs_mkstemp
Thanks to Andreas Hauptmann and Bastian Schmitz for their earlier work. Supersedes: https://github.com/libuv/libuv/pull/2074 Closes: https://github.com/libuv/libuv/issues/2555 PR-URL: https://github.com/libuv/libuv/pull/2557 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
0a4e0b9d30
commit
5500253cac
@ -99,7 +99,8 @@ Data types
|
||||
UV_FS_LCHOWN,
|
||||
UV_FS_OPENDIR,
|
||||
UV_FS_READDIR,
|
||||
UV_FS_CLOSEDIR
|
||||
UV_FS_CLOSEDIR,
|
||||
UV_FS_MKSTEMP
|
||||
} uv_fs_type;
|
||||
|
||||
.. c:type:: uv_statfs_t
|
||||
@ -245,10 +246,14 @@ API
|
||||
|
||||
.. c:function:: int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb)
|
||||
|
||||
Equivalent to :man:`mkdtemp(3)`.
|
||||
Equivalent to :man:`mkdtemp(3)`. The result can be found as a null terminated string at `req->path`.
|
||||
|
||||
.. note::
|
||||
The result can be found as a null terminated string at `req->path`.
|
||||
.. c:function:: int uv_fs_mkstemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl, uv_fs_cb cb)
|
||||
|
||||
Equivalent to :man:`mkstemp(3)`. The created file path can be found as a null terminated string at `req->path`.
|
||||
The file descriptor can be found as an integer at `req->result`.
|
||||
|
||||
.. versionadded:: 1.34.0
|
||||
|
||||
.. c:function:: int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb)
|
||||
|
||||
|
||||
@ -1258,7 +1258,8 @@ typedef enum {
|
||||
UV_FS_OPENDIR,
|
||||
UV_FS_READDIR,
|
||||
UV_FS_CLOSEDIR,
|
||||
UV_FS_STATFS
|
||||
UV_FS_STATFS,
|
||||
UV_FS_MKSTEMP
|
||||
} uv_fs_type;
|
||||
|
||||
struct uv_dir_s {
|
||||
@ -1349,6 +1350,10 @@ UV_EXTERN int uv_fs_mkdtemp(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* tpl,
|
||||
uv_fs_cb cb);
|
||||
UV_EXTERN int uv_fs_mkstemp(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* tpl,
|
||||
uv_fs_cb cb);
|
||||
UV_EXTERN int uv_fs_rmdir(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* path,
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
#include "internal.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -258,6 +259,80 @@ static ssize_t uv__fs_mkdtemp(uv_fs_t* req) {
|
||||
}
|
||||
|
||||
|
||||
static int uv__fs_mkstemp(uv_fs_t* req) {
|
||||
int r;
|
||||
#ifdef O_CLOEXEC
|
||||
int (*mkostemp_function)(char*, int);
|
||||
static int no_cloexec_support;
|
||||
#endif
|
||||
static const char pattern[] = "XXXXXX";
|
||||
static const size_t pattern_size = sizeof(pattern) - 1;
|
||||
char* path;
|
||||
size_t path_length;
|
||||
|
||||
path = (char*) req->path;
|
||||
path_length = strlen(path);
|
||||
|
||||
/* EINVAL can be returned for 2 reasons:
|
||||
1. The template's last 6 characters were not XXXXXX
|
||||
2. open() didn't support O_CLOEXEC
|
||||
We want to avoid going to the fallback path in case
|
||||
of 1, so it's manually checked before. */
|
||||
if (path_length < pattern_size ||
|
||||
strcmp(path + path_length - pattern_size, pattern)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef O_CLOEXEC
|
||||
if (no_cloexec_support == 0) {
|
||||
*(int**)(&mkostemp_function) = dlsym(RTLD_DEFAULT, "mkostemp");
|
||||
|
||||
/* We don't care about errors, but we do want to clean them up.
|
||||
If there has been no error, then dlerror() will just return
|
||||
NULL. */
|
||||
dlerror();
|
||||
|
||||
if (mkostemp_function != NULL) {
|
||||
r = mkostemp_function(path, O_CLOEXEC);
|
||||
|
||||
if (r >= 0)
|
||||
return r;
|
||||
|
||||
/* If mkostemp() returns EINVAL, it means the kernel doesn't
|
||||
support O_CLOEXEC, so we just fallback to mkstemp() below. */
|
||||
if (errno != EINVAL)
|
||||
return r;
|
||||
|
||||
/* We set the static variable so that next calls don't even
|
||||
try to use mkostemp. */
|
||||
no_cloexec_support = 1;
|
||||
}
|
||||
}
|
||||
#endif /* O_CLOEXEC */
|
||||
|
||||
if (req->cb != NULL)
|
||||
uv_rwlock_rdlock(&req->loop->cloexec_lock);
|
||||
|
||||
r = mkstemp(path);
|
||||
|
||||
/* In case of failure `uv__cloexec` will leave error in `errno`,
|
||||
* so it is enough to just set `r` to `-1`.
|
||||
*/
|
||||
if (r >= 0 && uv__cloexec(r, 1) != 0) {
|
||||
r = uv__close(r);
|
||||
if (r != 0)
|
||||
abort();
|
||||
r = -1;
|
||||
}
|
||||
|
||||
if (req->cb != NULL)
|
||||
uv_rwlock_rdunlock(&req->loop->cloexec_lock);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t uv__fs_open(uv_fs_t* req) {
|
||||
#ifdef O_CLOEXEC
|
||||
return open(req->path, req->flags | O_CLOEXEC, req->mode);
|
||||
@ -1424,6 +1499,7 @@ static void uv__fs_work(struct uv__work* w) {
|
||||
X(LINK, link(req->path, req->new_path));
|
||||
X(MKDIR, mkdir(req->path, req->mode));
|
||||
X(MKDTEMP, uv__fs_mkdtemp(req));
|
||||
X(MKSTEMP, uv__fs_mkstemp(req));
|
||||
X(OPEN, uv__fs_open(req));
|
||||
X(READ, uv__fs_read(req));
|
||||
X(SCANDIR, uv__fs_scandir(req));
|
||||
@ -1648,6 +1724,18 @@ int uv_fs_mkdtemp(uv_loop_t* loop,
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_mkstemp(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* tpl,
|
||||
uv_fs_cb cb) {
|
||||
INIT(MKSTEMP);
|
||||
req->path = uv__strdup(tpl);
|
||||
if (req->path == NULL)
|
||||
return UV_ENOMEM;
|
||||
POST;
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_open(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* path,
|
||||
@ -1866,10 +1954,12 @@ void uv_fs_req_cleanup(uv_fs_t* req) {
|
||||
|
||||
/* Only necessary for asychronous requests, i.e., requests with a callback.
|
||||
* Synchronous ones don't copy their arguments and have req->path and
|
||||
* req->new_path pointing to user-owned memory. UV_FS_MKDTEMP is the
|
||||
* exception to the rule, it always allocates memory.
|
||||
* req->new_path pointing to user-owned memory. UV_FS_MKDTEMP and
|
||||
* UV_FS_MKSTEMP are the exception to the rule, they always allocate memory.
|
||||
*/
|
||||
if (req->path != NULL && (req->cb != NULL || req->fs_type == UV_FS_MKDTEMP))
|
||||
if (req->path != NULL &&
|
||||
(req->cb != NULL ||
|
||||
req->fs_type == UV_FS_MKDTEMP || req->fs_type == UV_FS_MKSTEMP))
|
||||
uv__free((void*) req->path); /* Memory is shared with req->new_path. */
|
||||
|
||||
req->path = NULL;
|
||||
|
||||
108
src/win/fs.c
108
src/win/fs.c
@ -1195,9 +1195,10 @@ void fs__mkdir(uv_fs_t* req) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef int (*uv__fs_mktemp_func)(uv_fs_t* req);
|
||||
|
||||
/* OpenBSD original: lib/libc/stdio/mktemp.c */
|
||||
void fs__mkdtemp(uv_fs_t* req) {
|
||||
void fs__mktemp(uv_fs_t* req, uv__fs_mktemp_func func) {
|
||||
static const WCHAR *tempchars =
|
||||
L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
static const size_t num_chars = 62;
|
||||
@ -1227,13 +1228,11 @@ void fs__mkdtemp(uv_fs_t* req) {
|
||||
v /= num_chars;
|
||||
}
|
||||
|
||||
if (_wmkdir(req->file.pathw) == 0) {
|
||||
len = strlen(req->path);
|
||||
wcstombs((char*) req->path + len - num_x, ep - num_x, num_x);
|
||||
SET_REQ_RESULT(req, 0);
|
||||
break;
|
||||
} else if (errno != EEXIST) {
|
||||
SET_REQ_RESULT(req, -1);
|
||||
if (func(req)) {
|
||||
if (req->result >= 0) {
|
||||
len = strlen(req->path);
|
||||
wcstombs((char*) req->path + len - num_x, ep - num_x, num_x);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (--tries);
|
||||
@ -1244,6 +1243,77 @@ void fs__mkdtemp(uv_fs_t* req) {
|
||||
}
|
||||
|
||||
|
||||
static int fs__mkdtemp_func(uv_fs_t* req) {
|
||||
if (_wmkdir(req->file.pathw) == 0) {
|
||||
SET_REQ_RESULT(req, 0);
|
||||
return 1;
|
||||
} else if (errno != EEXIST) {
|
||||
SET_REQ_RESULT(req, -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void fs__mkdtemp(uv_fs_t* req) {
|
||||
fs__mktemp(req, fs__mkdtemp_func);
|
||||
}
|
||||
|
||||
|
||||
static int fs__mkstemp_func(uv_fs_t* req) {
|
||||
HANDLE file;
|
||||
int fd;
|
||||
|
||||
file = CreateFileW(req->file.pathw,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
CREATE_NEW,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
|
||||
if (file == INVALID_HANDLE_VALUE) {
|
||||
DWORD error;
|
||||
error = GetLastError();
|
||||
|
||||
/* If the file exists, the main fs__mktemp() function
|
||||
will retry. If it's another error, we want to stop. */
|
||||
if (error != ERROR_FILE_EXISTS) {
|
||||
SET_REQ_WIN32_ERROR(req, error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fd = _open_osfhandle((intptr_t) file, 0);
|
||||
if (fd < 0) {
|
||||
/* The only known failure mode for _open_osfhandle() is EMFILE, in which
|
||||
* case GetLastError() will return zero. However we'll try to handle other
|
||||
* errors as well, should they ever occur.
|
||||
*/
|
||||
if (errno == EMFILE)
|
||||
SET_REQ_UV_ERROR(req, UV_EMFILE, ERROR_TOO_MANY_OPEN_FILES);
|
||||
else if (GetLastError() != ERROR_SUCCESS)
|
||||
SET_REQ_WIN32_ERROR(req, GetLastError());
|
||||
else
|
||||
SET_REQ_WIN32_ERROR(req, UV_UNKNOWN);
|
||||
CloseHandle(file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
SET_REQ_RESULT(req, fd);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
void fs__mkstemp(uv_fs_t* req) {
|
||||
fs__mktemp(req, fs__mkstemp_func);
|
||||
}
|
||||
|
||||
|
||||
void fs__scandir(uv_fs_t* req) {
|
||||
static const size_t dirents_initial_size = 32;
|
||||
|
||||
@ -2609,6 +2679,7 @@ static void uv__fs_work(struct uv__work* w) {
|
||||
XX(RMDIR, rmdir)
|
||||
XX(MKDIR, mkdir)
|
||||
XX(MKDTEMP, mkdtemp)
|
||||
XX(MKSTEMP, mkstemp)
|
||||
XX(RENAME, rename)
|
||||
XX(SCANDIR, scandir)
|
||||
XX(READDIR, readdir)
|
||||
@ -2785,8 +2856,10 @@ int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl,
|
||||
uv_fs_cb cb) {
|
||||
int uv_fs_mkdtemp(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* tpl,
|
||||
uv_fs_cb cb) {
|
||||
int err;
|
||||
|
||||
INIT(UV_FS_MKDTEMP);
|
||||
@ -2798,6 +2871,21 @@ int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl,
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_mkstemp(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const char* tpl,
|
||||
uv_fs_cb cb) {
|
||||
int err;
|
||||
|
||||
INIT(UV_FS_MKSTEMP);
|
||||
err = fs__capture_path(req, tpl, NULL, TRUE);
|
||||
if (err)
|
||||
return uv_translate_sys_error(err);
|
||||
|
||||
POST;
|
||||
}
|
||||
|
||||
|
||||
int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb) {
|
||||
int err;
|
||||
|
||||
|
||||
@ -73,6 +73,7 @@ static int write_cb_count;
|
||||
static int unlink_cb_count;
|
||||
static int mkdir_cb_count;
|
||||
static int mkdtemp_cb_count;
|
||||
static int mkstemp_cb_count;
|
||||
static int rmdir_cb_count;
|
||||
static int scandir_cb_count;
|
||||
static int stat_cb_count;
|
||||
@ -107,6 +108,9 @@ static uv_fs_t close_req;
|
||||
static uv_fs_t mkdir_req;
|
||||
static uv_fs_t mkdtemp_req1;
|
||||
static uv_fs_t mkdtemp_req2;
|
||||
static uv_fs_t mkstemp_req1;
|
||||
static uv_fs_t mkstemp_req2;
|
||||
static uv_fs_t mkstemp_req3;
|
||||
static uv_fs_t rmdir_req;
|
||||
static uv_fs_t scandir_req;
|
||||
static uv_fs_t stat_req;
|
||||
@ -538,6 +542,32 @@ static void mkdtemp_cb(uv_fs_t* req) {
|
||||
}
|
||||
|
||||
|
||||
static void check_mkstemp_result(uv_fs_t* req) {
|
||||
int r;
|
||||
|
||||
ASSERT(req->fs_type == UV_FS_MKSTEMP);
|
||||
ASSERT(req->result >= 0);
|
||||
ASSERT(req->path);
|
||||
ASSERT(strlen(req->path) == 16);
|
||||
ASSERT(memcmp(req->path, "test_file_", 10) == 0);
|
||||
ASSERT(memcmp(req->path + 10, "XXXXXX", 6) != 0);
|
||||
check_permission(req->path, 0600);
|
||||
|
||||
/* Check if req->path is actually a file */
|
||||
r = uv_fs_stat(NULL, &stat_req, req->path, NULL);
|
||||
ASSERT(r == 0);
|
||||
ASSERT(stat_req.statbuf.st_mode & S_IFREG);
|
||||
uv_fs_req_cleanup(&stat_req);
|
||||
}
|
||||
|
||||
|
||||
static void mkstemp_cb(uv_fs_t* req) {
|
||||
ASSERT(req == &mkstemp_req1);
|
||||
check_mkstemp_result(req);
|
||||
mkstemp_cb_count++;
|
||||
}
|
||||
|
||||
|
||||
static void rmdir_cb(uv_fs_t* req) {
|
||||
ASSERT(req == &rmdir_req);
|
||||
ASSERT(req->fs_type == UV_FS_RMDIR);
|
||||
@ -1208,6 +1238,69 @@ TEST_IMPL(fs_mkdtemp) {
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(fs_mkstemp) {
|
||||
int r;
|
||||
int fd;
|
||||
const char path_template[] = "test_file_XXXXXX";
|
||||
uv_fs_t req;
|
||||
|
||||
loop = uv_default_loop();
|
||||
|
||||
r = uv_fs_mkstemp(loop, &mkstemp_req1, path_template, mkstemp_cb);
|
||||
ASSERT(r == 0);
|
||||
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
ASSERT(mkstemp_cb_count == 1);
|
||||
|
||||
/* sync mkstemp */
|
||||
r = uv_fs_mkstemp(NULL, &mkstemp_req2, path_template, NULL);
|
||||
ASSERT(r >= 0);
|
||||
check_mkstemp_result(&mkstemp_req2);
|
||||
|
||||
/* mkstemp return different values on subsequent calls */
|
||||
ASSERT(strcmp(mkstemp_req1.path, mkstemp_req2.path) != 0);
|
||||
|
||||
/* invalid template returns EINVAL */
|
||||
ASSERT(uv_fs_mkstemp(NULL, &mkstemp_req3, "test_file", NULL) == UV_EINVAL);
|
||||
|
||||
/* We can write to the opened file */
|
||||
iov = uv_buf_init(test_buf, sizeof(test_buf));
|
||||
r = uv_fs_write(NULL, &req, mkstemp_req1.result, &iov, 1, -1, NULL);
|
||||
ASSERT(r == sizeof(test_buf));
|
||||
ASSERT(req.result == sizeof(test_buf));
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
/* Cleanup */
|
||||
uv_fs_close(NULL, &req, mkstemp_req1.result, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
uv_fs_close(NULL, &req, mkstemp_req2.result, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
fd = uv_fs_open(NULL, &req, mkstemp_req1.path , O_RDONLY, 0, NULL);
|
||||
ASSERT(fd >= 0);
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
iov = uv_buf_init(buf, sizeof(buf));
|
||||
r = uv_fs_read(NULL, &req, fd, &iov, 1, -1, NULL);
|
||||
ASSERT(r >= 0);
|
||||
ASSERT(req.result >= 0);
|
||||
ASSERT(strcmp(buf, test_buf) == 0);
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
uv_fs_close(NULL, &req, fd, NULL);
|
||||
uv_fs_req_cleanup(&req);
|
||||
|
||||
unlink(mkstemp_req1.path);
|
||||
unlink(mkstemp_req2.path);
|
||||
uv_fs_req_cleanup(&mkstemp_req1);
|
||||
uv_fs_req_cleanup(&mkstemp_req2);
|
||||
|
||||
MAKE_VALGRIND_HAPPY();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
TEST_IMPL(fs_fstat) {
|
||||
int r;
|
||||
uv_fs_t req;
|
||||
@ -3784,6 +3877,9 @@ TEST_IMPL(fs_null_req) {
|
||||
r = uv_fs_mkdtemp(NULL, NULL, NULL, NULL);
|
||||
ASSERT(r == UV_EINVAL);
|
||||
|
||||
r = uv_fs_mkstemp(NULL, NULL, NULL, NULL);
|
||||
ASSERT(r == UV_EINVAL);
|
||||
|
||||
r = uv_fs_rmdir(NULL, NULL, NULL, NULL);
|
||||
ASSERT(r == UV_EINVAL);
|
||||
|
||||
|
||||
@ -310,6 +310,7 @@ TEST_DECLARE (fs_async_dir)
|
||||
TEST_DECLARE (fs_async_sendfile)
|
||||
TEST_DECLARE (fs_async_sendfile_nodata)
|
||||
TEST_DECLARE (fs_mkdtemp)
|
||||
TEST_DECLARE (fs_mkstemp)
|
||||
TEST_DECLARE (fs_fstat)
|
||||
TEST_DECLARE (fs_access)
|
||||
TEST_DECLARE (fs_chmod)
|
||||
@ -920,6 +921,7 @@ TASK_LIST_START
|
||||
TEST_ENTRY (fs_async_sendfile)
|
||||
TEST_ENTRY (fs_async_sendfile_nodata)
|
||||
TEST_ENTRY (fs_mkdtemp)
|
||||
TEST_ENTRY (fs_mkstemp)
|
||||
TEST_ENTRY (fs_fstat)
|
||||
TEST_ENTRY (fs_access)
|
||||
TEST_ENTRY (fs_chmod)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user