blob: 1d4f7ab496a05bc72754e5b3ad5ab199c3a1a914 [file] [log] [blame]
#define FUSE_USE_VERSION 25
#include <fuse_lowlevel.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <glib.h>
#include <db.h>
#include "dbfs.h"
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);
/* 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 dbfs **fs_io = userdata;
struct dbfs *fs;
int rc;
fs = dbfs_new();
rc = dbfs_open(fs);
if (rc)
abort(); /* TODO: improve */
*fs_io = fs;
}
static void dbfs_op_destroy(void *userdata)
{
struct dbfs **fs_io = userdata;
struct dbfs *fs = *fs_io;
dbfs_close(fs);
dbfs_free(fs);
*fs_io = NULL;
}
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;
/* lookup inode in parent directory */
rc = dbfs_dir_lookup(parent, name, &ino_n);
if (rc) {
fuse_reply_err(req, -rc);
return;
}
rc = dbfs_inode_read(ino_n, &ino);
if (rc) {
fuse_reply_err(req, -rc);
return;
}
/* send reply */
dbfs_reply_ino(req, ino);
}
static void dbfs_op_getattr(fuse_req_t req, fuse_ino_t ino_n,
struct fuse_file_info *fi)
{
struct dbfs_inode *ino = NULL;
struct stat st;
int rc;
/* read inode from database */
rc = dbfs_inode_read(ino_n, &ino);
if (rc) {
fuse_reply_err(req, ENOENT);
return;
}
/* fill in stat buf, taking care to convert from
* little endian to native endian
*/
memset(&st, 0, sizeof(st));
st.st_dev = 1;
st.st_ino = ino_n;
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);
/* send result back to FUSE */
fuse_reply_attr(req, &st, 2.0);
dbfs_inode_free(ino);
}
static void dbfs_op_readlink(fuse_req_t req, fuse_ino_t ino)
{
int rc;
DBT val;
char *s;
/* read link from database */
rc = dbfs_symlink_read(ino, &val);
if (rc) {
fuse_reply_err(req, -rc);
return;
}
/* send reply; use g_strndup to append a trailing null */
s = g_strndup(val.data, val.size);
fuse_reply_readlink(req, s);
g_free(s);
free(val.data);
}
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;
rc = dbfs_mode_validate(mode);
if (rc) {
fuse_reply_err(req, -rc);
return;
}
rc = dbfs_mknod(parent, name, mode, rdev, &ino);
if (rc) {
fuse_reply_err(req, -rc);
return;
}
dbfs_reply_ino(req, ino);
}
static void dbfs_op_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
mode_t mode)
{
mode &= (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX);
mode |= S_IFDIR;
return dbfs_op_mknod(req, parent, name, mode, 0);
}
static void dbfs_op_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
{
int rc = dbfs_unlink(parent, name, 0);
if (rc)
fuse_reply_err(req, -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;
/* get inode number associated with name */
rc = dbfs_dir_lookup(parent, name, &ino_n);
if (rc)
goto err_out;
/* read dir associated with name */
rc = dbfs_dir_read(ino_n, &val);
if (rc)
goto err_out;
/* 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 err_out;
/* dir is empty, go ahead and unlink */
rc = dbfs_unlink(parent, name, DBFS_UNLINK_DIR);
if (rc)
goto err_out;
return;
err_out:
fuse_reply_err(req, -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;
if (!g_utf8_validate(link, -1, NULL)) {
rc = -EINVAL;
goto err_out;
}
rc = dbfs_mknod(parent, name,
S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO, 0, &ino);
if (rc)
goto err_out;
rc = dbfs_symlink_write(GUINT64_FROM_LE(ino->raw_inode->ino), link);
if (rc)
goto err_out_mknod;
dbfs_reply_ino(req, ino);
return;
err_out_mknod:
dbfs_inode_del(ino);
dbfs_inode_free(ino);
err_out:
fuse_reply_err(req, -rc);
}
static void dbfs_op_opendir(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi)
{
DBT val;
int rc;
/* read directory from database */
rc = dbfs_dir_read(ino, &val);
if (rc) {
fuse_reply_err(req, -rc);
return;
}
/* save for later use */
fi->fh = (uint64_t) (unsigned long) val.data;
/* send reply */
fuse_reply_open(req, fi);
}
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;
/* release directory contents */
free(p);
}
struct dirbuf {
char *p;
size_t size;
};
/* stock function copied from FUSE template */
static void dirbuf_add(struct dirbuf *b, const char *name, fuse_ino_t ino)
{
struct stat stbuf;
size_t oldsize = b->size;
b->size += fuse_dirent_size(strlen(name));
b->p = (char *)realloc(b->p, b->size);
memset(&stbuf, 0, sizeof(stbuf));
stbuf.st_ino = ino;
fuse_add_dirent(b->p + 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 char *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 *b = userdata;
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(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 b;
void *p;
/* grab directory contents stored by opendir */
p = (void *) (unsigned long) fi->fh;
/* iterate through each dirent, filling dirbuf */
memset(&b, 0, sizeof(b));
dbfs_dir_foreach(p, dbfs_fill_dirbuf, &b);
/* send reply */
reply_buf_limited(req, b.p, b.size, off, size);
free(b.p);
}
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 = NULL,
.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 = NULL,
.link = NULL,
.open = NULL,
.read = NULL,
.write = NULL,
.flush = NULL,
.release = NULL,
.fsync = NULL,
.opendir = dbfs_op_opendir,
.readdir = dbfs_op_readdir,
.releasedir = dbfs_op_releasedir,
.fsyncdir = NULL,
.statfs = NULL,
.setxattr = NULL,
.getxattr = NULL,
.listxattr = NULL,
.removexattr = NULL,
.access = NULL,
.create = NULL,
};
/* stock main() from FUSE example */
int main(int argc, char *argv[])
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
char *mountpoint;
int err = -1;
int fd;
if (fuse_parse_cmdline(&args, &mountpoint, NULL, NULL) != -1 &&
(fd = fuse_mount(mountpoint, &args)) != -1) {
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) {
struct fuse_chan *ch = fuse_kern_chan_new(fd);
if (ch != NULL) {
fuse_session_add_chan(se, ch);
err = fuse_session_loop(se);
}
fuse_remove_signal_handlers(se);
}
fuse_session_destroy(se);
}
close(fd);
}
fuse_unmount(mountpoint);
fuse_opt_free_args(&args);
return err ? 1 : 0;
}