/* * FUSE wrapper for the littlefs * * Copyright (c) 2017 Christopher Haster * Distributed under the MIT license */ #define FUSE_USE_VERSION 26 #ifdef linux // needed for a few things fuse depends on #define _XOPEN_SOURCE 700 #endif #include #include "lfs.h" #include "lfs_util.h" #include "lfs_fuse_bd.h" #include #include #include #include #include #include // config and other state static struct lfs_config config = {0}; static const char *device = NULL; static bool format = false; static lfs_t lfs; // actual fuse functions void lfs_fuse_defaults(struct lfs_config *config) { // defaults, ram is less of a concern here than what // littlefs is used to, so these may end up a bit funny if (!config->lookahead) { config->lookahead = 8192; } if (!config->prog_size) { config->prog_size = config->block_size; } if (!config->read_size) { config->read_size = config->prog_size; } } void *lfs_fuse_init(struct fuse_conn_info *conn) { // set that we want to take care of O_TRUNC conn->want |= FUSE_CAP_ATOMIC_O_TRUNC; // we also support writes of any size conn->want |= FUSE_CAP_BIG_WRITES; return 0; } int lfs_fuse_format(void) { int err = lfs_fuse_bd_create(&config, device); if (err) { return err; } lfs_fuse_defaults(&config); err = lfs_format(&lfs, &config); lfs_fuse_bd_destroy(&config); return err; } int lfs_fuse_mount(void) { int err = lfs_fuse_bd_create(&config, device); if (err) { return err; } lfs_fuse_defaults(&config); return lfs_mount(&lfs, &config); } void lfs_fuse_destroy(void *eh) { lfs_unmount(&lfs); lfs_fuse_bd_destroy(&config); } static int lfs_fuse_statfs_count(void *p, lfs_block_t b) { *(lfs_size_t *)p += 1; return 0; } int lfs_fuse_statfs(const char *path, struct statvfs *s) { memset(s, 0, sizeof(struct statvfs)); lfs_size_t in_use = 0; int err = lfs_traverse(&lfs, lfs_fuse_statfs_count, &in_use); if (err) { return err; } s->f_bsize = config.block_size; s->f_frsize = config.block_size; s->f_blocks = config.block_count; s->f_bfree = config.block_count - in_use; s->f_bavail = config.block_count - in_use; s->f_namemax = LFS_NAME_MAX; return 0; } static void lfs_fuse_tostat(struct stat *s, struct lfs_info *info) { memset(s, 0, sizeof(struct stat)); s->st_size = info->size; s->st_mode = S_IRWXU | S_IRWXG | S_IRWXO; switch (info->type) { case LFS_TYPE_DIR: s->st_mode |= S_IFDIR; break; case LFS_TYPE_REG: s->st_mode |= S_IFREG; break; } } int lfs_fuse_getattr(const char *path, struct stat *s) { struct lfs_info info; int err = lfs_stat(&lfs, path, &info); if (err) { return err; } lfs_fuse_tostat(s, &info); return 0; } int lfs_fuse_access(const char *path, int mask) { struct lfs_info info; return lfs_stat(&lfs, path, &info); } int lfs_fuse_mkdir(const char *path, mode_t mode) { return lfs_mkdir(&lfs, path); } int lfs_fuse_opendir(const char *path, struct fuse_file_info *fi) { lfs_dir_t *dir = malloc(sizeof(lfs_dir_t)); memset(dir, 0, sizeof(lfs_dir_t)); int err = lfs_dir_open(&lfs, dir, path); if (err) { free(dir); return err; } fi->fh = (uint64_t)dir; return 0; } int lfs_fuse_releasedir(const char *path, struct fuse_file_info *fi) { lfs_dir_t *dir = (lfs_dir_t*)fi->fh; int err = lfs_dir_close(&lfs, dir); free(dir); return err; } int lfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { lfs_dir_t *dir = (lfs_dir_t*)fi->fh; struct stat s; struct lfs_info info; while (true) { int err = lfs_dir_read(&lfs, dir, &info); if (err != 1) { return err; } lfs_fuse_tostat(&s, &info); filler(buf, info.name, &s, 0); } } int lfs_fuse_rename(const char *from, const char *to) { return lfs_rename(&lfs, from, to); } int lfs_fuse_unlink(const char *path) { return lfs_remove(&lfs, path); } int lfs_fuse_open(const char *path, struct fuse_file_info *fi) { lfs_file_t *file = malloc(sizeof(lfs_file_t)); memset(file, 0, sizeof(lfs_file_t)); int flags = 0; if ((fi->flags & 3) == O_RDONLY) flags |= LFS_O_RDONLY; if ((fi->flags & 3) == O_WRONLY) flags |= LFS_O_WRONLY; if ((fi->flags & 3) == O_RDWR) flags |= LFS_O_RDWR; if (fi->flags & O_CREAT) flags |= LFS_O_CREAT; if (fi->flags & O_EXCL) flags |= LFS_O_EXCL; if (fi->flags & O_TRUNC) flags |= LFS_O_TRUNC; if (fi->flags & O_APPEND) flags |= LFS_O_APPEND; int err = lfs_file_open(&lfs, file, path, flags); if (err) { free(file); return err; } fi->fh = (uint64_t)file; return 0; } int lfs_fuse_release(const char *path, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; int err = lfs_file_close(&lfs, file); free(file); return err; } int lfs_fuse_fgetattr(const char *path, struct stat *s, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; lfs_fuse_tostat(s, &(struct lfs_info){ .size = lfs_file_size(&lfs, file), .type = LFS_TYPE_REG, }); return 0; } int lfs_fuse_read(const char *path, char *buf, size_t size, off_t off, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; if (lfs_file_tell(&lfs, file) != off) { lfs_soff_t soff = lfs_file_seek(&lfs, file, off, LFS_SEEK_SET); if (soff < 0) { return soff; } } return lfs_file_read(&lfs, file, buf, size); } int lfs_fuse_write(const char *path, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; if (lfs_file_tell(&lfs, file) != off) { lfs_soff_t soff = lfs_file_seek(&lfs, file, off, LFS_SEEK_SET); if (soff < 0) { return soff; } } return lfs_file_write(&lfs, file, buf, size); } int lfs_fuse_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; return lfs_file_sync(&lfs, file); } int lfs_fuse_flush(const char *path, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; return lfs_file_sync(&lfs, file); } int lfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) { int err = lfs_fuse_open(path, fi); if (err) { return err; } return lfs_fuse_fsync(path, 0, fi); } int lfs_fuse_ftruncate(const char *path, off_t size, struct fuse_file_info *fi) { lfs_file_t *file = (lfs_file_t*)fi->fh; return lfs_file_truncate(&lfs, file, size); } int lfs_fuse_truncate(const char *path, off_t size) { lfs_file_t file; int err = lfs_file_open(&lfs, &file, path, LFS_O_WRONLY); if (err) { return err; } err = lfs_file_truncate(&lfs, &file, size); if (err) { return err; } return lfs_file_close(&lfs, &file); } // unsupported functions int lfs_fuse_link(const char *from, const char *to) { // not supported, fail return -EPERM; } int lfs_fuse_mknod(const char *path, mode_t mode, dev_t dev) { // not supported, fail return -EPERM; } int lfs_fuse_chmod(const char *path, mode_t mode) { // not supported, always succeed return 0; } int lfs_fuse_chown(const char *path, uid_t uid, gid_t gid) { // not supported, fail return -EPERM; } int lfs_fuse_utimens(const char *path, const struct timespec ts[2]) { // not supported, always succeed return 0; } static struct fuse_operations lfs_fuse_ops = { .init = lfs_fuse_init, .destroy = lfs_fuse_destroy, .statfs = lfs_fuse_statfs, .getattr = lfs_fuse_getattr, .access = lfs_fuse_access, .mkdir = lfs_fuse_mkdir, .rmdir = lfs_fuse_unlink, .opendir = lfs_fuse_opendir, .releasedir = lfs_fuse_releasedir, .readdir = lfs_fuse_readdir, .rename = lfs_fuse_rename, .unlink = lfs_fuse_unlink, .open = lfs_fuse_open, .create = lfs_fuse_create, .truncate = lfs_fuse_truncate, .release = lfs_fuse_release, .fgetattr = lfs_fuse_fgetattr, .read = lfs_fuse_read, .write = lfs_fuse_write, .fsync = lfs_fuse_fsync, .flush = lfs_fuse_flush, .link = lfs_fuse_link, .symlink = lfs_fuse_link, .mknod = lfs_fuse_mknod, .chmod = lfs_fuse_chmod, .chown = lfs_fuse_chown, .utimens = lfs_fuse_utimens, }; // binding into fuse and general ui enum lfs_fuse_keys { KEY_HELP, KEY_VERSION, KEY_FORMAT, }; #define OPT(t, p) { t, offsetof(struct lfs_config, p), 0} static struct fuse_opt lfs_fuse_opts[] = { FUSE_OPT_KEY("--format", KEY_FORMAT), OPT("-b=%" SCNu32, block_size), OPT("--block_size=%" SCNu32, block_size), OPT("--block_count=%" SCNu32, block_count), OPT("--read_size=%" SCNu32, read_size), OPT("--prog_size=%" SCNu32, prog_size), OPT("--lookahead=%" SCNu32, lookahead), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_END }; static const char help_text[] = "usage: %s [options] device mountpoint\n" "\n" "general options:\n" " -o opt,[opt...] FUSE options\n" " -h --help print help\n" " -V --version print version\n" "\n" "littlefs options:\n" " --format format instead of mounting\n" " -b --block_size logical block size, overrides the block device\n" " --block_count block count, overrides the block device\n" " --read_size readable unit (block_size)\n" " --prog_size programmable unit (block_size)\n" " --lookahead size of lookahead buffer (8192)\n" "\n"; int lfs_fuse_opt_proc(void *data, const char *arg, int key, struct fuse_args *args) { // option parsing switch (key) { case FUSE_OPT_KEY_NONOPT: if (!device) { device = strdup(arg); return 0; } break; case KEY_FORMAT: format = true; return 0; case KEY_HELP: fprintf(stderr, help_text, args->argv[0]); fuse_opt_add_arg(args, "-ho"); fuse_main(args->argc, args->argv, &lfs_fuse_ops, NULL); exit(1); case KEY_VERSION: fprintf(stderr, "littlefs version: v%d.%d\n", LFS_VERSION_MAJOR, LFS_VERSION_MINOR); fprintf(stderr, "littlefs disk version: v%d.%d\n", LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); fuse_opt_add_arg(args, "--version"); fuse_main(args->argc, args->argv, &lfs_fuse_ops, NULL); exit(0); } return 1; } int main(int argc, char *argv[]) { // parse custom options struct fuse_args args = FUSE_ARGS_INIT(argc, argv); fuse_opt_parse(&args, &config, lfs_fuse_opts, lfs_fuse_opt_proc); if (!device) { fprintf(stderr, "missing device parameter\n"); exit(1); } if (format) { // format time, no mount int err = lfs_fuse_format(); if (err) { LFS_ERROR("%s", strerror(-err)); exit(-err); } exit(0); } // go ahead and mount so errors are reported before backgrounding int err = lfs_fuse_mount(); if (err) { LFS_ERROR("%s", strerror(-err)); exit(-err); } // always single-threaded fuse_opt_add_arg(&args, "-s"); // enter fuse err = fuse_main(args.argc, args.argv, &lfs_fuse_ops, NULL); if (err) { lfs_fuse_destroy(NULL); } return err; }