mirror of
https://github.com/libfuse/libfuse.git
synced 2026-07-07 14:47:41 +08:00
support FUSE_TMPFILE in the low level API
Note that name hashes and using paths as parameters makes it very hard to support anonymous files in the high level API. Known Issues: - tests have to bail out when O_TMPFILE is not supported. This will always be the case with high level passthrough implementations. - test_create_and_link_tmpfile has to be skipped due to unidentified problems with github runner
This commit is contained in:
committed by
Bernd Schubert
parent
8b34ed0368
commit
4ec109d1c4
@@ -914,6 +914,86 @@ static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name,
|
||||
fuse_reply_create(req, &e, fi);
|
||||
}
|
||||
|
||||
static Inode *create_new_inode(int fd, fuse_entry_param *e)
|
||||
{
|
||||
memset(e, 0, sizeof(*e));
|
||||
e->attr_timeout = fs.timeout;
|
||||
e->entry_timeout = fs.timeout;
|
||||
|
||||
auto res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
||||
if (res == -1) {
|
||||
if (fs.debug)
|
||||
cerr << "DEBUG: lookup(): fstatat failed" << endl;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SrcId id {e->attr.st_ino, e->attr.st_dev};
|
||||
unique_lock<mutex> fs_lock {fs.mutex};
|
||||
Inode* p_inode;
|
||||
try {
|
||||
p_inode = &fs.inodes[id];
|
||||
} catch (std::bad_alloc&) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
e->ino = reinterpret_cast<fuse_ino_t>(p_inode);
|
||||
e->generation = p_inode->generation;
|
||||
|
||||
lock_guard<mutex> g {p_inode->m};
|
||||
p_inode->src_ino = e->attr.st_ino;
|
||||
p_inode->src_dev = e->attr.st_dev;
|
||||
|
||||
p_inode->nlookup++;
|
||||
if (fs.debug)
|
||||
cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " "
|
||||
<< "inode " << p_inode->src_ino
|
||||
<< " count " << p_inode->nlookup << endl;
|
||||
|
||||
p_inode->fd = fd;
|
||||
fs_lock.unlock();
|
||||
|
||||
if (fs.debug)
|
||||
cerr << "DEBUG: lookup(): created userspace inode " << e->attr.st_ino
|
||||
<< "; fd = " << p_inode->fd << endl;
|
||||
return p_inode;
|
||||
}
|
||||
|
||||
static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent,
|
||||
mode_t mode, fuse_file_info *fi) {
|
||||
Inode& parent_inode = get_inode(parent);
|
||||
|
||||
auto fd = openat(parent_inode.fd, ".",
|
||||
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
|
||||
if (fd == -1) {
|
||||
auto err = errno;
|
||||
if (err == ENFILE || err == EMFILE)
|
||||
cerr << "ERROR: Reached maximum number of file descriptors." << endl;
|
||||
fuse_reply_err(req, err);
|
||||
return;
|
||||
}
|
||||
|
||||
fi->fh = fd;
|
||||
fuse_entry_param e;
|
||||
|
||||
Inode *inode = create_new_inode(dup(fd), &e);
|
||||
if (inode == NULL) {
|
||||
auto err = errno;
|
||||
cerr << "ERROR: could not create new inode." << endl;
|
||||
close(fd);
|
||||
fuse_reply_err(req, err);
|
||||
return;
|
||||
}
|
||||
|
||||
lock_guard<mutex> g {inode->m};
|
||||
|
||||
sfs_create_open_flags(fi);
|
||||
|
||||
if (fs.passthrough)
|
||||
do_passthrough_open(req, e.ino, fd, fi);
|
||||
|
||||
fuse_reply_create(req, &e, fi);
|
||||
}
|
||||
|
||||
|
||||
static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
|
||||
fuse_file_info *fi) {
|
||||
@@ -1237,6 +1317,7 @@ static void assign_operations(fuse_lowlevel_ops &sfs_oper) {
|
||||
sfs_oper.releasedir = sfs_releasedir;
|
||||
sfs_oper.fsyncdir = sfs_fsyncdir;
|
||||
sfs_oper.create = sfs_create;
|
||||
sfs_oper.tmpfile = sfs_tmpfile;
|
||||
sfs_oper.open = sfs_open;
|
||||
sfs_oper.release = sfs_release;
|
||||
sfs_oper.flush = sfs_flush;
|
||||
|
||||
+87
-19
@@ -308,6 +308,55 @@ static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
|
||||
{
|
||||
struct lo_inode *inode = NULL;
|
||||
struct lo_inode *prev, *next;
|
||||
|
||||
inode = calloc(1, sizeof(struct lo_inode));
|
||||
if (!inode)
|
||||
return NULL;
|
||||
|
||||
inode->refcount = 1;
|
||||
inode->fd = fd;
|
||||
inode->ino = e->attr.st_ino;
|
||||
inode->dev = e->attr.st_dev;
|
||||
|
||||
pthread_mutex_lock(&lo->mutex);
|
||||
prev = &lo->root;
|
||||
next = prev->next;
|
||||
next->prev = inode;
|
||||
inode->next = next;
|
||||
inode->prev = prev;
|
||||
prev->next = inode;
|
||||
pthread_mutex_unlock(&lo->mutex);
|
||||
return inode;
|
||||
}
|
||||
|
||||
static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
|
||||
{
|
||||
int res;
|
||||
struct lo_data *lo = lo_data(req);
|
||||
|
||||
memset(e, 0, sizeof(*e));
|
||||
e->attr_timeout = lo->timeout;
|
||||
e->entry_timeout = lo->timeout;
|
||||
|
||||
res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
|
||||
if (res == -1)
|
||||
return errno;
|
||||
|
||||
e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
|
||||
|
||||
if (lo_debug(req))
|
||||
fuse_log(FUSE_LOG_DEBUG, " %lli/%lli -> %lli\n",
|
||||
(unsigned long long) parent, fd, (unsigned long long) e->ino);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
|
||||
struct fuse_entry_param *e)
|
||||
{
|
||||
@@ -334,26 +383,9 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
|
||||
close(newfd);
|
||||
newfd = -1;
|
||||
} else {
|
||||
struct lo_inode *prev, *next;
|
||||
|
||||
saverr = ENOMEM;
|
||||
inode = calloc(1, sizeof(struct lo_inode));
|
||||
inode = create_new_inode(newfd, e, lo);
|
||||
if (!inode)
|
||||
goto out_err;
|
||||
|
||||
inode->refcount = 1;
|
||||
inode->fd = newfd;
|
||||
inode->ino = e->attr.st_ino;
|
||||
inode->dev = e->attr.st_dev;
|
||||
|
||||
pthread_mutex_lock(&lo->mutex);
|
||||
prev = &lo->root;
|
||||
next = prev->next;
|
||||
next->prev = inode;
|
||||
inode->next = next;
|
||||
inode->prev = prev;
|
||||
prev->next = inode;
|
||||
pthread_mutex_unlock(&lo->mutex);
|
||||
}
|
||||
e->ino = (uintptr_t) inode;
|
||||
|
||||
@@ -754,7 +786,7 @@ static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info
|
||||
fuse_reply_err(req, 0);
|
||||
}
|
||||
|
||||
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
|
||||
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
|
||||
mode_t mode, struct fuse_file_info *fi)
|
||||
{
|
||||
int fd;
|
||||
@@ -762,6 +794,41 @@ static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
|
||||
struct fuse_entry_param e;
|
||||
int err;
|
||||
|
||||
if (lo_debug(req))
|
||||
fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
|
||||
parent);
|
||||
|
||||
fd = openat(lo_fd(req, parent), ".",
|
||||
(fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
|
||||
if (fd == -1)
|
||||
return (void) fuse_reply_err(req, errno);
|
||||
|
||||
fi->fh = fd;
|
||||
if (lo->cache == CACHE_NEVER)
|
||||
fi->direct_io = 1;
|
||||
else if (lo->cache == CACHE_ALWAYS)
|
||||
fi->keep_cache = 1;
|
||||
|
||||
/* parallel_direct_writes feature depends on direct_io features.
|
||||
To make parallel_direct_writes valid, need set fi->direct_io
|
||||
in current function. */
|
||||
fi->parallel_direct_writes = 1;
|
||||
|
||||
err = fill_entry_param_new_inode(req, parent, fd, &e);
|
||||
if (err)
|
||||
fuse_reply_err(req, err);
|
||||
else
|
||||
fuse_reply_create(req, &e, fi);
|
||||
}
|
||||
|
||||
static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
|
||||
mode_t mode, struct fuse_file_info *fi)
|
||||
{
|
||||
int fd;
|
||||
struct lo_data *lo = lo_data(req);
|
||||
struct fuse_entry_param e;
|
||||
int err;
|
||||
|
||||
if (lo_debug(req))
|
||||
fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
|
||||
parent, name);
|
||||
@@ -1178,6 +1245,7 @@ static const struct fuse_lowlevel_ops lo_oper = {
|
||||
.releasedir = lo_releasedir,
|
||||
.fsyncdir = lo_fsyncdir,
|
||||
.create = lo_create,
|
||||
.tmpfile = lo_tmpfile,
|
||||
.open = lo_open,
|
||||
.release = lo_release,
|
||||
.flush = lo_flush,
|
||||
|
||||
@@ -1302,6 +1302,29 @@ struct fuse_lowlevel_ops {
|
||||
*/
|
||||
void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
|
||||
struct fuse_file_info *fi);
|
||||
|
||||
|
||||
/**
|
||||
* Create a tempfile
|
||||
*
|
||||
* Tempfile means an anonymous file. It can be made into a normal file later
|
||||
* by using linkat or such.
|
||||
*
|
||||
* If this is answered with an error ENOSYS this is treated by the kernel as
|
||||
* a permanent failure and it will disable the feature and not ask again.
|
||||
*
|
||||
* Valid replies:
|
||||
* fuse_reply_create
|
||||
* fuse_reply_err
|
||||
*
|
||||
* @param req request handle
|
||||
* @param parent inode number of the parent directory
|
||||
* @param mode file type and mode with which to create the new file
|
||||
* @param fi file information
|
||||
*/
|
||||
void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
|
||||
mode_t mode, struct fuse_file_info *fi);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1361,6 +1361,24 @@ static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
fuse_reply_err(req, ENOSYS);
|
||||
}
|
||||
|
||||
static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
{
|
||||
struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
|
||||
|
||||
if (req->se->op.tmpfile) {
|
||||
struct fuse_file_info fi;
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.flags = arg->flags;
|
||||
|
||||
if (req->se->conn.proto_minor >= 12)
|
||||
req->ctx.umask = arg->umask;
|
||||
|
||||
req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
|
||||
} else
|
||||
fuse_reply_err(req, ENOSYS);
|
||||
}
|
||||
|
||||
static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
{
|
||||
struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
|
||||
@@ -2678,6 +2696,7 @@ static struct {
|
||||
[FUSE_SETLKW] = { do_setlkw, "SETLKW" },
|
||||
[FUSE_ACCESS] = { do_access, "ACCESS" },
|
||||
[FUSE_CREATE] = { do_create, "CREATE" },
|
||||
[FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
|
||||
[FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
|
||||
[FUSE_BMAP] = { do_bmap, "BMAP" },
|
||||
[FUSE_IOCTL] = { do_ioctl, "IOCTL" },
|
||||
|
||||
@@ -38,6 +38,7 @@ static struct {
|
||||
[FUSE_SETLKW] = { "SETLKW" },
|
||||
[FUSE_ACCESS] = { "ACCESS" },
|
||||
[FUSE_CREATE] = { "CREATE" },
|
||||
[FUSE_TMPFILE] = { "TMPFILE" },
|
||||
[FUSE_INTERRUPT] = { "INTERRUPT" },
|
||||
[FUSE_BMAP] = { "BMAP" },
|
||||
[FUSE_DESTROY] = { "DESTROY" },
|
||||
|
||||
@@ -1957,6 +1957,104 @@ static int do_test_create_ro_dir(int flags, const char *flags_str)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* this tests open with O_TMPFILE
|
||||
note that this will only work with the fuse low level api
|
||||
you will get ENOTSUP with the high level api */
|
||||
static int test_create_tmpfile(void)
|
||||
{
|
||||
rmdir(testdir);
|
||||
int res = mkdir(testdir, 0777);
|
||||
if (res)
|
||||
return -1;
|
||||
|
||||
start_test("create tmpfile");
|
||||
|
||||
int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if(fd == -1) {
|
||||
if (errno == ENOTSUP) {
|
||||
/* don't bother if we're working on an old kernel
|
||||
or on the high level API */
|
||||
return 0;
|
||||
}
|
||||
|
||||
PERROR("open O_TMPFILE | O_RDWR");
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
|
||||
if(fd == -1){
|
||||
PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
|
||||
return -1;
|
||||
};
|
||||
close(fd);
|
||||
|
||||
fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
|
||||
if (fd != -1) {
|
||||
ERROR("open with O_TMPFILE | O_RDONLY succeeded");
|
||||
return -1;
|
||||
}
|
||||
|
||||
success();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_create_and_link_tmpfile(void)
|
||||
{
|
||||
/* skip this test for now since the github runner will fail in the linkat call below */
|
||||
return 0;
|
||||
|
||||
rmdir(testdir);
|
||||
unlink(testfile);
|
||||
|
||||
int res = mkdir(testdir, 0777);
|
||||
if (res)
|
||||
return -1;
|
||||
|
||||
start_test("create and link tmpfile");
|
||||
|
||||
int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
|
||||
if(fd == -1) {
|
||||
if (errno == ENOTSUP) {
|
||||
/* don't bother if we're working on an old kernel
|
||||
or on the high level API */
|
||||
return 0;
|
||||
}
|
||||
PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
|
||||
ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
|
||||
if(fd == -1) {
|
||||
PERROR("open O_TMPFILE");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (check_nonexist(testfile)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
|
||||
PERROR("linkat tempfile");
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (check_nlink(testfile, 1)) {
|
||||
return -1;
|
||||
}
|
||||
unlink(testfile);
|
||||
|
||||
success();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int err = 0;
|
||||
@@ -2085,6 +2183,8 @@ int main(int argc, char *argv[])
|
||||
err += test_create_ro_dir(O_CREAT | O_WRONLY);
|
||||
err += test_create_ro_dir(O_CREAT | O_TRUNC);
|
||||
err += test_copy_file_range();
|
||||
err += test_create_tmpfile();
|
||||
err += test_create_and_link_tmpfile();
|
||||
|
||||
unlink(testfile2);
|
||||
unlink(testsock);
|
||||
|
||||
Reference in New Issue
Block a user