diff --git a/Makefile b/Makefile index c6daadc8..2ec19e7f 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ CFLAGS+=-std=gnu89 -O2 -g -MMD -Wall \ -D_LGPL_SOURCE \ -DRCU_MEMBARRIER \ -DZSTD_STATIC_LINKING_ONLY \ + -DFUSE_USE_VERSION=32 \ -DNO_BCACHEFS_CHARDEV \ -DNO_BCACHEFS_FS \ -DNO_BCACHEFS_SYSFS \ @@ -36,7 +37,7 @@ ifdef D CFLAGS+=-DCONFIG_BCACHEFS_DEBUG=y endif -PKGCONFIG_LIBS="blkid uuid liburcu libsodium zlib liblz4 libzstd" +PKGCONFIG_LIBS="blkid uuid liburcu libsodium zlib liblz4 libzstd fuse3" PKGCONFIG_CFLAGS:=$(shell $(PKG_CONFIG) --cflags $(PKGCONFIG_LIBS)) ifeq (,$(PKGCONFIG_CFLAGS)) diff --git a/bcachefs.c b/bcachefs.c index 9c503e00..8840d516 100644 --- a/bcachefs.c +++ b/bcachefs.c @@ -203,6 +203,9 @@ int main(int argc, char *argv[]) if (!strcmp(cmd, "setattr")) return cmd_setattr(argc, argv); + if (!strcmp(cmd, "fusemount")) + return cmd_fusemount(argc, argv); + if (!strcmp(cmd, "--help")) { usage(); return 0; diff --git a/cmd_fusemount.c b/cmd_fusemount.c new file mode 100644 index 00000000..6f9a9be8 --- /dev/null +++ b/cmd_fusemount.c @@ -0,0 +1,594 @@ +#include +#include +#include +#include +#include + +#include + +#include "cmds.h" +#include "libbcachefs.h" +#include "tools-util.h" + +#include "libbcachefs/bcachefs.h" +#include "libbcachefs/btree_iter.h" +#include "libbcachefs/buckets.h" +#include "libbcachefs/dirent.h" +#include "libbcachefs/error.h" +#include "libbcachefs/fs-common.h" +#include "libbcachefs/inode.h" +#include "libbcachefs/opts.h" +#include "libbcachefs/super.h" + +/* mode_to_type(): */ +#include "libbcachefs/fs.h" + +#include + +/* XXX cut and pasted from fsck.c */ +#define QSTR(n) { { { .len = strlen(n) } }, .name = n } + +static inline u64 map_root_ino(u64 ino) +{ + return ino == 1 ? 4096 : ino; +} + +static inline u64 unmap_root_ino(u64 ino) +{ + return ino == 4096 ? 1 : ino; +} + +static struct stat inode_to_stat(struct bch_fs *c, + struct bch_inode_unpacked *bi) +{ + return (struct stat) { + .st_size = bi->bi_size, + .st_mode = bi->bi_mode, + .st_uid = bi->bi_uid, + .st_gid = bi->bi_gid, + .st_nlink = bch2_inode_nlink_get(bi), + .st_rdev = bi->bi_dev, + .st_blksize = block_bytes(c), + .st_blocks = bi->bi_sectors, + .st_atim = bch2_time_to_timespec(c, bi->bi_atime), + .st_mtim = bch2_time_to_timespec(c, bi->bi_mtime), + .st_ctim = bch2_time_to_timespec(c, bi->bi_ctime), + }; +} + +static struct fuse_entry_param inode_to_entry(struct bch_fs *c, + struct bch_inode_unpacked *bi) +{ + return (struct fuse_entry_param) { + .ino = bi->bi_inum, + .generation = bi->bi_generation, + .attr = inode_to_stat(c, bi), + .attr_timeout = DBL_MAX, + .entry_timeout = DBL_MAX, + }; +} + +static void bcachefs_fuse_destroy(void *arg) +{ + struct bch_fs *c = arg; + + bch2_fs_stop(c); +} + +static void bcachefs_fuse_lookup(fuse_req_t req, fuse_ino_t dir, + const char *name) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked bi; + struct qstr qstr = QSTR(name); + u64 inum; + int ret; + + dir = map_root_ino(dir); + + pr_info("dir %llu name %s", (u64) dir, name); + + inum = bch2_dirent_lookup(c, dir, &qstr); + if (!inum) { + ret = -ENOENT; + goto err; + } + + ret = bch2_inode_find_by_inum(c, inum, &bi); + if (ret) + goto err; + + bi.bi_inum = unmap_root_ino(bi.bi_inum); + + struct fuse_entry_param e = inode_to_entry(c, &bi); + fuse_reply_entry(req, &e); + return; +err: + fuse_reply_err(req, -ret); +} + +static void bcachefs_fuse_getattr(fuse_req_t req, fuse_ino_t inum, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked bi; + struct stat attr; + int ret; + + inum = map_root_ino(inum); + + pr_info("inum %llu", (u64) inum); + + ret = bch2_inode_find_by_inum(c, inum, &bi); + if (ret) { + fuse_reply_err(req, -ret); + return; + } + + bi.bi_inum = unmap_root_ino(bi.bi_inum); + + attr = inode_to_stat(c, &bi); + fuse_reply_attr(req, &attr, DBL_MAX); +} + +static void bcachefs_fuse_setattr(fuse_req_t req, fuse_ino_t inum, + struct stat *attr, int to_set, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked inode_u; + struct btree_trans trans; + struct btree_iter *iter; + u64 now; + int ret; + + inum = map_root_ino(inum); + + bch2_trans_init(&trans, c, 0, 0); +retry: + bch2_trans_begin(&trans); + now = bch2_current_time(c); + + iter = bch2_inode_peek(&trans, &inode_u, inum, BTREE_ITER_INTENT); + ret = PTR_ERR_OR_ZERO(iter); + if (ret) + goto err; + + if (to_set & FUSE_SET_ATTR_MODE) + inode_u.bi_mode = attr->st_mode; + if (to_set & FUSE_SET_ATTR_UID) + inode_u.bi_uid = attr->st_uid; + if (to_set & FUSE_SET_ATTR_GID) + inode_u.bi_gid = attr->st_gid; + if (to_set & FUSE_SET_ATTR_SIZE) + inode_u.bi_size = attr->st_size; + if (to_set & FUSE_SET_ATTR_ATIME) + inode_u.bi_atime = timespec_to_bch2_time(c, attr->st_atim); + if (to_set & FUSE_SET_ATTR_MTIME) + inode_u.bi_mtime = timespec_to_bch2_time(c, attr->st_mtim); + if (to_set & FUSE_SET_ATTR_ATIME_NOW) + inode_u.bi_atime = now; + if (to_set & FUSE_SET_ATTR_MTIME_NOW) + inode_u.bi_mtime = now; + + ret = bch2_inode_write(&trans, iter, &inode_u) ?: + bch2_trans_commit(&trans, NULL, NULL, + BTREE_INSERT_ATOMIC| + BTREE_INSERT_NOFAIL); +err: + if (ret == -EINTR) + goto retry; + + bch2_trans_exit(&trans); + + if (!ret) { + *attr = inode_to_stat(c, &inode_u); + fuse_reply_attr(req, attr, DBL_MAX); + } else { + fuse_reply_err(req, -ret); + } +} + +static void bcachefs_fuse_readlink(fuse_req_t req, fuse_ino_t inum) +{ + //struct bch_fs *c = fuse_req_userdata(req); + + //char *link = malloc(); + + //fuse_reply_readlink(req, link); +} + +static int do_create(struct bch_fs *c, u64 dir, + const char *name, mode_t mode, dev_t rdev, + struct bch_inode_unpacked *new_inode) +{ + struct qstr qstr = QSTR(name); + struct bch_inode_unpacked dir_u; + + dir = map_root_ino(dir); + + bch2_inode_init_early(c, new_inode); + + return bch2_trans_do(c, NULL, 0, + bch2_create_trans(&trans, + dir, &dir_u, + new_inode, &qstr, + 0, 0, mode, rdev, NULL, NULL)); +} + +static void bcachefs_fuse_mknod(fuse_req_t req, fuse_ino_t dir, + const char *name, mode_t mode, + dev_t rdev) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked new_inode; + int ret; + + ret = do_create(c, dir, name, mode, rdev, &new_inode); + if (ret) + goto err; + + struct fuse_entry_param e = inode_to_entry(c, &new_inode); + fuse_reply_entry(req, &e); + return; +err: + fuse_reply_err(req, -ret); +} + +static void bcachefs_fuse_mkdir(fuse_req_t req, fuse_ino_t dir, + const char *name, mode_t mode) +{ + bcachefs_fuse_mknod(req, dir, name, mode, 0); +} + +static void bcachefs_fuse_unlink(fuse_req_t req, fuse_ino_t dir, + const char *name) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked dir_u, inode_u; + struct qstr qstr = QSTR(name); + int ret; + + dir = map_root_ino(dir); + + ret = bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC|BTREE_INSERT_NOFAIL, + bch2_unlink_trans(&trans, dir, &dir_u, + &inode_u, &qstr)); + + fuse_reply_err(req, -ret); +} + +static void bcachefs_fuse_rmdir(fuse_req_t req, fuse_ino_t dir, + const char *name) +{ + dir = map_root_ino(dir); + + bcachefs_fuse_unlink(req, dir, name); +} + +#if 0 +static void bcachefs_fuse_symlink(fuse_req_t req, const char *link, + fuse_ino_t parent, const char *name) +{ + struct bch_fs *c = fuse_req_userdata(req); +} +#endif + +static void bcachefs_fuse_rename(fuse_req_t req, + fuse_ino_t src_dir, const char *srcname, + fuse_ino_t dst_dir, const char *dstname, + unsigned flags) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked dst_dir_u, src_dir_u; + struct bch_inode_unpacked src_inode_u, dst_inode_u; + struct qstr dst_name = QSTR(srcname); + struct qstr src_name = QSTR(dstname); + int ret; + + src_dir = map_root_ino(src_dir); + dst_dir = map_root_ino(dst_dir); + + /* XXX handle overwrites */ + ret = bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC, + bch2_rename_trans(&trans, + src_dir, &src_dir_u, + dst_dir, &dst_dir_u, + &src_inode_u, &dst_inode_u, + &src_name, &dst_name, + BCH_RENAME)); + + fuse_reply_err(req, -ret); +} + +static void bcachefs_fuse_link(fuse_req_t req, fuse_ino_t inum, + fuse_ino_t newparent, const char *newname) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked inode_u; + struct qstr qstr = QSTR(newname); + int ret; + + ret = bch2_trans_do(c, NULL, BTREE_INSERT_ATOMIC, + bch2_link_trans(&trans, newparent, + inum, &inode_u, &qstr)); + + if (!ret) { + struct fuse_entry_param e = inode_to_entry(c, &inode_u); + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, -ret); + } +} + +#if 0 +static void bcachefs_fuse_open(fuse_req_t req, fuse_ino_t inum, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_read(fuse_req_t req, fuse_ino_t inum, + size_t size, off_t off, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_flush(fuse_req_t req, fuse_ino_t inum, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_release(fuse_req_t req, fuse_ino_t inum, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_fsync(fuse_req_t req, fuse_ino_t inum, int datasync, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_opendir(fuse_req_t req, fuse_ino_t inum, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} +#endif + +struct fuse_dir_context { + struct dir_context ctx; + fuse_req_t req; + char *buf; + size_t bufsize; +}; + +static int fuse_filldir(struct dir_context *_ctx, + const char *name, int namelen, + loff_t pos, u64 dir, unsigned type) +{ + struct fuse_dir_context *ctx = + container_of(_ctx, struct fuse_dir_context, ctx); + + struct stat statbuf = { + .st_ino = map_root_ino(dir), + .st_mode = type << 12, + }; + + size_t len = fuse_add_direntry(ctx->req, + ctx->buf, + ctx->bufsize, + name, + &statbuf, + pos + 1); + + if (len > ctx->bufsize) + return 0; + + ctx->buf += len; + ctx->bufsize -= len; + return 1; +} + +static void bcachefs_fuse_readdir(fuse_req_t req, fuse_ino_t dir, + size_t size, off_t off, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); + char buf[4096]; + struct fuse_dir_context ctx = { + .ctx.actor = fuse_filldir, + .ctx.pos = off, + .req = req, + .buf = buf, + .bufsize = sizeof(buf), + }; + int ret; + + dir = map_root_ino(dir); + + ret = bch2_readdir(c, dir, &ctx.ctx); + if (!ret) { + fuse_reply_buf(req, buf, ctx.buf - buf); + } else { + fuse_reply_err(req, -ret); + } +} + +#if 0 +static void bcachefs_fuse_releasedir(fuse_req_t req, fuse_ino_t inum, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_fsyncdir(fuse_req_t req, fuse_ino_t inum, int datasync, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} +#endif + +static void bcachefs_fuse_statfs(fuse_req_t req, fuse_ino_t inum) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_fs_usage_short usage = bch2_fs_usage_read_short(c); + unsigned shift = c->block_bits; + struct statvfs statbuf = { + .f_bsize = block_bytes(c), + .f_frsize = block_bytes(c), + .f_blocks = usage.capacity >> shift, + .f_bfree = (usage.capacity - usage.used) >> shift, + //.f_bavail = statbuf.f_bfree, + .f_files = usage.nr_inodes, + .f_ffree = U64_MAX, + .f_namemax = BCH_NAME_MAX, + }; + + fuse_reply_statfs(req, &statbuf); +} + +#if 0 +static void bcachefs_fuse_setxattr(fuse_req_t req, fuse_ino_t inum, + const char *name, const char *value, + size_t size, int flags) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_getxattr(fuse_req_t req, fuse_ino_t inum, + const char *name, size_t size) +{ + struct bch_fs *c = fuse_req_userdata(req); + + fuse_reply_xattr(req, ); +} + +static void bcachefs_fuse_listxattr(fuse_req_t req, fuse_ino_t inum, size_t size) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_removexattr(fuse_req_t req, fuse_ino_t inum, + const char *name) +{ + struct bch_fs *c = fuse_req_userdata(req); +} +#endif + +static void bcachefs_fuse_create(fuse_req_t req, fuse_ino_t dir, + const char *name, mode_t mode, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); + struct bch_inode_unpacked new_inode; + int ret; + + ret = do_create(c, dir, name, mode, 0, &new_inode); + if (ret) + goto err; + + struct fuse_entry_param e = inode_to_entry(c, &new_inode); + fuse_reply_create(req, &e, fi); + return; +err: + fuse_reply_err(req, -ret); + +} + +#if 0 +static void bcachefs_fuse_write_buf(fuse_req_t req, fuse_ino_t inum, + struct fuse_bufvec *bufv, off_t off, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} + +static void bcachefs_fuse_fallocate(fuse_req_t req, fuse_ino_t inum, int mode, + off_t offset, off_t length, + struct fuse_file_info *fi) +{ + struct bch_fs *c = fuse_req_userdata(req); +} +#endif + +static const struct fuse_lowlevel_ops bcachefs_fuse_ops = { + .destroy = bcachefs_fuse_destroy, + .lookup = bcachefs_fuse_lookup, + .getattr = bcachefs_fuse_getattr, + .setattr = bcachefs_fuse_setattr, + .readlink = bcachefs_fuse_readlink, + .mknod = bcachefs_fuse_mknod, + .mkdir = bcachefs_fuse_mkdir, + .unlink = bcachefs_fuse_unlink, + .rmdir = bcachefs_fuse_rmdir, + //.symlink = bcachefs_fuse_symlink, + .rename = bcachefs_fuse_rename, + .link = bcachefs_fuse_link, + //.open = bcachefs_fuse_open, + //.read = bcachefs_fuse_read, + //.write = bcachefs_fuse_write, + //.flush = bcachefs_fuse_flush, + //.release = bcachefs_fuse_release, + //.fsync = bcachefs_fuse_fsync, + //.opendir = bcachefs_fuse_opendir, + .readdir = bcachefs_fuse_readdir, + //.releasedir = bcachefs_fuse_releasedir, + //.fsyncdir = bcachefs_fuse_fsyncdir, + .statfs = bcachefs_fuse_statfs, + //.setxattr = bcachefs_fuse_setxattr, + //.getxattr = bcachefs_fuse_getxattr, + //.listxattr = bcachefs_fuse_listxattr, + //.removexattr = bcachefs_fuse_removexattr, + .create = bcachefs_fuse_create, + + /* posix locks: */ +#if 0 + .getlk = bcachefs_fuse_getlk, + .setlk = bcachefs_fuse_setlk, +#endif + //.write_buf = bcachefs_fuse_write_buf, + //.fallocate = bcachefs_fuse_fallocate, + +}; + +int cmd_fusemount(int argc, char *argv[]) +{ + struct bch_opts bch_opts = bch2_opts_empty(); + struct bch_fs *c = NULL; + + c = bch2_fs_open(argv + optind, argc - optind, bch_opts); + if (IS_ERR(c)) + die("error opening %s: %s", argv[optind], + strerror(-PTR_ERR(c))); + + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_cmdline_opts fuse_opts; + if (fuse_parse_cmdline(&args, &fuse_opts) < 0) + die("fuse_parse_cmdline err: %m"); + + struct fuse_session *se = + fuse_session_new(&args, &bcachefs_fuse_ops, + sizeof(bcachefs_fuse_ops), c); + if (!se) + die("fuse_lowlevel_new err: %m"); + + if (fuse_set_signal_handlers(se) < 0) + die("fuse_set_signal_handlers err: %m"); + + if (fuse_session_mount(se, "/home/kent/mnt")) + die("fuse_mount err: %m"); + + int ret = fuse_session_loop(se); + + fuse_session_unmount(se); + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/cmds.h b/cmds.h index d64ffeeb..230ad5b0 100644 --- a/cmds.h +++ b/cmds.h @@ -47,4 +47,6 @@ int cmd_version(int argc, char *argv[]); int cmd_setattr(int argc, char *argv[]); +int cmd_fusemount(int argc, char *argv[]); + #endif /* _CMDS_H */