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:
Horst Birthelmer
2024-11-20 16:14:43 +01:00
committed by Bernd Schubert
parent 8b34ed0368
commit 4ec109d1c4
6 changed files with 311 additions and 19 deletions
+81
View File
@@ -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;
+86 -18
View File
@@ -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,6 +786,41 @@ static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info
fuse_reply_err(req, 0);
}
static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
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_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)
{
@@ -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,
+23
View File
@@ -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);
};
/**
+19
View File
@@ -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" },
+1
View File
@@ -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" },
+100
View File
@@ -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);