mirror of
https://github.com/littlefs-project/littlefs-fuse.git
synced 2025-10-14 19:18:19 +08:00
483 lines
12 KiB
C
483 lines
12 KiB
C
/*
|
|
* 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 <fuse/fuse.h>
|
|
#include "lfs.h"
|
|
#include "lfs_util.h"
|
|
#include "lfs_fuse_bd.h"
|
|
|
|
#include <stdio.h>
|
|
#include <inttypes.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
|
|
// 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;
|
|
}
|