blob: 749ae866fdd387dd1d4b227930cbceb6ac0b2615 [file] [log] [blame]
/*
* Copyright 2006 Jeff Garzik <jgarzik@pobox.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#define FUSE_USE_VERSION 26
#define _BSD_SOURCE
#include "dbfs-config.h"
#include <fuse_lowlevel.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/vfs.h>
#include <glib.h>
#include <db.h>
#include "dbfs.h"
int debugging = 1;
static void dbfs_fill_attr(const struct dbfs_inode *ino, struct stat *st)
{
memset(st, 0, sizeof(*st));
st->st_dev = 1;
st->st_ino = GUINT64_FROM_LE(ino->raw_inode->ino);
st->st_mode = GUINT32_FROM_LE(ino->raw_inode->mode);
st->st_nlink = GUINT32_FROM_LE(ino->raw_inode->nlink);
st->st_uid = GUINT32_FROM_LE(ino->raw_inode->uid);
st->st_gid = GUINT32_FROM_LE(ino->raw_inode->gid);
st->st_rdev = GUINT64_FROM_LE(ino->raw_inode->rdev);
st->st_size = GUINT64_FROM_LE(ino->raw_inode->size);
st->st_blksize = 512;
st->st_blocks = GUINT64_FROM_LE(ino->raw_inode->size) / 512ULL;
st->st_atime = GUINT64_FROM_LE(ino->raw_inode->atime);
st->st_mtime = GUINT64_FROM_LE(ino->raw_inode->mtime);
st->st_ctime = GUINT64_FROM_LE(ino->raw_inode->ctime);
if (debugging)
syslog(LOG_DEBUG, "fill_attr: ino %lu, mode %u, uid %u, "
"gid %u, size %lu, mtime %lu",
st->st_ino,
st->st_mode,
st->st_uid,
st->st_gid,
st->st_size,
st->st_mtime);
}
static void dbfs_fill_ent(const struct dbfs_inode *ino,
struct fuse_entry_param *ent)
{
memset(ent, 0, sizeof(*ent));
ent->ino = GUINT64_FROM_LE(ino->raw_inode->ino);
ent->generation = GUINT64_FROM_LE(ino->raw_inode->version);
dbfs_fill_attr(ino, &ent->attr);
/* these timeouts are just a guess */
ent->attr_timeout = 2.0;
ent->entry_timeout = 2.0;
}
static void dbfs_reply_ino(fuse_req_t req, struct dbfs_inode *ino)
{
struct fuse_entry_param ent;
dbfs_fill_ent(ino, &ent);
fuse_reply_entry(req, &ent);
dbfs_inode_free(ino);
}
static void dbfs_op_init(void *userdata, struct fuse_conn_info *conn)
{
struct dbfs *fs;
int rc;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_init");
fs = dbfs_new();
rc = dbfs_open(fs, DB_RECOVER | DB_CREATE, DB_CREATE, "dbfs", TRUE);
if (rc) {
syslog(LOG_ERR, "dbfs_open failed");
abort(); /* TODO: improve */
}
gfs = fs;
syslog(LOG_INFO, PACKAGE_STRING " initialized");
}
static void dbfs_op_destroy(void *userdata)
{
struct dbfs *fs = gfs;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_destroy");
dbfs_close(fs);
dbfs_free(fs);
gfs = NULL;
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_destroy");
}
static void dbfs_op_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
{
guint64 ino_n;
struct dbfs_inode *ino;
int rc;
DB_TXN *txn;
unsigned long long ino_pr;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_lookup, name=='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
fuse_reply_err(req, rc);
return;
}
/* lookup inode in parent directory */
rc = dbfs_dir_lookup(txn, parent, name, &ino_n);
if (rc)
goto err_out;
rc = dbfs_inode_read(txn, ino_n, &ino);
if (rc)
goto err_out;
rc = txn->commit(txn, 0);
if (rc) {
dbfs_inode_free(ino);
fuse_reply_err(req, rc);
return;
}
/* send reply */
ino_pr = GUINT64_FROM_LE(ino->raw_inode->ino);
dbfs_reply_ino(req, ino);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_lookup, ino==%Lu", ino_pr);
return;
err_out:
txn->abort(txn);
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_lookup, rc==%d", -rc);
}
static void dbfs_op_getattr(fuse_req_t req, fuse_ino_t ino_n,
struct fuse_file_info *fi)
{
struct dbfs_inode *ino;
struct stat st;
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_getattr, ino==%lu", ino_n);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
fuse_reply_err(req, rc);
return;
}
/* read inode from database */
rc = dbfs_inode_read(txn, ino_n, &ino);
if (rc) {
rc = ENOENT;
goto err_out_txn;
}
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
/* fill in stat buf, taking care to convert from
* little endian to native endian
*/
dbfs_fill_attr(ino, &st);
/* send result back to FUSE */
fuse_reply_attr(req, &st, 2.0);
dbfs_inode_free(ino);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_getattr, ino==%Lu",
(unsigned long long) st.st_ino);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_getattr, rc==%d", rc);
}
static void dbfs_op_setattr(fuse_req_t req, fuse_ino_t ino_n,
struct stat *attr, int to_set,
struct fuse_file_info *fi)
{
struct dbfs_inode *ino;
struct stat st;
int rc, dirty = 0;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_setattr, ino==%lu", ino_n);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
/* read inode from database */
rc = dbfs_inode_read(txn, ino_n, &ino);
if (rc)
goto err_out_txn;
if (to_set & FUSE_SET_ATTR_MODE) {
ino->raw_inode->mode = GUINT32_TO_LE(attr->st_mode);
dirty = 1;
}
if (to_set & FUSE_SET_ATTR_UID) {
ino->raw_inode->uid = GUINT32_TO_LE(attr->st_uid);
dirty = 1;
}
if (to_set & FUSE_SET_ATTR_GID) {
ino->raw_inode->gid = GUINT32_TO_LE(attr->st_gid);
dirty = 1;
}
if (to_set & FUSE_SET_ATTR_SIZE) {
rc = dbfs_inode_resize(txn, ino, attr->st_size);
if (rc)
goto err_out_free;
ino->raw_inode->size = GUINT64_TO_LE(attr->st_size);
dirty = 1;
}
if (to_set & FUSE_SET_ATTR_ATIME) {
ino->raw_inode->atime = GUINT64_TO_LE(attr->st_atime);
dirty = 1;
}
if (to_set & FUSE_SET_ATTR_MTIME) {
ino->raw_inode->mtime = GUINT64_TO_LE(attr->st_mtime);
dirty = 1;
}
if (dirty) {
rc = dbfs_inode_write(txn, ino);
if (rc)
goto err_out_free;
}
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
txn = NULL;
goto err_out_free;
}
dbfs_fill_attr(ino, &st);
dbfs_inode_free(ino);
fuse_reply_attr(req, &st, 2.0);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_setattr, ino==%Lu",
(unsigned long long) st.st_ino);
return;
err_out_free:
dbfs_inode_free(ino);
err_out_txn:
if (txn)
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_setattr, rc==%d", -rc);
}
static void dbfs_op_readlink(fuse_req_t req, fuse_ino_t ino)
{
int rc;
DBT val;
char *s;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_readlink, ino==%lu", ino);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
/* read link from database */
rc = dbfs_symlink_read(txn, ino, &val);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
free(val.data);
goto err_out;
}
/* send reply; use g_strndup to append a trailing null */
s = g_strndup(val.data, val.size);
fuse_reply_readlink(req, s);
free(val.data);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_readlink, linktext=='%s'", s);
g_free(s);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_readlink, rc==%d", -rc);
}
static int dbfs_mode_validate(mode_t mode)
{
unsigned int ifmt = mode & S_IFMT;
int rc = 0;
if (S_ISREG(mode)) {
if (ifmt & ~S_IFREG)
rc = -EINVAL;
}
else if (S_ISDIR(mode)) {
if (ifmt & ~S_IFDIR)
rc = -EINVAL;
}
else if (S_ISCHR(mode)) {
if (ifmt & ~S_IFCHR)
rc = -EINVAL;
}
else if (S_ISBLK(mode)) {
if (ifmt & ~S_IFBLK)
rc = -EINVAL;
}
else if (S_ISFIFO(mode)) {
if (ifmt & ~S_IFIFO)
rc = -EINVAL;
}
else if (S_ISLNK(mode))
rc = -EINVAL;
else if (S_ISSOCK(mode)) {
if (ifmt & ~S_IFSOCK)
rc = -EINVAL;
}
else
rc = -EINVAL;
return rc;
}
static void dbfs_op_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
mode_t mode, dev_t rdev)
{
struct dbfs_inode *ino;
int rc;
DB_TXN *txn;
unsigned long long ino_pr;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_mknod, name='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_mode_validate(mode);
if (rc)
goto err_out_txn;
/* these have separate inode-creation hooks */
if (S_ISDIR(mode) || S_ISLNK(mode)) {
rc = -EINVAL;
goto err_out_txn;
}
rc = dbfs_mknod(txn, parent, name, mode, rdev, &ino);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
dbfs_inode_free(ino);
rc = -rc;
goto err_out;
}
ino_pr = GUINT64_FROM_LE(ino->raw_inode->ino);
dbfs_reply_ino(req, ino);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_mknod, ino==%Lu", ino_pr);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_mknod, rc==%d", -rc);
}
static void dbfs_op_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
mode_t mode)
{
struct dbfs_inode *ino;
int rc;
DB_TXN *txn;
unsigned long long ino_pr;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_mkdir, name=='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
mode &= ALLPERMS;
mode |= S_IFDIR;
rc = dbfs_mknod(txn, parent, name, mode, 0, &ino);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
dbfs_inode_free(ino);
rc = -rc;
goto err_out;
}
ino_pr = GUINT64_FROM_LE(ino->raw_inode->ino);
dbfs_reply_ino(req, ino);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_mkdir, ino==%Lu", ino_pr);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_mkdir, rc==%d", -rc);
}
static void dbfs_op_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_unlink, name=='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto out;
}
rc = dbfs_unlink(txn, parent, name, 0);
if (rc)
goto err_out;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto out;
}
out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_unlink, rc==%d", -rc);
return;
err_out:
txn->abort(txn);
goto out;
}
static void dbfs_op_link(fuse_req_t req, fuse_ino_t ino_n, fuse_ino_t parent,
const char *newname)
{
struct dbfs_inode *ino;
int rc;
DB_TXN *txn;
unsigned long long ino_pr;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_link, ino==%lu, newname=='%s'",
ino_n, newname);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
/* read inode from database */
rc = dbfs_inode_read(txn, ino_n, &ino);
if (rc) {
rc = -ENOENT;
goto err_out_txn;
}
/* attempt to create hard link */
rc = dbfs_link(txn, ino, ino_n, parent, newname);
if (rc)
goto err_out_ino;
rc = txn->commit(txn, 0);
if (rc) {
dbfs_inode_free(ino);
rc = -rc;
goto err_out;
}
ino_pr = GUINT64_FROM_LE(ino->raw_inode->ino);
dbfs_reply_ino(req, ino);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_link, ino==%Lu", ino_pr);
return;
err_out_ino:
dbfs_inode_free(ino);
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_link, rc==%d", -rc);
}
static void dbfs_op_open(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi)
{
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_open, ino==%lu", ino);
fi->direct_io = 0;
fi->keep_cache = 1;
fuse_reply_open(req, fi);
}
static void dbfs_op_read(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *fi)
{
void *buf = NULL;
int rc, rc2;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_read, ino==%lu", ino);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_read(txn, ino, off, size, &buf);
if (rc < 0)
goto err_out_txn;
rc2 = txn->commit(txn, 0);
if (rc2) {
rc = -rc2;
goto err_out;
}
fuse_reply_buf(req, buf, rc);
free(buf);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_read, rc==%d", rc);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_read, rc==%d", -rc);
}
static void dbfs_op_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
size_t size, off_t off, struct fuse_file_info *fi)
{
int rc, rc2;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_write, ino==%lu", ino);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_write(txn, ino, off, buf, size);
if (rc < 0)
goto err_out_txn;
rc2 = txn->commit(txn, 0);
if (rc2) {
rc = -rc2;
goto err_out;
}
fuse_reply_write(req, rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_write, rc==%d", rc);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_write, error, rc==%d", -rc);
}
static int dbfs_chk_empty(struct dbfs_dirent *de, void *userdata)
{
if ((GUINT16_FROM_LE(de->namelen) == 1) && (!memcmp(de->name, ".", 1)))
return 0;
if ((GUINT16_FROM_LE(de->namelen) == 2) && (!memcmp(de->name, "..", 2)))
return 0;
return ENOTEMPTY;
}
static void dbfs_op_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
{
guint64 ino_n;
int rc;
DBT val;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_rmdir, name=='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto out;
}
/* get inode number associated with name */
rc = dbfs_dir_lookup(txn, parent, name, &ino_n);
if (rc)
goto out_txn;
/* read dir associated with name */
rc = dbfs_dir_read(txn, ino_n, &val);
if (rc)
goto out_txn;
/* make sure dir only contains "." and ".." */
rc = dbfs_dir_foreach(val.data, dbfs_chk_empty, NULL);
free(val.data);
/* if dbfs_chk_empty() returns non-zero, dir is not empty */
if (rc)
goto out_txn;
/* dir is empty, go ahead and unlink */
rc = dbfs_unlink(txn, parent, name, DBFS_UNLINK_DIR);
if (rc)
goto out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto out;
}
fuse_reply_err(req, 0);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_rmdir, rc==0");
return;
out_txn:
txn->abort(txn);
out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_rmdir, rc==%d", -rc);
}
static void dbfs_op_symlink(fuse_req_t req, const char *link,
fuse_ino_t parent, const char *name)
{
struct dbfs_inode *ino;
int rc;
DB_TXN *txn;
unsigned long long ino_pr;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_symlink, name=='%s'", name);
if (!g_utf8_validate(link, -1, NULL)) {
rc = -EINVAL;
goto err_out;
}
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_mknod(txn, parent, name,
S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO, 0, &ino);
if (rc)
goto err_out_txn;
rc = dbfs_symlink_write(txn, GUINT64_FROM_LE(ino->raw_inode->ino), link);
if (rc)
goto err_out_ino;
rc = txn->commit(txn, 0);
if (rc) {
dbfs_inode_free(ino);
rc = -rc;
goto err_out;
}
ino_pr = GUINT64_FROM_LE(ino->raw_inode->ino);
dbfs_reply_ino(req, ino);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_symlink, ino==%Lu", ino_pr);
return;
err_out_ino:
dbfs_inode_free(ino);
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_symlink, rc==%d", -rc);
}
static void dbfs_op_rename(fuse_req_t req, fuse_ino_t parent,
const char *name, fuse_ino_t newparent,
const char *newname)
{
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_rename, name=='%s', newname=='%s'",
name, newname);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto out;
}
rc = dbfs_rename(txn, parent, name, newparent, newname);
if (rc)
goto out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto out;
}
fuse_reply_err(req, 0);
return;
out_txn:
txn->abort(txn);
out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_rename, rc==%d", -rc);
}
static void dbfs_op_fsync (fuse_req_t req, fuse_ino_t ino,
int datasync, struct fuse_file_info *fi)
{
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_fsync, ino==%lu", ino);
/* DB should have already sync'd our data for us */
fuse_reply_err(req, 0);
}
static void dbfs_op_opendir(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi)
{
DBT val;
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_opendir, ino==%lu", ino);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
/* read directory from database */
rc = dbfs_dir_read(txn, ino, &val);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
/* save for later use */
fi->fh = (uint64_t) (unsigned long) val.data;
/* send reply */
fuse_reply_open(req, fi);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_opendir");
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_opendir, rc==%d", -rc);
}
struct dirbuf {
char *p;
size_t size;
};
struct dirbuf_iter {
struct dirbuf db;
fuse_req_t req;
};
/* stock function copied from FUSE template (hello_ll.c) */
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
fuse_ino_t ino)
{
struct stat stbuf;
size_t oldsize = b->size;
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
b->p = (char *) realloc(b->p, b->size);
memset(&stbuf, 0, sizeof(stbuf));
stbuf.st_ino = ino;
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
b->size);
}
#ifndef min
#define min(x, y) ((x) < (y) ? (x) : (y))
#endif
/* stock function copied from FUSE template */
static int reply_buf_limited(fuse_req_t req, const void *buf, size_t bufsize,
off_t off, size_t maxsize)
{
if (off < bufsize)
return fuse_reply_buf(req, buf + off,
min(bufsize - off, maxsize));
else
return fuse_reply_buf(req, NULL, 0);
}
static int dbfs_fill_dirbuf(struct dbfs_dirent *de, void *userdata)
{
struct dirbuf_iter *di = userdata;
struct dirbuf *b = &di->db;
char *s;
/* add dirent to buffer; use g_strndup solely to append nul */
s = g_strndup(de->name, GUINT16_FROM_LE(de->namelen));
dirbuf_add(di->req, b, s, GUINT64_FROM_LE(de->ino));
free(s);
return 0;
}
static void dbfs_op_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
off_t off, struct fuse_file_info *fi)
{
struct dirbuf_iter di;
void *p;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_readdir, ino==%lu", ino);
/* grab directory contents stored by opendir */
p = (void *) (unsigned long) fi->fh;
/* iterate through each dirent, filling dirbuf */
memset(&di, 0, sizeof(di));
dbfs_dir_foreach(p, dbfs_fill_dirbuf, &di);
/* send reply */
reply_buf_limited(req, di.db.p, di.db.size, off, size);
free(di.db.p);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_readdir");
}
static void dbfs_op_releasedir(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi)
{
void *p = (void *) (unsigned long) fi->fh;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_releasedir, ino==%lu", ino);
/* release directory contents */
free(p);
}
static void dbfs_op_fsyncdir (fuse_req_t req, fuse_ino_t ino,
int datasync, struct fuse_file_info *fi)
{
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_fsyncdir, ino==%lu", ino);
/* DB should have already sync'd our data for us */
fuse_reply_err(req, 0);
}
#define COPY(x) f.f_##x = st.f_##x
static void dbfs_op_statfs(fuse_req_t req, fuse_ino_t ino)
{
struct statvfs f;
struct statfs st;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_statfs");
if (statfs(gfs->home, &st) < 0) {
fuse_reply_err(req, errno);
return;
}
memset(&f, 0, sizeof(f));
COPY(bsize);
f.f_frsize = 512;
COPY(blocks);
COPY(bfree);
COPY(bavail);
f.f_files = 0xfffffff;
f.f_ffree = 0xffffff;
f.f_favail = 0xffffff;
f.f_fsid = 0xdeadbeef;
f.f_flag = 0;
f.f_namemax = DBFS_FILENAME_MAX;
fuse_reply_statfs(req, &f);
}
#undef COPY
static void dbfs_op_setxattr(fuse_req_t req, fuse_ino_t ino,
const char *name, const char *value,
size_t size, int flags)
{
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_setxattr, name=='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_xattr_set(txn, ino, name, value, size, flags);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
fuse_reply_err(req, 0);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_setxattr, rc==%d", -rc);
}
static void dbfs_op_getxattr(fuse_req_t req, fuse_ino_t ino,
const char *name, size_t size)
{
void *buf = NULL;
size_t buflen = 0;
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_getxattr, name=='%s'", name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_xattr_get(txn, ino, name, &buf, &buflen);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
if (size == 0)
fuse_reply_xattr(req, buflen);
else if (buflen <= size)
fuse_reply_buf(req, buf, buflen);
else {
rc = -ERANGE;
goto err_out;
}
free(buf);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_getxattr, rc==%d", -rc);
}
static void dbfs_op_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
{
int rc;
void *buf;
size_t buflen;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_listxattr, ino==%lu", ino);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_xattr_list(txn, ino, &buf, &buflen);
if (rc < 0)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
if (size == 0)
fuse_reply_xattr(req, buflen);
else if (size < buflen)
fuse_reply_err(req, ERANGE);
else
fuse_reply_buf(req, buf, buflen);
free(buf);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_listxattr, rc==%d", -rc);
}
static void dbfs_op_removexattr(fuse_req_t req, fuse_ino_t ino,
const char *name)
{
int rc;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_removexattr, ino==%lu, name=='%s'",
ino, name);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
rc = dbfs_xattr_remove(txn, ino, name, TRUE);
if (rc)
goto err_out_txn;
rc = txn->commit(txn, 0);
if (rc) {
rc = -rc;
goto err_out;
}
fuse_reply_err(req, 0);
return;
err_out_txn:
txn->abort(txn);
err_out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_removexattr, rc==%d", -rc);
}
static void dbfs_op_access(fuse_req_t req, fuse_ino_t ino_n, int mask)
{
struct dbfs_inode *ino;
const struct fuse_ctx *ctx;
int rc;
guint32 mode, uid, gid;
DB_TXN *txn;
if (debugging)
syslog(LOG_DEBUG, "ENTER dbfs_op_access, ino==%lu", ino_n);
ctx = fuse_req_ctx(req);
g_assert(ctx != NULL);
rc = gfs->env->txn_begin(gfs->env, NULL, &txn, 0);
if (rc) {
rc = -rc;
goto out;
}
rc = dbfs_inode_read(txn, ino_n, &ino);
if (rc)
goto out_txn;
rc = txn->commit(txn, 0);
if (rc) {
dbfs_inode_free(ino);
rc = -rc;
goto out;
}
mode = GUINT32_FROM_LE(ino->raw_inode->mode);
uid = GUINT32_FROM_LE(ino->raw_inode->uid);
gid = GUINT32_FROM_LE(ino->raw_inode->gid);
if (uid == ctx->uid)
mode >>= 8;
else if (gid == ctx->gid)
mode >>= 4;
rc = 0;
if ((mask & R_OK) && (!(mode & S_IROTH)))
rc = -EACCES;
if ((mask & W_OK) && (!(mode & S_IWOTH)))
rc = -EACCES;
if ((mask & X_OK) && (!(mode & S_IXOTH)))
rc = -EACCES;
dbfs_inode_free(ino);
out:
fuse_reply_err(req, -rc);
if (debugging)
syslog(LOG_DEBUG, "EXIT dbfs_op_access, rc==%d", -rc);
return;
out_txn:
txn->abort(txn);
goto out;
}
static struct fuse_lowlevel_ops dbfs_ops = {
.init = dbfs_op_init,
.destroy = dbfs_op_destroy,
.lookup = dbfs_op_lookup,
.forget = NULL,
.getattr = dbfs_op_getattr,
.setattr = dbfs_op_setattr,
.readlink = dbfs_op_readlink,
.mknod = dbfs_op_mknod,
.mkdir = dbfs_op_mkdir,
.unlink = dbfs_op_unlink,
.rmdir = dbfs_op_rmdir,
.symlink = dbfs_op_symlink,
.rename = dbfs_op_rename,
.link = dbfs_op_link,
.open = dbfs_op_open,
.read = dbfs_op_read,
.write = dbfs_op_write,
.flush = NULL,
.release = NULL,
.fsync = dbfs_op_fsync,
.opendir = dbfs_op_opendir,
.readdir = dbfs_op_readdir,
.releasedir = dbfs_op_releasedir,
.fsyncdir = dbfs_op_fsyncdir,
.statfs = dbfs_op_statfs,
.setxattr = dbfs_op_setxattr,
.getxattr = dbfs_op_getxattr,
.listxattr = dbfs_op_listxattr,
.removexattr = dbfs_op_removexattr,
.access = dbfs_op_access,
.create = NULL,
};
/* stock main() from FUSE example */
int main(int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_chan *ch;
char *mountpoint;
int err = -1;
openlog("dbfs", LOG_PID, LOG_LOCAL4);
if (fuse_parse_cmdline(&args, &mountpoint, NULL, NULL) != -1 &&
(ch = fuse_mount(mountpoint, &args)) != NULL) {
struct fuse_session *se;
se = fuse_lowlevel_new(&args, &dbfs_ops,
sizeof(dbfs_ops), NULL);
if (se != NULL) {
if (fuse_set_signal_handlers(se) != -1) {
fuse_session_add_chan(se, ch);
err = fuse_session_loop(se);
fuse_remove_signal_handlers(se);
fuse_session_remove_chan(ch);
}
fuse_session_destroy(se);
}
fuse_unmount(mountpoint, ch);
}
fuse_opt_free_args(&args);
return err ? 1 : 0;
}