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:
Saúl Ibarra Corretgé 2019-11-29 10:05:45 +01:00
parent 0a4e0b9d30
commit 5500253cac
6 changed files with 304 additions and 18 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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)