blob: 9324262adecd0083b9380023c8b15df4e843cd1c [file] [log] [blame]
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2008 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU GPL.
See the file COPYING.
*/
#include "fuse_i.h"
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/module.h>
#include <linux/compat.h>
#include <linux/swap.h>
#include <linux/falloc.h>
#include <linux/uio.h>
static int fuse_send_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
int opcode, struct fuse_open_out *outargp)
{
struct fuse_open_in inarg;
FUSE_ARGS(args);
memset(&inarg, 0, sizeof(inarg));
inarg.flags = file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY);
args.in.h.opcode = opcode;
args.in.h.nodeid = nodeid;
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
args.out.numargs = 1;
args.out.args[0].size = sizeof(*outargp);
args.out.args[0].value = outargp;
return fuse2_simple_request(fc, &args);
}
struct fuse_file *fuse2_file_alloc(struct fuse_conn *fc)
{
struct fuse_file *ff;
ff = kzalloc(sizeof(struct fuse_file), GFP_KERNEL);
if (unlikely(!ff))
return NULL;
ff->fc = fc;
ff->kh = atomic64_inc_return(&fc->khctr);
return ff;
}
void fuse2_file_free(struct fuse_file *ff)
{
kfree(ff);
}
static int fuse_do_open(struct fuse_conn *fc, u64 nodeid, struct file *file,
bool isdir)
{
struct fuse_file *ff;
int opcode = isdir ? FUSE_OPENDIR : FUSE_OPEN;
struct fuse_open_out outarg;
int err;
ff = fuse2_file_alloc(fc);
if (!ff)
return -ENOMEM;
err = fuse_send_open(fc, nodeid, file, opcode, &outarg);
if (err) {
fuse2_file_free(ff);
return err;
}
ff->open_flags = outarg.open_flags;
ff->fh = outarg.fh;
ff->nodeid = nodeid;
file->private_data = ff;
return 0;
}
void fuse2_finish_open(struct inode *inode, struct file *file)
{
struct fuse_inode *fi = get_fuse_inode(inode);
if (file->f_flags & O_TRUNC) {
spin_lock(&fi->lock);
i_size_write(inode, 0);
spin_unlock(&fi->lock);
}
}
int fuse2_open_common(struct inode *inode, struct file *file, bool isdir)
{
struct fuse_conn *fc = get_fuse_conn(inode);
int err;
err = generic_file_open(inode, file);
if (err)
return err;
err = fuse_do_open(fc, get_node_id(inode), file, isdir);
if (!err)
fuse2_finish_open(inode, file);
return err;
}
void fuse2_release_common(struct fuse_conn *fc, struct fuse_file *ff,
int flags, fl_owner_t id, bool isdir)
{
struct fuse_release_in inarg;
FUSE_ARGS(args);
memset(&inarg, 0, sizeof(inarg));
inarg.fh = ff->fh;
inarg.flags = flags;
args.force = true;
args.in.h.opcode = isdir ? FUSE_RELEASEDIR : FUSE_RELEASE;
args.in.h.nodeid = ff->nodeid;
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
if (ff->flock) {
inarg.release_flags |= FUSE_RELEASE_FLOCK_UNLOCK;
inarg.lock_owner = fuse2_lock_owner_id(ff->fc, id);
}
fuse2_simple_request(fc, &args);
kfree(ff);
}
static int fuse_open(struct inode *inode, struct file *file)
{
return fuse2_open_common(inode, file, false);
}
static int fuse_release(struct inode *inode, struct file *file)
{
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_file *ff = file->private_data;
fuse2_release_common(fc, ff, file->f_flags, (fl_owner_t) file, false);
/* return value is ignored by VFS */
return 0;
}
/*
* Scramble the ID space with XTEA, so that the value of the files_struct
* pointer is not exposed to userspace.
*/
u64 fuse2_lock_owner_id(struct fuse_conn *fc, fl_owner_t id)
{
u32 *k = fc->scramble_key;
u64 v = (unsigned long) id;
u32 v0 = v;
u32 v1 = v >> 32;
u32 sum = 0;
int i;
for (i = 0; i < 32; i++) {
v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]);
sum += 0x9E3779B9;
v1 += ((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum>>11 & 3]);
}
return (u64) v0 + ((u64) v1 << 32);
}
static int fuse_flush(struct file *file, fl_owner_t id)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_file *ff = file->private_data;
struct fuse_flush_in inarg;
FUSE_ARGS(args);
int err;
if (is_bad_inode(inode))
return -EIO;
if (fc->no_flush)
return 0;
err = filemap_check_errors(file->f_mapping);
if (err)
return err;
memset(&inarg, 0, sizeof(inarg));
inarg.fh = ff->fh;
inarg.lock_owner = fuse2_lock_owner_id(fc, id);
args.force = true;
args.in.h.opcode = FUSE_FLUSH;
args.in.h.nodeid = get_node_id(inode);
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
err = fuse2_simple_request(fc, &args);
if (err == -ENOSYS) {
fc->no_flush = 1;
err = 0;
}
return err;
}
int fuse2_fsync_common(struct file *file, loff_t start, loff_t end,
int datasync, int opcode)
{
struct inode *inode = file->f_mapping->host;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_file *ff = file->private_data;
FUSE_ARGS(args);
struct fuse_fsync_in inarg;
memset(&inarg, 0, sizeof(inarg));
inarg.fh = ff->fh;
inarg.fsync_flags = datasync ? 1 : 0;
args.in.h.opcode = opcode;
args.in.h.nodeid = get_node_id(inode);
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
return fuse2_simple_request(fc, &args);
}
static int fuse_fsync(struct file *file, loff_t start, loff_t end,
int datasync)
{
struct inode *inode = file->f_mapping->host;
struct fuse_conn *fc = get_fuse_conn(inode);
int err;
if (is_bad_inode(inode))
return -EIO;
if (fc->no_fsync)
return 0;
inode_lock(inode);
err = fuse2_fsync_common(file, start, end, datasync, FUSE_FSYNC);
if (err == -ENOSYS) {
fc->no_fsync = 1;
err = 0;
}
inode_unlock(inode);
return err;
}
void fuse2_read_fill(struct fuse_req *req, struct fuse_read_in **p,
struct file *file, loff_t pos, size_t count, int opcode)
{
struct fuse_file *ff = file->private_data;
struct fuse_read_in *inarg;
req->inline_inlen = sizeof(struct fuse_in_header) + sizeof(*inarg);
BUILD_BUG_ON(req->inline_inlen > FUSE_REQ_INLINE_DATA);
req->mand_outlen = req->inline_outlen = sizeof(struct fuse_out_header);
req->max_outlen = req->mand_outlen + count;
inarg = (void *) req->inlinedata + sizeof(struct fuse_in_header);
inarg->fh = ff->fh;
inarg->offset = pos;
inarg->size = count;
inarg->flags = file->f_flags;
if (p)
*p = inarg;
req->inh.opcode = opcode;
req->inh.nodeid = ff->nodeid;
req->inh.len = req->inline_inlen;
}
static ssize_t fuse_send_read(struct fuse_req *req, struct kiocb *iocb,
loff_t pos, size_t count)
{
struct file *file = iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = ff->fc;
ssize_t res;
fuse2_read_fill(req, NULL, iocb->ki_filp, pos, count, FUSE_READ);
res = fuse2_request_send(fc, req);
return res ? res : req->outh.len - req->mand_outlen;
}
static int fuse_do_readpage(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_req *req;
loff_t pos = page_offset(page);
size_t count = PAGE_SIZE;
int err;
req = fuse2_get_req(fc, 1);
if (IS_ERR(req))
return PTR_ERR(req);
req->num_pages = 1;
get_page(req->pages[0] = page);
req->page_descs[0].length = count;
__set_bit(FR_ZEROTAIL, &req->flags);
fuse2_read_fill(req, NULL, file, pos, count, FUSE_READ);
err = fuse2_request_send(fc, req);
if (!err)
SetPageUptodate(page);
fuse2_put_request(req);
return err;
}
static int fuse_readpage(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
int err;
err = -EIO;
if (is_bad_inode(inode))
goto out;
err = fuse_do_readpage(file, page);
out:
unlock_page(page);
return err;
}
static ssize_t fuse_send_write(struct fuse_req *req, struct kiocb *iocb,
loff_t pos, size_t count)
{
struct file *file = iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = ff->fc;
struct fuse_write_in *inarg;
struct fuse_write_out *outarg;
ssize_t res;
req->inline_inlen = sizeof(struct fuse_in_header) + sizeof(*inarg);
BUILD_BUG_ON(req->inline_inlen > FUSE_REQ_INLINE_DATA);
req->inline_outlen = sizeof(struct fuse_out_header) + sizeof(*outarg);
BUILD_BUG_ON(req->inline_outlen > FUSE_REQ_INLINE_DATA);
req->max_outlen = req->mand_outlen = req->inline_outlen;
inarg = (void *) req->inlinedata + sizeof(struct fuse_in_header);
inarg->fh = ff->fh;
inarg->offset = pos;
inarg->size = count;
inarg->flags = file->f_flags;
if (iocb->ki_flags & IOCB_DSYNC)
inarg->flags |= O_DSYNC;
if (iocb->ki_flags & IOCB_SYNC)
inarg->flags |= O_SYNC;
if (!capable(CAP_FSETID))
inarg->write_flags |= FUSE_WRITE_KILL_PRIV;
req->inh.opcode = FUSE_WRITE;
req->inh.nodeid = ff->nodeid;
req->inh.len = req->inline_inlen + count;
res = fuse2_request_send(fc, req);
outarg = (void *) req->inlinedata + sizeof(struct fuse_out_header);
return res ? res : outarg->size;
}
static void fuse_write_update_size(struct inode *inode, loff_t pos)
{
struct fuse_inode *fi = get_fuse_inode(inode);
spin_lock(&fi->lock);
if (pos > inode->i_size)
i_size_write(inode, pos);
spin_unlock(&fi->lock);
}
static inline void fuse_page_descs_length_init(struct fuse_req *req,
unsigned index, unsigned nr_pages)
{
int i;
for (i = index; i < index + nr_pages; i++)
req->page_descs[i].length = PAGE_SIZE -
req->page_descs[i].offset;
}
static int fuse_get_user_pages(struct fuse_req *req, struct iov_iter *ii,
size_t *nbytesp, int write)
{
size_t nbytes = 0; /* # bytes already packed in req */
ssize_t ret = 0;
while (nbytes < *nbytesp && req->num_pages < req->max_pages) {
unsigned npages;
size_t start;
ret = iov_iter_get_pages(ii, &req->pages[req->num_pages],
*nbytesp - nbytes,
req->max_pages - req->num_pages,
&start);
if (ret < 0)
break;
iov_iter_advance(ii, ret);
nbytes += ret;
ret += start;
npages = (ret + PAGE_SIZE - 1) / PAGE_SIZE;
req->page_descs[req->num_pages].offset = start;
fuse_page_descs_length_init(req, req->num_pages, npages);
req->num_pages += npages;
req->page_descs[req->num_pages - 1].length -=
(PAGE_SIZE - ret) & (PAGE_SIZE - 1);
}
*nbytesp = nbytes;
return ret < 0 ? ret : 0;
}
/* If set, it is WRITE; otherwise - READ */
#define FUSE_DIO_WRITE (1 << 0)
static ssize_t fuse_direct_io(struct kiocb *iocb, struct iov_iter *iter,
int flags)
{
int write = flags & FUSE_DIO_WRITE;
struct file *file = iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct fuse_conn *fc = ff->fc;
loff_t pos = iocb->ki_pos;
size_t count = iov_iter_count(iter);
ssize_t res = 0;
struct fuse_req *req;
bool should_dirty = !write && iter_is_iovec(iter);
unsigned int i;
int err = 0;
req = fuse2_get_req(fc, iov_iter_npages(iter, fc->max_pages));
if (IS_ERR(req))
return PTR_ERR(req);
while (count) {
ssize_t nres;
size_t nbytes = count;
err = fuse_get_user_pages(req, iter, &nbytes, write);
if (err && !nbytes)
break;
if (write)
nres = fuse_send_write(req, iocb, pos, nbytes);
else
nres = fuse_send_read(req, iocb, pos, nbytes);
if (should_dirty) {
for (i = 0; i < req->num_pages; i++)
set_page_dirty_lock(req->pages[i]);
}
if (nres < 0) {
err = nres;
break;
} else if (nres > nbytes) {
res = 0;
err = -EIO;
break;
}
count -= nres;
res += nres;
pos += nres;
if (nres != nbytes)
break;
if (count) {
fuse2_put_request(req);
req = fuse2_get_req(fc, iov_iter_npages(iter, fc->max_pages));
if (IS_ERR(req))
break;
}
}
if (!IS_ERR(req))
fuse2_put_request(req);
if (res > 0) {
iocb->ki_pos = pos;
return res;
}
return err;
}
static ssize_t fuse_read_buf(struct file *file, loff_t pos,
void *buf, size_t len)
{
struct fuse_conn *fc = get_fuse_conn(file_inode(file));
struct fuse_file *ff = file->private_data;
struct fuse_read_in inarg = {
.fh = ff->fh,
.offset = pos,
.size = len,
.flags = file->f_flags,
};
FUSE_ARGS(args);
args.in.h.opcode = FUSE_READ;
args.in.h.nodeid = ff->nodeid;
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
args.out.argvar = 1;
args.out.numargs = 1;
args.out.args[0].size = len;
args.out.args[0].value = buf;
return fuse2_simple_request(fc, &args);
}
struct fuse_kvec_ctx {
struct file *file;
size_t res;
loff_t pos;
};
static int fuse_read_kvec(struct kvec *vec, void *_ctx)
{
struct fuse_kvec_ctx *ctx = _ctx;
ssize_t res;
while (vec->iov_len) {
size_t len = min(vec->iov_len, PAGE_SIZE);
res = fuse_read_buf(ctx->file, ctx->pos, vec->iov_base, len);
if (res <= 0)
return res;
ctx->res += res;
ctx->pos += res;
if (res < len)
break;
vec->iov_len -= res;
vec->iov_base += res;
}
return 0;
}
static int fuse_send_map(struct kiocb *iocb, size_t count,
struct fuse_map_out *outarg)
{
struct file *file = iocb->ki_filp;
struct fuse_conn *fc = get_fuse_conn(file_inode(file));
struct fuse_file *ff = file->private_data;
struct fuse_read_in inarg = {
.fh = ff->fh,
.offset = iocb->ki_pos,
.size = count,
.flags = file->f_flags,
};
FUSE_ARGS(args);
args.in.h.opcode = FUSE_MAP;
args.in.h.nodeid = ff->nodeid;
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
args.out.numargs = 1;
args.out.args[0].size = sizeof(*outarg);
args.out.args[0].value = outarg;
return fuse2_simple_request(fc, &args);
}
static ssize_t fuse_file_map_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct fuse_conn *fc = get_fuse_conn(file_inode(iocb->ki_filp));
struct fuse_map_out outarg;
struct file *mapfile;
ssize_t res, total = 0;
size_t count;
loff_t pos;
while ((count = iov_iter_count(to))) {
res = fuse_send_map(iocb, count, &outarg);
if (res || !outarg.size)
break;
res = -EBADF;
mapfile = fuse2_map_get(fc, outarg.mapfd);
if (!mapfile)
break;
iov_iter_truncate(to, outarg.size);
pos = outarg.offset;
res = vfs_iter_read(mapfile, to, &pos, /* FIXME */ 0);
fput(mapfile);
if (res < 0)
break;
iov_iter_reexpand(to, count - res);
if (res == 0)
break;
total += res;
iocb->ki_pos += res;
}
return total ?: res;
}
static ssize_t fuse_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
struct fuse_file *ff = file->private_data;
struct inode *inode = file_inode(file);
if (is_bad_inode(inode))
return -EIO;
if (ff->open_flags & FOPEN_MAP)
return fuse_file_map_iter(iocb, to);
if (iov_iter_is_kvec(to)) {
struct fuse_kvec_ctx ctx = {
.file = file,
.pos = iocb->ki_pos
};
size_t len = iov_iter_count(to);
int err;
err = iov_iter_for_each_range(to, len, fuse_read_kvec, &ctx);
iocb->ki_pos = ctx.pos;
if (ctx.res > 0)
return ctx.res;
return err;
}
return fuse_direct_io(iocb, to, 0);
}
static ssize_t fuse_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
ssize_t res;
if (is_bad_inode(inode))
return -EIO;
/* Don't allow parallel writes to the same file */
inode_lock(inode);
res = generic_write_checks(iocb, from);
if (res > 0)
res = fuse_direct_io(iocb, from, FUSE_DIO_WRITE);
if (res > 0)
fuse_write_update_size(inode, iocb->ki_pos);
inode_unlock(inode);
return res;
}
static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma)
{
/* Can't provide the coherency needed for MAP_SHARED */
if (vma->vm_flags & VM_MAYSHARE)
return -ENODEV;
invalidate_inode_pages2(file->f_mapping);
return generic_file_mmap(file, vma);
}
static int convert_fuse_file_lock(struct fuse_conn *fc,
const struct fuse_file_lock *ffl,
struct file_lock *fl)
{
switch (ffl->type) {
case F_UNLCK:
break;
case F_RDLCK:
case F_WRLCK:
if (ffl->start > OFFSET_MAX || ffl->end > OFFSET_MAX ||
ffl->end < ffl->start)
return -EIO;
fl->fl_start = ffl->start;
fl->fl_end = ffl->end;
/*
* Convert pid into init's pid namespace. The locks API will
* translate it into the caller's pid namespace.
*/
rcu_read_lock();
fl->fl_pid = pid_nr_ns(find_pid_ns(ffl->pid, fc->pid_ns), &init_pid_ns);
rcu_read_unlock();
break;
default:
return -EIO;
}
fl->fl_type = ffl->type;
return 0;
}
static void fuse_lk_fill(struct fuse_args *args, struct file *file,
const struct file_lock *fl, int opcode, pid_t pid,
int flock, struct fuse_lk_in *inarg)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_file *ff = file->private_data;
memset(inarg, 0, sizeof(*inarg));
inarg->fh = ff->fh;
inarg->owner = fuse2_lock_owner_id(fc, fl->fl_owner);
inarg->lk.start = fl->fl_start;
inarg->lk.end = fl->fl_end;
inarg->lk.type = fl->fl_type;
inarg->lk.pid = pid;
if (flock)
inarg->lk_flags |= FUSE_LK_FLOCK;
args->in.h.opcode = opcode;
args->in.h.nodeid = get_node_id(inode);
args->in.numargs = 1;
args->in.args[0].size = sizeof(*inarg);
args->in.args[0].value = inarg;
}
static int fuse_getlk(struct file *file, struct file_lock *fl)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
FUSE_ARGS(args);
struct fuse_lk_in inarg;
struct fuse_lk_out outarg;
int err;
fuse_lk_fill(&args, file, fl, FUSE_GETLK, 0, 0, &inarg);
args.out.numargs = 1;
args.out.args[0].size = sizeof(outarg);
args.out.args[0].value = &outarg;
err = fuse2_simple_request(fc, &args);
if (!err)
err = convert_fuse_file_lock(fc, &outarg.lk, fl);
return err;
}
static int fuse_setlk(struct file *file, struct file_lock *fl, int flock)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
FUSE_ARGS(args);
struct fuse_lk_in inarg;
int opcode = (fl->fl_flags & FL_SLEEP) ? FUSE_SETLKW : FUSE_SETLK;
struct pid *pid = fl->fl_type != F_UNLCK ? task_tgid(current) : NULL;
pid_t pid_nr = pid_nr_ns(pid, fc->pid_ns);
int err;
if (fl->fl_lmops && fl->fl_lmops->lm_grant) {
/* NLM needs asynchronous locks, which we don't support yet */
return -ENOLCK;
}
/* Unlock on close is handled by the flush method */
if ((fl->fl_flags & FL_CLOSE_POSIX) == FL_CLOSE_POSIX)
return 0;
fuse_lk_fill(&args, file, fl, opcode, pid_nr, flock, &inarg);
err = fuse2_simple_request(fc, &args);
/* locking is restartable */
if (err == -EINTR)
err = -ERESTARTSYS;
return err;
}
static int fuse_file_lock(struct file *file, int cmd, struct file_lock *fl)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
int err;
if (cmd == F_CANCELLK) {
err = 0;
} else if (cmd == F_GETLK) {
if (fc->no_lock) {
posix_test_lock(file, fl);
err = 0;
} else
err = fuse_getlk(file, fl);
} else {
if (fc->no_lock)
err = posix_lock_file(file, fl, NULL);
else
err = fuse_setlk(file, fl, 0);
}
return err;
}
static int fuse_file_flock(struct file *file, int cmd, struct file_lock *fl)
{
struct inode *inode = file_inode(file);
struct fuse_conn *fc = get_fuse_conn(inode);
int err;
if (fc->no_flock) {
err = locks_lock_file_wait(file, fl);
} else {
struct fuse_file *ff = file->private_data;
/* emulate flock with POSIX locks */
ff->flock = true;
err = fuse_setlk(file, fl, 1);
}
return err;
}
static loff_t fuse_lseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
struct fuse_conn *fc = get_fuse_conn(inode);
struct fuse_file *ff = file->private_data;
FUSE_ARGS(args);
struct fuse_lseek_in inarg = {
.fh = ff->fh,
.offset = offset,
.whence = whence
};
struct fuse_lseek_out outarg;
int err;
if (fc->no_lseek)
goto fallback;
args.in.h.opcode = FUSE_LSEEK;
args.in.h.nodeid = ff->nodeid;
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
args.out.numargs = 1;
args.out.args[0].size = sizeof(outarg);
args.out.args[0].value = &outarg;
err = fuse2_simple_request(fc, &args);
if (err) {
if (err == -ENOSYS) {
fc->no_lseek = 1;
goto fallback;
}
return err;
}
return vfs_setpos(file, outarg.offset, inode->i_sb->s_maxbytes);
fallback:
err = fuse2_update_attributes(inode, file);
if (!err)
return generic_file_llseek(file, offset, whence);
else
return err;
}
static loff_t fuse_file_llseek(struct file *file, loff_t offset, int whence)
{
loff_t retval;
struct inode *inode = file_inode(file);
switch (whence) {
case SEEK_SET:
case SEEK_CUR:
/* No i_mutex protection necessary for SEEK_CUR and SEEK_SET */
retval = generic_file_llseek(file, offset, whence);
break;
case SEEK_END:
inode_lock(inode);
retval = fuse2_update_attributes(inode, file);
if (!retval)
retval = generic_file_llseek(file, offset, whence);
inode_unlock(inode);
break;
case SEEK_HOLE:
case SEEK_DATA:
inode_lock(inode);
retval = fuse_lseek(file, offset, whence);
inode_unlock(inode);
break;
default:
retval = -EINVAL;
}
return retval;
}
static inline loff_t fuse_round_up(struct fuse_conn *fc, loff_t off)
{
return round_up(off, fc->max_pages << PAGE_SHIFT);
}
static long fuse_file_fallocate(struct file *file, int mode, loff_t offset,
loff_t length)
{
struct fuse_file *ff = file->private_data;
struct inode *inode = file_inode(file);
struct fuse_conn *fc = ff->fc;
FUSE_ARGS(args);
struct fuse_fallocate_in inarg = {
.fh = ff->fh,
.offset = offset,
.length = length,
.mode = mode
};
int err;
bool lock_inode = !(mode & FALLOC_FL_KEEP_SIZE) ||
(mode & FALLOC_FL_PUNCH_HOLE);
if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE))
return -EOPNOTSUPP;
if (fc->no_fallocate)
return -EOPNOTSUPP;
if (lock_inode) {
inode_lock(inode);
if (mode & FALLOC_FL_PUNCH_HOLE) {
loff_t endbyte = offset + length - 1;
err = filemap_write_and_wait_range(inode->i_mapping,
offset, endbyte);
if (err)
goto out;
}
}
args.in.h.opcode = FUSE_FALLOCATE;
args.in.h.nodeid = ff->nodeid;
args.in.numargs = 1;
args.in.args[0].size = sizeof(inarg);
args.in.args[0].value = &inarg;
err = fuse2_simple_request(fc, &args);
if (err == -ENOSYS) {
fc->no_fallocate = 1;
err = -EOPNOTSUPP;
}
if (err)
goto out;
if (mode & FALLOC_FL_PUNCH_HOLE)
truncate_pagecache_range(inode, offset, offset + length - 1);
out:
if (lock_inode)
inode_unlock(inode);
return err;
}
static const struct file_operations fuse_file_operations = {
.llseek = fuse_file_llseek,
.read_iter = fuse_file_read_iter,
.write_iter = fuse_file_write_iter,
.mmap = fuse_file_mmap,
.open = fuse_open,
.flush = fuse_flush,
.release = fuse_release,
.fsync = fuse_fsync,
.lock = fuse_file_lock,
.flock = fuse_file_flock,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
.fallocate = fuse_file_fallocate,
};
static const struct address_space_operations fuse_file_aops = {
.readpage = fuse_readpage,
};
void fuse2_init_file_inode(struct inode *inode)
{
inode->i_fop = &fuse_file_operations;
inode->i_data.a_ops = &fuse_file_aops;
}