├── .gitignore ├── lfs_fuse_bd.h ├── LICENSE.md ├── Makefile ├── .travis.yml ├── lfs_fuse_bd.c ├── README.md └── lfs_fuse.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *.a 4 | lfs 5 | 6 | # submodule 7 | littlefs 8 | stamp-littlefs 9 | -------------------------------------------------------------------------------- /lfs_fuse_bd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Linux user-space block device wrapper 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef LFS_FUSE_BD_H 8 | #define LFS_FUSE_BD_H 9 | 10 | #include "lfs.h" 11 | 12 | 13 | // Create a block device with path to dev block device 14 | int lfs_fuse_bd_create(struct lfs_config *cfg, const char *path); 15 | 16 | // Clean up memory associated with emu block device 17 | void lfs_fuse_bd_destroy(const struct lfs_config *cfg); 18 | 19 | // Read a block 20 | int lfs_fuse_bd_read(const struct lfs_config *cfg, lfs_block_t block, 21 | lfs_off_t off, void *buffer, lfs_size_t size); 22 | 23 | // Program a block 24 | // 25 | // The block must have previously been erased. 26 | int lfs_fuse_bd_prog(const struct lfs_config *cfg, lfs_block_t block, 27 | lfs_off_t off, const void *buffer, lfs_size_t size); 28 | 29 | // Erase a block 30 | // 31 | // A block must be erased before being programmed. The 32 | // state of an erased block is undefined. 33 | int lfs_fuse_bd_erase(const struct lfs_config *cfg, lfs_block_t block); 34 | 35 | // Sync the block device 36 | int lfs_fuse_bd_sync(const struct lfs_config *cfg); 37 | 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Arm Limited. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | - Redistributions in binary form must reproduce the above copyright notice, this 9 | list of conditions and the following disclaimer in the documentation and/or 10 | other materials provided with the distribution. 11 | - Neither the name of ARM nor the names of its contributors may be used to 12 | endorse or promote products derived from this software without specific prior 13 | written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET = lfs 2 | 3 | OS := $(shell uname -s) 4 | 5 | CC = cc 6 | AR = ar 7 | SIZE = size 8 | 9 | SRC += littlefs/lfs.c littlefs/lfs_util.c 10 | SRC += lfs_fuse.c lfs_fuse_bd.c 11 | OBJ := $(SRC:.c=.o) 12 | DEP := $(SRC:.c=.d) 13 | 14 | ifdef DEBUG 15 | override CFLAGS += -O0 -g3 16 | else 17 | override CFLAGS += -Os 18 | endif 19 | ifdef WORD 20 | override CFLAGS += -m$(WORD) 21 | endif 22 | override CFLAGS += -I. -Ilittlefs 23 | override CFLAGS += -std=c99 -Wall -pedantic 24 | override CFLAGS += -D_FILE_OFFSET_BITS=64 25 | override CFLAGS += -D_XOPEN_SOURCE=700 26 | override CFLAGS += -DLFS_MIGRATE 27 | 28 | ifeq ($(OS), Darwin) 29 | override CFLAGS += -I /usr/local/include/osxfuse 30 | override LFLAGS += -L /usr/local/lib 31 | override LFLAGS += -losxfuse 32 | else 33 | override LFLAGS += -lfuse 34 | endif 35 | 36 | ifeq ($(OS), FreeBSD) 37 | override CFLAGS += -I /usr/local/include 38 | override CFLAGS += -D __BSD_VISIBLE 39 | override LFLAGS += -L /usr/local/lib 40 | endif 41 | 42 | all: stamp-littlefs 43 | 44 | stamp-littlefs: 45 | git clone https://github.com/littlefs-project/littlefs 46 | @test -f littlefs/lfs.c || (echo No littlefs found.; exit 1) 47 | @test -f littlefs/lfs_util.c || (echo Incomplete littlefs implementation.; exit 1) 48 | touch $@ littlefs 49 | $(MAKE) $(TARGET) 50 | 51 | size: $(OBJ) 52 | $(SIZE) -t $^ 53 | 54 | -include $(DEP) 55 | 56 | $(TARGET): $(OBJ) 57 | $(CC) $(CFLAGS) $^ $(LFLAGS) -o $@ 58 | 59 | %.a: $(OBJ) 60 | $(AR) rcs $@ $^ 61 | 62 | %.o: %.c 63 | $(CC) -c -MMD $(CFLAGS) $< -o $@ 64 | 65 | clean: 66 | rm -f $(TARGET) 67 | rm -f $(OBJ) 68 | rm -f $(DEP) 69 | 70 | distclean: clean 71 | rm -rf littlefs 72 | rm -f stamp-littlefs 73 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | env: 3 | global: 4 | - CFLAGS=-Werror 5 | 6 | # CI Jobs 7 | jobs: 8 | include: 9 | # Test stage 10 | - stage: test 11 | env: 12 | - STAGE=test 13 | install: 14 | - sudo apt-get install libfuse-dev 15 | - fusermount -V 16 | - gcc --version 17 | before_script: 18 | - mkdir mount 19 | - sudo chmod a+rw /dev/loop0 20 | - dd if=/dev/zero bs=512 count=4096 of=disk 21 | - losetup /dev/loop0 disk 22 | script: 23 | # Build 24 | - make 25 | # Format and mount 26 | - ./lfs --format /dev/loop0 27 | - ./lfs /dev/loop0 mount 28 | # Some simple operations than self hosting test 29 | - ls mount 30 | - cp -r littlefs mount/littlefs 31 | - cd mount/littlefs 32 | - stat . 33 | - ls -flh 34 | - make -B test_dirs test_files QUIET=1 35 | 36 | # Deploy stage for updating versions and tags 37 | - stage: deploy 38 | env: 39 | - STAGE=deploy 40 | script: 41 | - | 42 | bash << 'SCRIPT' 43 | set -ev 44 | # Find version defined in lfs.h 45 | LFS_VERSION=$(grep -ox '#define LFS_VERSION .*' littlefs/lfs.h | cut -d ' ' -f3) 46 | LFS_VERSION_MAJOR=$((0xffff & ($LFS_VERSION >> 16))) 47 | LFS_VERSION_MINOR=$((0xffff & ($LFS_VERSION >> 0))) 48 | # Grab latest patch from repo tags, default to 0, needs finagling 49 | # to get past GitHub's pagination API 50 | PREV_URL=https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs/tags/v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR. 51 | PREV_URL=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" -I \ 52 | | sed -n '/^Link/{s/.*<\(.*\)>; rel="last"/\1/;p;q0};$q1' \ 53 | || echo $PREV_URL) 54 | LFS_VERSION_PATCH=$(curl -u "$GEKY_BOT_RELEASES" "$PREV_URL" \ 55 | | jq 'map(.ref | match("\\bv.*\\..*\\.(.*)$";"g") 56 | .captures[].string | tonumber) | max + 1' \ 57 | || echo 0) 58 | # We have our new version 59 | LFS_VERSION="v$LFS_VERSION_MAJOR.$LFS_VERSION_MINOR.$LFS_VERSION_PATCH" 60 | echo "VERSION $LFS_VERSION" 61 | # Check that we're the most recent commit 62 | CURRENT_COMMIT=$(curl -f -u "$GEKY_BOT_RELEASES" \ 63 | https://api.github.com/repos/$TRAVIS_REPO_SLUG/commits/master \ 64 | | jq -re '.sha') 65 | [ "$TRAVIS_COMMIT" == "$CURRENT_COMMIT" ] || exit 0 66 | # Create major branch (vN) 67 | git branch v$LFS_VERSION_MAJOR HEAD 68 | git push https://$GEKY_BOT_RELEASES@github.com/$TRAVIS_REPO_SLUG.git \ 69 | v$LFS_VERSION_MAJOR 70 | # Create patch version tag (vN.N.N) 71 | curl -f -u "$GEKY_BOT_RELEASES" -X POST \ 72 | https://api.github.com/repos/$TRAVIS_REPO_SLUG/git/refs \ 73 | -d "{ 74 | \"ref\": \"refs/tags/$LFS_VERSION\", 75 | \"sha\": \"$TRAVIS_COMMIT\" 76 | }" 77 | SCRIPT 78 | 79 | # Job control 80 | stages: 81 | - name: test 82 | - name: deploy 83 | if: branch = master AND type = push 84 | -------------------------------------------------------------------------------- /lfs_fuse_bd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Linux user-space block device wrapper 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #include "lfs_fuse_bd.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #if defined(__APPLE__) 15 | #define BLKSSZGET DKIOCGETBLOCKSIZE 16 | #define BLKGETSIZE DKIOCGETBLOCKCOUNT 17 | #include 18 | #elif defined(__FreeBSD__) 19 | #define BLKSSZGET DIOCGSECTORSIZE 20 | #define BLKGETSIZE DIOCGMEDIASIZE 21 | #include 22 | #else 23 | #include 24 | #include 25 | #endif 26 | 27 | 28 | // Block device wrapper for user-space block devices 29 | int lfs_fuse_bd_create(struct lfs_config *cfg, const char *path) { 30 | int fd = open(path, O_RDWR); 31 | if (fd < 0) { 32 | return -errno; 33 | } 34 | cfg->context = (void*)(intptr_t)fd; 35 | 36 | // get sector size 37 | if (!cfg->block_size) { 38 | long ssize; 39 | int err = ioctl(fd, BLKSSZGET, &ssize); 40 | if (err) { 41 | return -errno; 42 | } 43 | cfg->block_size = ssize; 44 | } 45 | 46 | // get size in sectors 47 | if (!cfg->block_count) { 48 | long size; 49 | int err = ioctl(fd, BLKGETSIZE, &size); 50 | if (err) { 51 | return -errno; 52 | } 53 | cfg->block_count = size; 54 | } 55 | 56 | // setup function pointers 57 | cfg->read = lfs_fuse_bd_read; 58 | cfg->prog = lfs_fuse_bd_prog; 59 | cfg->erase = lfs_fuse_bd_erase; 60 | cfg->sync = lfs_fuse_bd_sync; 61 | 62 | return 0; 63 | } 64 | 65 | void lfs_fuse_bd_destroy(const struct lfs_config *cfg) { 66 | int fd = (intptr_t)cfg->context; 67 | close(fd); 68 | } 69 | 70 | int lfs_fuse_bd_read(const struct lfs_config *cfg, lfs_block_t block, 71 | lfs_off_t off, void *buffer, lfs_size_t size) { 72 | int fd = (intptr_t)cfg->context; 73 | 74 | // check if read is valid 75 | assert(block < cfg->block_count); 76 | 77 | // go to block 78 | off_t err = lseek(fd, (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); 79 | if (err < 0) { 80 | return -errno; 81 | } 82 | 83 | // read block 84 | ssize_t res = read(fd, buffer, (size_t)size); 85 | if (res < 0) { 86 | return -errno; 87 | } 88 | 89 | return 0; 90 | } 91 | 92 | int lfs_fuse_bd_prog(const struct lfs_config *cfg, lfs_block_t block, 93 | lfs_off_t off, const void *buffer, lfs_size_t size) { 94 | int fd = (intptr_t)cfg->context; 95 | 96 | // check if write is valid 97 | assert(block < cfg->block_count); 98 | 99 | // go to block 100 | off_t err = lseek(fd, (off_t)block*cfg->block_size + (off_t)off, SEEK_SET); 101 | if (err < 0) { 102 | return -errno; 103 | } 104 | 105 | // write block 106 | ssize_t res = write(fd, buffer, (size_t)size); 107 | if (res < 0) { 108 | return -errno; 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int lfs_fuse_bd_erase(const struct lfs_config *cfg, lfs_block_t block) { 115 | // do nothing 116 | return 0; 117 | } 118 | 119 | int lfs_fuse_bd_sync(const struct lfs_config *cfg) { 120 | int fd = (intptr_t)cfg->context; 121 | 122 | int err = fsync(fd); 123 | if (err) { 124 | return -errno; 125 | } 126 | 127 | return 0; 128 | } 129 | 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The little filesystem in user-space 2 | 3 | A FUSE wrapper that puts the littlefs in user-space. 4 | 5 | **FUSE** - https://github.com/libfuse/libfuse 6 | **littlefs** - https://github.com/ARMmbed/littlefs 7 | 8 | This project allows you to mount littlefs directly in a host PC. 9 | This allows you to easily debug an embedded system using littlefs on 10 | removable storage, or even debug littlefs itself, since the block device 11 | can be viewed in a hex-editor simultaneously. 12 | 13 | littlefs-fuse uses FUSE to interact with the host OS kernel, which means 14 | it can be compiled into a simple user program without kernel modifications. 15 | This comes with a performance penalty, but works well for the littlefs, 16 | since littlefs is intended for embedded systems. 17 | 18 | Currently littlefs-fuse has been tested on the following OSs: 19 | - [Linux](#usage-on-linux) 20 | - [FreeBSD](#usage-on-freebsd) 21 | - [macOS](#usage-on-macos) 22 | 23 | ## Usage on Linux 24 | 25 | littlefs-fuse requires FUSE version 2.6 or higher, you can find your FUSE 26 | version with: 27 | ``` bash 28 | fusermount -V 29 | ``` 30 | 31 | In order to build against FUSE, you will need the package `libfuse-dev`: 32 | ``` bash 33 | sudo apt-get install libfuse-dev 34 | ``` 35 | 36 | Once you have cloned littlefs-fuse, you can compile the program with make: 37 | ``` bash 38 | make 39 | ``` 40 | 41 | This should have built the `lfs` program in the top-level directory. 42 | 43 | From here we will need a block device. If you don't have removable storage 44 | handy, you can use a file-backed block device with Linux's loop devices: 45 | ``` bash 46 | sudo chmod a+rw /dev/loop0 # make loop device user accessible 47 | dd if=/dev/zero of=image bs=512 count=2048 # create a 1MB image 48 | losetup /dev/loop0 image # attach the loop device 49 | ``` 50 | 51 | littlefs-fuse has two modes of operation, formatting and mounting. 52 | 53 | To format a block device, pass the `--format` flag. Note! This will erase any 54 | data on the block device! 55 | ``` bash 56 | ./lfs --format /dev/loop0 57 | ``` 58 | 59 | To mount, run littlefs-fuse with a block device and a mountpoint: 60 | ``` bash 61 | mkdir mount 62 | ./lfs /dev/loop0 mount 63 | ``` 64 | 65 | Once mounted, the littlefs filesystem will be accessible through the 66 | mountpoint. You can now use the littlefs like you would any other filesystem: 67 | 68 | ``` bash 69 | cd mount 70 | echo "hello" > hi.txt 71 | ls 72 | cat hi.txt 73 | ``` 74 | 75 | After using littlefs, you can unmount and detach the loop device: 76 | ``` bash 77 | cd .. 78 | umount mount 79 | sudo losetup -d /dev/loop0 80 | ``` 81 | 82 | ## Usage on FreeBSD 83 | 84 | littlefs-fuse requires FUSE version 2.6 or higher, you can find your FUSE 85 | version with: 86 | ``` bash 87 | pkg info fusefs-libs | grep Version 88 | ``` 89 | 90 | Once you have cloned littlefs-fuse, you can compile the program with make: 91 | ``` bash 92 | gmake 93 | ``` 94 | 95 | This should have built the `lfs` program in the top-level directory. 96 | 97 | From here we will need a block device. If you don't have removable storage 98 | handy, you can use a file-backed block device with FreeBSD's loop devices: 99 | ``` bash 100 | dd if=/dev/zero of=imageBSD bs=1m count=1 # create a 1 MB image 101 | sudo mdconfig -at vnode -f image # attach the loop device 102 | sudo chmod 666 /dev/mdX # make loop device user accessible, 103 | # where mdX is device created with mdconfig command 104 | ``` 105 | 106 | littlefs-fuse has two modes of operation, formatting and mounting. 107 | 108 | To format a block device, pass the `--format` flag. Note! This will erase any 109 | data on the block device! 110 | ``` bash 111 | ./lfs --format /dev/md0 112 | ``` 113 | 114 | To mount, run littlefs-fuse with a block device and a mountpoint: 115 | ``` bash 116 | mkdir mount 117 | ./lfs /dev/md0 mount 118 | ``` 119 | 120 | Once mounted, the littlefs filesystem will be accessible through the 121 | mountpoint. You can now use the littlefs like you would any other filesystem: 122 | ``` bash 123 | cd mount 124 | echo "hello" > hi.txt 125 | ls 126 | cat hi.txt 127 | ``` 128 | 129 | After using littlefs, you can unmount and detach the loop device: 130 | ``` bash 131 | cd .. 132 | umount mount 133 | sudo mdconfig -du 0 134 | ``` 135 | 136 | ## Usage on macOS 137 | 138 | littlefs-fuse requires [macFUSE](https://macfuse.io/) for macOS: 139 | ```bash 140 | brew install --cask macfuse 141 | ``` 142 | 143 | Once you have cloned littlefs-fuse, you can compile the program with make: 144 | ``` bash 145 | make 146 | ``` 147 | 148 | This should have built the `lfs` program in the top-level directory. 149 | 150 | From here we will need a block device. If you don't have removable storage 151 | handy, you can use a file-backed block device with FreeBSD's loop devices: 152 | ``` bash 153 | dd if=/dev/zero of=image bs=1m count=1 # create a 1 MB image 154 | hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount image # attach the loop device 155 | sudo chmod 666 /dev/diskX # make loop device user accessible, 156 | ``` 157 | 158 | littlefs-fuse has two modes of operation, formatting and mounting. 159 | 160 | To format a block device, pass the `--format` flag. Note! This will erase any 161 | data on the block device! 162 | ``` bash 163 | ./lfs --format /dev/diskX 164 | ``` 165 | 166 | To mount, run littlefs-fuse with a block device and a mountpoint: 167 | ``` bash 168 | mkdir mount 169 | ./lfs /dev/diskX mount 170 | ``` 171 | 172 | Once mounted, the littlefs filesystem will be accessible through the 173 | mountpoint. You can now use the littlefs like you would any other filesystem: 174 | ``` bash 175 | cd mount 176 | echo "hello" > hi.txt 177 | ls 178 | cat hi.txt 179 | ``` 180 | 181 | After using littlefs, you can unmount and detach the loop device: 182 | ``` bash 183 | cd .. 184 | hdiutil unmount /dev/diskX 185 | hdiutil detach /dev/diskX 186 | ``` 187 | 188 | ## Limitations 189 | 190 | As an embedded filesystem, littlefs is designed to be simple. By default, 191 | this comes with a number of limitations compared to more PC oriented 192 | filesystems: 193 | 194 | - No timestamps, this will cause some programs, such as make to fail 195 | - No user permissions, this is why all of the files show up bright green 196 | in ls, all files are accessible by anyone 197 | - No symbolic links or special device files, currently only regular and 198 | directory file-types are implemented 199 | 200 | ## Tips 201 | 202 | If the littlefs was formatted with different geometry than the physical block 203 | device, you can override what littlefs-fuse detects. `lfs -h` lists all 204 | available options: 205 | ``` bash 206 | ./lfs --block_size=512 --format /dev/loop0 207 | ./lfs --block_size=512 /dev/loop0 mount 208 | ``` 209 | 210 | You can run littlefs-fuse in debug mode to get a log of the kernel interactions 211 | with littlefs-fuse. Any printfs in the littlefs driver will end up here: 212 | ``` bash 213 | ./lfs -d /dev/loop0 mount 214 | ``` 215 | 216 | You can even run littlefs-fuse in gdb to debug the filesystem under user 217 | operations. Note! When gdb is halted this will freeze any programs interacting 218 | with the filesystem! 219 | ``` bash 220 | make DEBUG=1 clean all # build with debug info 221 | gdb --args ./lfs -d /dev/loop0 mount # run with gdb 222 | ``` 223 | 224 | Using xxd or other hex-editory is very useful for inspecting the block 225 | device while debugging. You can even run xxd from inside gdb using gdb's 226 | `!` syntax: 227 | ``` bash 228 | dd if=/dev/loop0 bs=512 count=1 skip=0 | xxd -g1 229 | ``` 230 | -------------------------------------------------------------------------------- /lfs_fuse.c: -------------------------------------------------------------------------------- 1 | /* 2 | * FUSE wrapper for the littlefs 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | 8 | #define FUSE_USE_VERSION 26 9 | 10 | #ifdef linux 11 | // needed for a few things fuse depends on 12 | #define _XOPEN_SOURCE 700 13 | #endif 14 | 15 | #include 16 | #include "lfs.h" 17 | #include "lfs_util.h" 18 | #include "lfs_fuse_bd.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | // config and other state 29 | static struct lfs_config config = {0}; 30 | static const char *device = NULL; 31 | static bool format = false; 32 | static bool migrate = false; 33 | static lfs_t lfs; 34 | 35 | 36 | // actual fuse functions 37 | void lfs_fuse_defaults(struct lfs_config *config) { 38 | // default to 512 erase cycles, arbitrary value 39 | if (!config->block_cycles) { 40 | config->block_cycles = 512; 41 | } 42 | 43 | // defaults, ram is less of a concern here than what 44 | // littlefs is used to, so these may end up a bit funny 45 | if (!config->prog_size) { 46 | config->prog_size = config->block_size; 47 | } 48 | 49 | if (!config->read_size) { 50 | config->read_size = config->block_size; 51 | } 52 | 53 | if (!config->cache_size) { 54 | config->cache_size = config->block_size; 55 | } 56 | 57 | // arbitrary, though we have a lot of RAM here 58 | if (!config->lookahead_size) { 59 | config->lookahead_size = 8192; 60 | } 61 | } 62 | 63 | void *lfs_fuse_init(struct fuse_conn_info *conn) { 64 | // set that we want to take care of O_TRUNC 65 | conn->want |= FUSE_CAP_ATOMIC_O_TRUNC; 66 | 67 | // we also support writes of any size 68 | conn->want |= FUSE_CAP_BIG_WRITES; 69 | 70 | return 0; 71 | } 72 | 73 | int lfs_fuse_format(void) { 74 | int err = lfs_fuse_bd_create(&config, device); 75 | if (err) { 76 | return err; 77 | } 78 | 79 | lfs_fuse_defaults(&config); 80 | 81 | err = lfs_format(&lfs, &config); 82 | 83 | lfs_fuse_bd_destroy(&config); 84 | return err; 85 | } 86 | 87 | int lfs_fuse_migrate(void) { 88 | int err = lfs_fuse_bd_create(&config, device); 89 | if (err) { 90 | return err; 91 | } 92 | 93 | lfs_fuse_defaults(&config); 94 | 95 | err = lfs_migrate(&lfs, &config); 96 | 97 | lfs_fuse_bd_destroy(&config); 98 | return err; 99 | } 100 | 101 | int lfs_fuse_mount(void) { 102 | int err = lfs_fuse_bd_create(&config, device); 103 | if (err) { 104 | return err; 105 | } 106 | 107 | lfs_fuse_defaults(&config); 108 | 109 | return lfs_mount(&lfs, &config); 110 | } 111 | 112 | void lfs_fuse_destroy(void *eh) { 113 | lfs_unmount(&lfs); 114 | lfs_fuse_bd_destroy(&config); 115 | } 116 | 117 | int lfs_fuse_statfs(const char *path, struct statvfs *s) { 118 | memset(s, 0, sizeof(struct statvfs)); 119 | 120 | lfs_ssize_t in_use = lfs_fs_size(&lfs); 121 | if (in_use < 0) { 122 | return in_use; 123 | } 124 | 125 | s->f_bsize = config.block_size; 126 | s->f_frsize = config.block_size; 127 | s->f_blocks = config.block_count; 128 | s->f_bfree = config.block_count - in_use; 129 | s->f_bavail = config.block_count - in_use; 130 | s->f_namemax = config.name_max; 131 | 132 | return 0; 133 | } 134 | 135 | static void lfs_fuse_tostat(struct stat *s, struct lfs_info *info) { 136 | memset(s, 0, sizeof(struct stat)); 137 | 138 | s->st_size = info->size; 139 | s->st_mode = S_IRWXU | S_IRWXG | S_IRWXO; 140 | 141 | switch (info->type) { 142 | case LFS_TYPE_DIR: s->st_mode |= S_IFDIR; break; 143 | case LFS_TYPE_REG: s->st_mode |= S_IFREG; break; 144 | } 145 | } 146 | 147 | int lfs_fuse_getattr(const char *path, struct stat *s) { 148 | struct lfs_info info; 149 | int err = lfs_stat(&lfs, path, &info); 150 | if (err) { 151 | return err; 152 | } 153 | 154 | lfs_fuse_tostat(s, &info); 155 | return 0; 156 | } 157 | 158 | int lfs_fuse_access(const char *path, int mask) { 159 | struct lfs_info info; 160 | return lfs_stat(&lfs, path, &info); 161 | } 162 | 163 | int lfs_fuse_mkdir(const char *path, mode_t mode) { 164 | return lfs_mkdir(&lfs, path); 165 | } 166 | 167 | int lfs_fuse_opendir(const char *path, struct fuse_file_info *fi) { 168 | lfs_dir_t *dir = malloc(sizeof(lfs_dir_t)); 169 | memset(dir, 0, sizeof(lfs_dir_t)); 170 | 171 | int err = lfs_dir_open(&lfs, dir, path); 172 | if (err) { 173 | free(dir); 174 | return err; 175 | } 176 | 177 | fi->fh = (uint64_t)dir; 178 | return 0; 179 | } 180 | 181 | int lfs_fuse_releasedir(const char *path, struct fuse_file_info *fi) { 182 | lfs_dir_t *dir = (lfs_dir_t*)fi->fh; 183 | 184 | int err = lfs_dir_close(&lfs, dir); 185 | free(dir); 186 | return err; 187 | } 188 | 189 | int lfs_fuse_readdir(const char *path, void *buf, 190 | fuse_fill_dir_t filler, off_t offset, 191 | struct fuse_file_info *fi) { 192 | 193 | lfs_dir_t *dir = (lfs_dir_t*)fi->fh; 194 | struct stat s; 195 | struct lfs_info info; 196 | 197 | while (true) { 198 | int err = lfs_dir_read(&lfs, dir, &info); 199 | if (err != 1) { 200 | return err; 201 | } 202 | 203 | lfs_fuse_tostat(&s, &info); 204 | filler(buf, info.name, &s, 0); 205 | } 206 | } 207 | 208 | int lfs_fuse_rename(const char *from, const char *to) { 209 | return lfs_rename(&lfs, from, to); 210 | } 211 | 212 | int lfs_fuse_unlink(const char *path) { 213 | return lfs_remove(&lfs, path); 214 | } 215 | 216 | int lfs_fuse_open(const char *path, struct fuse_file_info *fi) { 217 | lfs_file_t *file = malloc(sizeof(lfs_file_t)); 218 | memset(file, 0, sizeof(lfs_file_t)); 219 | 220 | int flags = 0; 221 | if ((fi->flags & 3) == O_RDONLY) flags |= LFS_O_RDONLY; 222 | if ((fi->flags & 3) == O_WRONLY) flags |= LFS_O_WRONLY; 223 | if ((fi->flags & 3) == O_RDWR) flags |= LFS_O_RDWR; 224 | if (fi->flags & O_CREAT) flags |= LFS_O_CREAT; 225 | if (fi->flags & O_EXCL) flags |= LFS_O_EXCL; 226 | if (fi->flags & O_TRUNC) flags |= LFS_O_TRUNC; 227 | if (fi->flags & O_APPEND) flags |= LFS_O_APPEND; 228 | 229 | int err = lfs_file_open(&lfs, file, path, flags); 230 | if (err) { 231 | free(file); 232 | return err; 233 | } 234 | 235 | fi->fh = (uint64_t)file; 236 | return 0; 237 | } 238 | 239 | int lfs_fuse_release(const char *path, struct fuse_file_info *fi) { 240 | lfs_file_t *file = (lfs_file_t*)fi->fh; 241 | 242 | int err = lfs_file_close(&lfs, file); 243 | free(file); 244 | return err; 245 | } 246 | 247 | int lfs_fuse_fgetattr(const char *path, struct stat *s, 248 | struct fuse_file_info *fi) { 249 | lfs_file_t *file = (lfs_file_t*)fi->fh; 250 | 251 | lfs_fuse_tostat(s, &(struct lfs_info){ 252 | .size = lfs_file_size(&lfs, file), 253 | .type = LFS_TYPE_REG, 254 | }); 255 | 256 | return 0; 257 | } 258 | 259 | int lfs_fuse_read(const char *path, char *buf, size_t size, 260 | off_t off, struct fuse_file_info *fi) { 261 | lfs_file_t *file = (lfs_file_t*)fi->fh; 262 | 263 | if (lfs_file_tell(&lfs, file) != off) { 264 | lfs_soff_t soff = lfs_file_seek(&lfs, file, off, LFS_SEEK_SET); 265 | if (soff < 0) { 266 | return soff; 267 | } 268 | } 269 | 270 | return lfs_file_read(&lfs, file, buf, size); 271 | } 272 | 273 | int lfs_fuse_write(const char *path, const char *buf, size_t size, 274 | off_t off, struct fuse_file_info *fi) { 275 | lfs_file_t *file = (lfs_file_t*)fi->fh; 276 | 277 | if (lfs_file_tell(&lfs, file) != off) { 278 | lfs_soff_t soff = lfs_file_seek(&lfs, file, off, LFS_SEEK_SET); 279 | if (soff < 0) { 280 | return soff; 281 | } 282 | } 283 | 284 | return lfs_file_write(&lfs, file, buf, size); 285 | } 286 | 287 | int lfs_fuse_fsync(const char *path, int isdatasync, 288 | struct fuse_file_info *fi) { 289 | lfs_file_t *file = (lfs_file_t*)fi->fh; 290 | return lfs_file_sync(&lfs, file); 291 | } 292 | 293 | int lfs_fuse_flush(const char *path, struct fuse_file_info *fi) { 294 | lfs_file_t *file = (lfs_file_t*)fi->fh; 295 | return lfs_file_sync(&lfs, file); 296 | } 297 | 298 | int lfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) { 299 | int err = lfs_fuse_open(path, fi); 300 | if (err) { 301 | return err; 302 | } 303 | 304 | return lfs_fuse_fsync(path, 0, fi); 305 | } 306 | 307 | int lfs_fuse_ftruncate(const char *path, off_t size, 308 | struct fuse_file_info *fi) { 309 | lfs_file_t *file = (lfs_file_t*)fi->fh; 310 | return lfs_file_truncate(&lfs, file, size); 311 | } 312 | 313 | int lfs_fuse_truncate(const char *path, off_t size) { 314 | lfs_file_t file; 315 | int err = lfs_file_open(&lfs, &file, path, LFS_O_WRONLY); 316 | if (err) { 317 | return err; 318 | } 319 | 320 | err = lfs_file_truncate(&lfs, &file, size); 321 | if (err) { 322 | return err; 323 | } 324 | 325 | return lfs_file_close(&lfs, &file); 326 | } 327 | 328 | // unsupported functions 329 | int lfs_fuse_link(const char *from, const char *to) { 330 | // not supported, fail 331 | return -EPERM; 332 | } 333 | 334 | int lfs_fuse_mknod(const char *path, mode_t mode, dev_t dev) { 335 | // not supported, fail 336 | return -EPERM; 337 | } 338 | 339 | int lfs_fuse_chmod(const char *path, mode_t mode) { 340 | // not supported, always succeed 341 | return 0; 342 | } 343 | 344 | int lfs_fuse_chown(const char *path, uid_t uid, gid_t gid) { 345 | // not supported, fail 346 | return -EPERM; 347 | } 348 | 349 | int lfs_fuse_utimens(const char *path, const struct timespec ts[2]) { 350 | // not supported, always succeed 351 | return 0; 352 | } 353 | 354 | static struct fuse_operations lfs_fuse_ops = { 355 | .init = lfs_fuse_init, 356 | .destroy = lfs_fuse_destroy, 357 | .statfs = lfs_fuse_statfs, 358 | 359 | .getattr = lfs_fuse_getattr, 360 | .access = lfs_fuse_access, 361 | 362 | .mkdir = lfs_fuse_mkdir, 363 | .rmdir = lfs_fuse_unlink, 364 | .opendir = lfs_fuse_opendir, 365 | .releasedir = lfs_fuse_releasedir, 366 | .readdir = lfs_fuse_readdir, 367 | 368 | .rename = lfs_fuse_rename, 369 | .unlink = lfs_fuse_unlink, 370 | 371 | .open = lfs_fuse_open, 372 | .create = lfs_fuse_create, 373 | .truncate = lfs_fuse_truncate, 374 | .release = lfs_fuse_release, 375 | .fgetattr = lfs_fuse_fgetattr, 376 | .read = lfs_fuse_read, 377 | .write = lfs_fuse_write, 378 | .fsync = lfs_fuse_fsync, 379 | .flush = lfs_fuse_flush, 380 | 381 | .link = lfs_fuse_link, 382 | .symlink = lfs_fuse_link, 383 | .mknod = lfs_fuse_mknod, 384 | .chmod = lfs_fuse_chmod, 385 | .chown = lfs_fuse_chown, 386 | .utimens = lfs_fuse_utimens, 387 | }; 388 | 389 | 390 | // binding into fuse and general ui 391 | enum lfs_fuse_keys { 392 | KEY_HELP, 393 | KEY_VERSION, 394 | KEY_FORMAT, 395 | KEY_MIGRATE, 396 | }; 397 | 398 | #define OPT(t, p) { t, offsetof(struct lfs_config, p), 0} 399 | static struct fuse_opt lfs_fuse_opts[] = { 400 | FUSE_OPT_KEY("--format", KEY_FORMAT), 401 | FUSE_OPT_KEY("--migrate", KEY_MIGRATE), 402 | OPT("-b=%" SCNu32, block_size), 403 | OPT("--block_size=%" SCNu32, block_size), 404 | OPT("--block_count=%" SCNu32, block_count), 405 | OPT("--block_cycles=%" SCNu32, block_cycles), 406 | OPT("--read_size=%" SCNu32, read_size), 407 | OPT("--prog_size=%" SCNu32, prog_size), 408 | OPT("--cache_size=%" SCNu32, cache_size), 409 | OPT("--lookahead_size=%" SCNu32, lookahead_size), 410 | OPT("--name_max=%" SCNu32, name_max), 411 | OPT("--file_max=%" SCNu32, file_max), 412 | OPT("--attr_max=%" SCNu32, attr_max), 413 | FUSE_OPT_KEY("-V", KEY_VERSION), 414 | FUSE_OPT_KEY("--version", KEY_VERSION), 415 | FUSE_OPT_KEY("-h", KEY_HELP), 416 | FUSE_OPT_KEY("--help", KEY_HELP), 417 | FUSE_OPT_END 418 | }; 419 | 420 | static const char help_text[] = 421 | "usage: %s [options] device mountpoint\n" 422 | "\n" 423 | "general options:\n" 424 | " -o opt,[opt...] FUSE options\n" 425 | " -h --help print help\n" 426 | " -V --version print version\n" 427 | "\n" 428 | "littlefs options:\n" 429 | " --format format instead of mounting\n" 430 | " --migrate migrate previous version instead of mounting\n" 431 | " -b --block_size logical block size, overrides the block device\n" 432 | " --block_count block count, overrides the block device\n" 433 | " --block_cycles number of erase cycles before eviction (512)\n" 434 | " --read_size readable unit (block_size)\n" 435 | " --prog_size programmable unit (block_size)\n" 436 | " --cache_size size of caches (block_size)\n" 437 | " --lookahead_size size of lookahead buffer (8192)\n" 438 | " --name_max max size of file names (255)\n" 439 | " --file_max max size of file contents (2147483647)\n" 440 | " --attr_max max size of custom attributes (1022)\n" 441 | "\n"; 442 | 443 | int lfs_fuse_opt_proc(void *data, const char *arg, 444 | int key, struct fuse_args *args) { 445 | 446 | // option parsing 447 | switch (key) { 448 | case FUSE_OPT_KEY_NONOPT: 449 | if (!device) { 450 | device = strdup(arg); 451 | return 0; 452 | } 453 | break; 454 | 455 | case KEY_FORMAT: 456 | format = true; 457 | return 0; 458 | 459 | case KEY_MIGRATE: 460 | migrate = true; 461 | return 0; 462 | 463 | case KEY_HELP: 464 | fprintf(stderr, help_text, args->argv[0]); 465 | fuse_opt_add_arg(args, "-ho"); 466 | fuse_main(args->argc, args->argv, &lfs_fuse_ops, NULL); 467 | exit(1); 468 | 469 | case KEY_VERSION: 470 | fprintf(stderr, "littlefs version: v%d.%d\n", 471 | LFS_VERSION_MAJOR, LFS_VERSION_MINOR); 472 | fprintf(stderr, "littlefs disk version: v%d.%d\n", 473 | LFS_DISK_VERSION_MAJOR, LFS_DISK_VERSION_MINOR); 474 | fuse_opt_add_arg(args, "--version"); 475 | fuse_main(args->argc, args->argv, &lfs_fuse_ops, NULL); 476 | exit(0); 477 | } 478 | 479 | return 1; 480 | } 481 | 482 | int main(int argc, char *argv[]) { 483 | // parse custom options 484 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 485 | fuse_opt_parse(&args, &config, lfs_fuse_opts, lfs_fuse_opt_proc); 486 | if (!device) { 487 | fprintf(stderr, "missing device parameter\n"); 488 | exit(1); 489 | } 490 | 491 | if (format) { 492 | // format time, no mount 493 | int err = lfs_fuse_format(); 494 | if (err) { 495 | LFS_ERROR("%s", strerror(-err)); 496 | exit(-err); 497 | } 498 | exit(0); 499 | } 500 | 501 | if (migrate) { 502 | // migrate time, no mount 503 | int err = lfs_fuse_migrate(); 504 | if (err) { 505 | LFS_ERROR("%s", strerror(-err)); 506 | exit(-err); 507 | } 508 | exit(0); 509 | } 510 | 511 | // go ahead and mount so errors are reported before backgrounding 512 | int err = lfs_fuse_mount(); 513 | if (err) { 514 | LFS_ERROR("%s", strerror(-err)); 515 | exit(-err); 516 | } 517 | 518 | // always single-threaded 519 | fuse_opt_add_arg(&args, "-s"); 520 | 521 | // enter fuse 522 | err = fuse_main(args.argc, args.argv, &lfs_fuse_ops, NULL); 523 | if (err) { 524 | lfs_fuse_destroy(NULL); 525 | } 526 | 527 | return err; 528 | } 529 | --------------------------------------------------------------------------------