#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "fuse_private.h"
#include "debug.h"
#define CHECK_OPT(opname) if (!f->op.opname) { \
err = -ENOSYS; \
goto out; \
}
static fuse_req_t ireq;
const fuse_req_t
ifuse_req(void)
{
return (ireq);
}
static void
ictx_init(fuse_req_t req)
{
ireq = req;
}
static void
ictx_destroy(void)
{
ireq = NULL;
}
static int
update_attr(struct fuse *f, struct stat *attr, const char *realname,
const fuse_ino_t ino)
{
int err;
memset(attr, 0, sizeof(struct stat));
err = f->op.getattr(realname, attr);
if (err)
return (err);
if (!f->conf.use_ino)
attr->st_ino = ino;
if (f->conf.set_mode)
attr->st_mode = (attr->st_mode & S_IFMT) | (0777 & ~f->conf.umask);
if (f->conf.set_uid)
attr->st_uid = f->conf.uid;
if (f->conf.set_gid)
attr->st_gid = f->conf.gid;
return (0);
}
static void
ifuse_ops_init(void *userdata, struct fuse_conn_info *fci)
{
struct fuse *f = (struct fuse *)userdata;
if (f->op.init)
f->op.init(fci);
}
static void
ifuse_ops_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct stat stbuf;
char *realname;
int err;
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = update_attr(f, &stbuf, realname, ino);
ictx_destroy();
free(realname);
out:
if (!err)
fuse_reply_attr(req, &stbuf, 0.0);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_access(fuse_req_t req, fuse_ino_t ino, int mask)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err;
CHECK_OPT(access);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.access(realname, mask);
ictx_destroy();
free(realname);
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err = 0;
if (f->op.open) {
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.open(realname, ffi);
ictx_destroy();
free(realname);
}
out:
if (!err)
fuse_reply_open(req, ffi);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err = 0;
if (f->op.opendir) {
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.opendir(realname, ffi);
ictx_destroy();
free(realname);
}
out:
if (!err)
fuse_reply_open(req, ffi);
else
fuse_reply_err(req, -err);
}
#define GENERIC_DIRSIZ(NLEN) \
((sizeof (struct dirent) - (MAXNAMLEN+1)) + ((NLEN+1 + 7) &~ 7))
static int
ifuse_fill_readdir(void *dh, const char *name, const struct stat *stbuf,
off_t off)
{
struct fuse *f;
struct fuse_dirhandle *fd = dh;
struct fuse_vnode *v;
struct stat attr;
char *buf;
uint32_t len, resid;
f = fd->fuse;
len = fuse_add_direntry(NULL, NULL, 0, name, stbuf, off);
if (fd->full || (fd->len + len > fd->size)) {
fd->full = 1;
return (0);
}
if (fd->start != 0 && fd->idx < fd->start) {
fd->idx += len;
return (0);
}
if (stbuf != NULL && f->conf.use_ino)
attr.st_ino = stbuf->st_ino;
else {
v = get_vn_by_name_and_parent(f, name, fd->ino);
if (v == NULL) {
if (strcmp(name, ".") == 0)
attr.st_ino = fd->ino;
else
attr.st_ino = 0xffffffff;
} else
attr.st_ino = v->ino;
}
if (stbuf != NULL)
attr.st_mode = stbuf->st_mode;
else
attr.st_mode = S_IFREG;
buf = (char *) fd->buf + fd->len;
resid = fd->size - fd->len;
len = fuse_add_direntry(ifuse_req(), buf, resid, name, &attr, off);
fd->len += len;
fd->idx += len;
return (0);
}
static int
ifuse_fill_getdir(fuse_dirh_t fd, const char *name, int type, ino_t ino)
{
struct stat st;
memset(&st, 0, sizeof(st));
st.st_mode = type << 12;
if (ino == 0)
st.st_ino = 0xffffffff;
else
st.st_ino = ino;
return (fd->filler(fd, name, &st, 0));
}
static void
ifuse_ops_readdir(struct fuse_req *req, fuse_ino_t ino, size_t size,
off_t offset, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_dirhandle fd;
char *realname;
int err;
fd.buf = calloc(1, size);
if (fd.buf == NULL) {
err = -errno;
goto out;
}
fd.filler = ifuse_fill_readdir;
fd.full = 0;
fd.len = 0;
fd.size = size;
fd.idx = 0;
fd.fuse = f;
fd.start = offset;
fd.ino = ino;
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
if (f->op.readdir)
err = f->op.readdir(realname, &fd, ifuse_fill_readdir,
offset, ffi);
else if (f->op.getdir)
err = f->op.getdir(realname, &fd, ifuse_fill_getdir);
else
err = -ENOSYS;
ictx_destroy();
free(realname);
out:
if (!err)
fuse_reply_buf(req, fd.buf, fd.len);
else
fuse_reply_err(req, -err);
free(fd.buf);
}
static void
ifuse_ops_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err = 0;
if (f->op.releasedir) {
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.releasedir(realname, ffi);
ictx_destroy();
free(realname);
}
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err = 0;
if (f->op.release) {
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.release(realname, ffi);
ictx_destroy();
free(realname);
}
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err;
CHECK_OPT(fsync);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.fsync(realname, datasync, ffi);
ictx_destroy();
free(realname);
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err;
CHECK_OPT(flush);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.flush(realname, ffi);
ictx_destroy();
free(realname);
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_entry_param entry;
struct fuse_vnode *vn;
char *realname;
int err;
vn = get_vn_by_name_and_parent(f, name, parent);
if (vn == NULL) {
vn = alloc_vn(f, name, -1, parent);
if (vn == NULL) {
err = -errno;
goto out;
}
set_vn(f, vn);
} else if (vn->ino != FUSE_ROOT_INO)
ref_vn(vn);
realname = build_realname(f, vn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
memset(&entry, 0, sizeof(entry));
entry.ino = vn->ino;
err = update_attr(f, &entry.attr, realname, vn->ino);
free(realname);
out:
if (!err)
fuse_reply_entry(req, &entry);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t offset,
struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *buf = NULL;
char *realname = NULL;
int err;
CHECK_OPT(read);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
buf = calloc(1, size);
if (buf == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.read(realname, buf, size, offset, ffi);
ictx_destroy();
out:
if (err >= 0)
fuse_reply_buf(req, buf, err);
else
fuse_reply_err(req, -err);
free(realname);
free(buf);
}
static void
ifuse_ops_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
size_t size, off_t offset, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char *realname;
int err;
CHECK_OPT(write);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.write(realname, buf, size, offset, ffi);
ictx_destroy();
free(realname);
out:
if (err >= 0)
fuse_reply_write(req, err);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
mode_t mode)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_entry_param entry;
struct fuse_vnode *newvn;
char *realname;
int err;
CHECK_OPT(mkdir);
newvn = get_vn_by_name_and_parent(f, name, parent);
if (newvn == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, newvn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.mkdir(realname, mode);
ictx_destroy();
if (!err)
err = update_attr(f, &entry.attr, realname, newvn->ino);
free(realname);
out:
if (!err) {
entry.ino = newvn->ino;
fuse_reply_entry(req, &entry);
} else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_vnode *vn;
char *realname;
int err;
CHECK_OPT(rmdir);
vn = get_vn_by_name_and_parent(f, name, parent);
if (vn == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, vn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.rmdir(realname);
ictx_destroy();
free(realname);
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_readlink(fuse_req_t req, fuse_ino_t ino)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
char path[PATH_MAX];
char *realname;
int err;
CHECK_OPT(readlink);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.readlink(realname, path, sizeof(path));
ictx_destroy();
free(realname);
out:
if (!err)
fuse_reply_readlink(req, path);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_vnode *vn;
char *realname;
int err;
CHECK_OPT(unlink);
vn = get_vn_by_name_and_parent(f, name, parent);
if (vn == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, vn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.unlink(realname);
ictx_destroy();
free(realname);
out:
fuse_reply_err(req, -err);
}
static void
ifuse_ops_statfs(fuse_req_t req, fuse_ino_t ino)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct statvfs stbuf;
char *realname;
int err;
CHECK_OPT(statfs);
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
memset(&stbuf, 0, sizeof(stbuf));
ictx_init(req);
err = f->op.statfs(realname, &stbuf);
ictx_destroy();
free(realname);
out:
if (!err)
fuse_reply_statfs(req, &stbuf);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
const char *newname)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_entry_param entry;
struct fuse_vnode *newvn;
char *realname = NULL;
char *target = NULL;
int err;
CHECK_OPT(link);
newvn = get_vn_by_name_and_parent(f, newname, newparent);
if (newvn == NULL) {
err = -errno;
goto out;
}
target = build_realname(f, ino);
if (target == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, newvn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.link(target, realname);
ictx_destroy();
if (!err)
err = update_attr(f, &entry.attr, realname, newvn->ino);
out:
free(realname);
free(target);
if (!err) {
entry.ino = newvn->ino;
fuse_reply_entry(req, &entry);
} else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
int flags, struct fuse_file_info *ffi)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct stat stbuf;
struct timespec ts[2];
struct utimbuf tbuf;
char *realname;
uid_t uid;
gid_t gid;
int err = 0;
realname = build_realname(f, ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
if (flags & FUSE_FATTR_MODE) {
if (f->op.chmod)
err = f->op.chmod(realname, attr->st_mode);
else
err = ENOSYS;
}
if (!err && (flags & FUSE_FATTR_UID || flags & FUSE_FATTR_GID)) {
uid = (flags & FUSE_FATTR_UID) ? attr->st_uid : (uid_t)-1;
gid = (flags & FUSE_FATTR_GID) ? attr->st_gid : (gid_t)-1;
if (f->op.chown)
err = f->op.chown(realname, uid, gid);
else
err = ENOSYS;
}
if (!err && (flags & FUSE_FATTR_MTIME || flags & FUSE_FATTR_ATIME)) {
if (f->op.utimens) {
ts[0] = attr->st_atim;
ts[1] = attr->st_mtim;
err = f->op.utimens(realname, ts);
} else if (f->op.utime) {
tbuf.actime = attr->st_atim.tv_sec;
tbuf.modtime = attr->st_mtim.tv_sec;
err = f->op.utime(realname, &tbuf);
} else
err = ENOSYS;
}
if (!err && (flags & FUSE_FATTR_SIZE)) {
if (f->op.truncate)
err = f->op.truncate(realname, attr->st_size);
else
err = ENOSYS;
}
ictx_destroy();
if (!err)
err = update_attr(f, &stbuf, realname, ino);
free(realname);
out:
if (!err)
fuse_reply_attr(req, &stbuf, 0.0);
else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_symlink(fuse_req_t req, const char *link, fuse_ino_t parent,
const char *name)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_entry_param entry;
struct fuse_vnode *newvn;
char *realname;
int err;
CHECK_OPT(symlink);
newvn = get_vn_by_name_and_parent(f, name, parent);
if (newvn == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, newvn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.symlink(link, realname);
ictx_destroy();
if (!err)
err = update_attr(f, &entry.attr, realname, newvn->ino);
free(realname);
out:
if (!err) {
entry.ino = newvn->ino;
fuse_reply_entry(req, &entry);
} else
fuse_reply_err(req, -err);
}
static void
ifuse_ops_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
fuse_ino_t newparent, const char *newname)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_vnode *vn;
struct fuse_vnode *newvn;
char *realname = NULL;
char *newrealname = NULL;
int err;
CHECK_OPT(rename);
vn = get_vn_by_name_and_parent(f, name, parent);
if (vn == NULL) {
err = -errno;
goto out;
}
newvn = get_vn_by_name_and_parent(f, newname, newparent);
if (newvn == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, vn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
newrealname = build_realname(f, newvn->ino);
if (newrealname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.rename(realname, newrealname);
ictx_destroy();
out:
free(realname);
free(newrealname);
fuse_reply_err(req, -err);
}
static void
ifuse_ops_destroy(void *userdata)
{
struct fuse *f = (struct fuse *)userdata;
if (f->op.destroy)
f->op.destroy(f->private_data);
}
static void
ifuse_ops_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup )
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_vnode *vn;
vn = tree_get(&f->vnode_tree, ino);
if (vn != NULL)
unref_vn(f, vn);
fuse_reply_err(req, 0);
}
static void
ifuse_ops_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
mode_t mode, dev_t dev)
{
struct fuse *f = (struct fuse *)fuse_req_userdata(req);
struct fuse_entry_param entry;
struct fuse_vnode *newvn;
char *realname;
int err;
CHECK_OPT(mknod);
newvn = get_vn_by_name_and_parent(f, name, parent);
if (newvn == NULL) {
err = -errno;
goto out;
}
realname = build_realname(f, newvn->ino);
if (realname == NULL) {
err = -errno;
goto out;
}
ictx_init(req);
err = f->op.mknod(realname, mode, dev);
ictx_destroy();
if (!err)
err = update_attr(f, &entry.attr, realname, newvn->ino);
free(realname);
out:
if (!err) {
entry.ino = newvn->ino;
fuse_reply_entry(req, &entry);
} else
fuse_reply_err(req, -err);
}
struct fuse_lowlevel_ops llops = {
.init = ifuse_ops_init,
.destroy = ifuse_ops_destroy,
.access = ifuse_ops_access,
.flush = ifuse_ops_flush,
.forget = ifuse_ops_forget,
.fsync = ifuse_ops_fsync,
.getattr = ifuse_ops_getattr,
.link = ifuse_ops_link,
.lookup = ifuse_ops_lookup,
.mkdir = ifuse_ops_mkdir,
.mknod = ifuse_ops_mknod,
.open = ifuse_ops_open,
.opendir = ifuse_ops_opendir,
.read = ifuse_ops_read,
.readdir = ifuse_ops_readdir,
.readlink = ifuse_ops_readlink,
.release = ifuse_ops_release,
.releasedir = ifuse_ops_releasedir,
.rename = ifuse_ops_rename,
.rmdir = ifuse_ops_rmdir,
.setattr = ifuse_ops_setattr,
.statfs = ifuse_ops_statfs,
.symlink = ifuse_ops_symlink,
.unlink = ifuse_ops_unlink,
.write = ifuse_ops_write,
};