#include "fuse_i.h"
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/file.h>
#include <linux/fs.h>
static inline bool fuse_is_io_cache_wait(struct fuse_inode *fi)
{
return READ_ONCE(fi->iocachectr) < 0 && !fuse_inode_backing(fi);
}
int fuse_file_cached_io_open(struct inode *inode, struct fuse_file *ff)
{
struct fuse_inode *fi = get_fuse_inode(inode);
if (!ff->args)
return 0;
spin_lock(&fi->lock);
while (fuse_is_io_cache_wait(fi)) {
set_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
spin_unlock(&fi->lock);
wait_event(fi->direct_io_waitq, !fuse_is_io_cache_wait(fi));
spin_lock(&fi->lock);
}
if (fuse_inode_backing(fi)) {
clear_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
spin_unlock(&fi->lock);
return -ETXTBSY;
}
WARN_ON(ff->iomode == IOM_UNCACHED);
if (ff->iomode == IOM_NONE) {
ff->iomode = IOM_CACHED;
if (fi->iocachectr == 0)
set_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
fi->iocachectr++;
}
spin_unlock(&fi->lock);
return 0;
}
static void fuse_file_cached_io_release(struct fuse_file *ff,
struct fuse_inode *fi)
{
spin_lock(&fi->lock);
WARN_ON(fi->iocachectr <= 0);
WARN_ON(ff->iomode != IOM_CACHED);
ff->iomode = IOM_NONE;
fi->iocachectr--;
if (fi->iocachectr == 0)
clear_bit(FUSE_I_CACHE_IO_MODE, &fi->state);
spin_unlock(&fi->lock);
}
int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb)
{
struct fuse_backing *oldfb;
int err = 0;
spin_lock(&fi->lock);
oldfb = fuse_inode_backing(fi);
if (fb && oldfb && oldfb != fb) {
err = -EBUSY;
goto unlock;
}
if (fi->iocachectr > 0) {
err = -ETXTBSY;
goto unlock;
}
fi->iocachectr--;
if (fb && !oldfb) {
oldfb = fuse_inode_backing_set(fi, fb);
WARN_ON_ONCE(oldfb != NULL);
} else {
fuse_backing_put(fb);
}
unlock:
spin_unlock(&fi->lock);
return err;
}
static int fuse_file_uncached_io_open(struct inode *inode,
struct fuse_file *ff,
struct fuse_backing *fb)
{
struct fuse_inode *fi = get_fuse_inode(inode);
int err;
err = fuse_inode_uncached_io_start(fi, fb);
if (err)
return err;
WARN_ON(ff->iomode != IOM_NONE);
ff->iomode = IOM_UNCACHED;
return 0;
}
void fuse_inode_uncached_io_end(struct fuse_inode *fi)
{
struct fuse_backing *oldfb = NULL;
spin_lock(&fi->lock);
WARN_ON(fi->iocachectr >= 0);
fi->iocachectr++;
if (!fi->iocachectr) {
wake_up(&fi->direct_io_waitq);
oldfb = fuse_inode_backing_set(fi, NULL);
}
spin_unlock(&fi->lock);
if (oldfb)
fuse_backing_put(oldfb);
}
static void fuse_file_uncached_io_release(struct fuse_file *ff,
struct fuse_inode *fi)
{
WARN_ON(ff->iomode != IOM_UNCACHED);
ff->iomode = IOM_NONE;
fuse_inode_uncached_io_end(fi);
}
#define FOPEN_PASSTHROUGH_MASK \
(FOPEN_PASSTHROUGH | FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES | \
FOPEN_NOFLUSH)
static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
{
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_backing *fb;
int err;
if (!IS_ENABLED(CONFIG_FUSE_PASSTHROUGH) || !fc->passthrough ||
(ff->open_flags & ~FOPEN_PASSTHROUGH_MASK))
return -EINVAL;
fb = fuse_passthrough_open(file, ff->args->open_outarg.backing_id);
if (IS_ERR(fb))
return PTR_ERR(fb);
err = fuse_file_uncached_io_open(inode, ff, fb);
if (!err)
return 0;
fuse_passthrough_release(ff, fb);
fuse_backing_put(fb);
return err;
}
int fuse_file_io_open(struct file *file, struct inode *inode)
{
struct fuse_file *ff = file->private_data;
struct fuse_inode *fi = get_fuse_inode(inode);
int err;
if (FUSE_IS_DAX(inode) || !ff->args)
return 0;
err = -EINVAL;
if (fuse_inode_backing(fi) && !(ff->open_flags & FOPEN_PASSTHROUGH))
goto fail;
if (!(ff->open_flags & FOPEN_DIRECT_IO))
ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES;
if ((ff->open_flags & FOPEN_DIRECT_IO) &&
!(ff->open_flags & FOPEN_PASSTHROUGH))
return 0;
if (ff->open_flags & FOPEN_PASSTHROUGH)
err = fuse_file_passthrough_open(inode, file);
else
err = fuse_file_cached_io_open(inode, ff);
if (err)
goto fail;
return 0;
fail:
pr_debug("failed to open file in requested io mode (open_flags=0x%x, err=%i).\n",
ff->open_flags, err);
return -EIO;
}
void fuse_file_io_release(struct fuse_file *ff, struct inode *inode)
{
struct fuse_inode *fi = get_fuse_inode(inode);
switch (ff->iomode) {
case IOM_NONE:
break;
case IOM_UNCACHED:
fuse_file_uncached_io_release(ff, fi);
break;
case IOM_CACHED:
fuse_file_cached_io_release(ff, fi);
break;
}
}