├── .github └── workflows │ └── tests.yml ├── LICENSE ├── README.md ├── benchmarks ├── ssd_fuse_ext4 │ ├── file-cr-wr-4KB-32th-4Mf │ │ └── file-cr-wr-4KB-32th-4Mf.f │ ├── file-sq-re-4KB-1th-1f │ │ └── file-sq-re-4KB-1th-1f.f │ ├── file-sq-re-4KB-32th-32f │ │ └── file-sq-re-4KB-32th-32f.f │ ├── file_server_50th │ │ └── file_server_50th.f │ └── web_server_100th │ │ └── web_server.f ├── ssd_ldpfuse_ext4 │ ├── file-cr-wr-4KB-1th-4Mf │ │ └── file-cr-wr-4KB-1th-4Mf.f │ ├── file-cr-wr-4KB-32th-4Mf │ │ └── file-cr-wr-4KB-32th-4Mf.f │ ├── file-sq-re-4KB-1th-1f │ │ └── file-sq-re-4KB-1th-1f.f │ ├── file-sq-re-4KB-32th-32f │ │ └── file-sq-re-4KB-32th-32f.f │ ├── file_server_50th │ │ └── file_server_50th.f │ └── web_server_100th │ │ └── web_server_100th.f └── ssd_native_ext4 │ ├── file-cr-wr-4KB-1th-4Mf │ └── file-cr-wr-4KB-1th-4Mf.f │ ├── file-cr-wr-4KB-32th-4Mf │ └── file-cr-wr-4KB-32th-4Mf.f │ ├── file-sq-re-4KB-1th-1f │ └── file-sq-re-4KB-1th-1f.f │ ├── file-sq-re-4KB-32th-32f │ └── file-sq-re-4KB-32th-32f.f │ └── file_server_50th │ └── file_server_50th.f ├── cli ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── examples ├── encrypted │ ├── .gitignore │ ├── Makefile │ └── encrypted.c └── passthrough │ ├── .gitignore │ ├── Makefile │ └── passthrough.c ├── ldp_fuse └── include │ ├── cwalk.c │ ├── cwalk.h │ └── ldpfuse.h ├── scripts ├── ldpfuse.sh ├── run_fuse_benchmark.sh ├── run_ldpfuse_benchmark.sh └── run_native_benchmark.sh └── tests ├── .gitignore ├── Makefile ├── passthrough_test_fs.c ├── run_all.sh ├── test_multithread.c ├── test_oft.c └── test_path_in_fs.c /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Run unit/integration tests 2 | on: [push] 3 | jobs: 4 | run-tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - name: Setup and tests 9 | working-directory: ./tests 10 | run: make 11 | - name: Run tests 12 | working-directory: ./tests 13 | run: ./run_all.sh 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sjors Holtrop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | LDP_FUSE (`LD_PRELOAD` Filesystem in Userspace) is a header-only C library for writing file systems, leveraging the [`LD_PRELOAD` trick](https://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/). Explained briefly, you write a shared library (.so file) using the API that LDP_FUSE provides, and then run a binary with your shared library `LD_PRELOAD`ed. LDP_FUSE will take care of several low level details for you (see [Documentation](#documentation)). 4 | 5 | **Important note**: This project is mostly a proof-of-concept. All of the current [limitations](#known-issues--limitations) make it not feasible to run in production. 6 | 7 | # Installation 8 | 9 | Include all of the files under the `include/` folder in your build system. 10 | 11 | To manage LDP_FUSE file systems more easily, you can optionally install a Rust-based CLI tool: 12 | 13 | ```bash 14 | cargo install ldpfuse 15 | ``` 16 | 17 | # Usage 18 | 19 | ## Example 20 | 21 | An example of how to write an LDP_FUSE file system: 22 | 23 | ```c 24 | #include 25 | #include "ldpfuse.h" 26 | 27 | ssize_t my_read(int fd, void *buf, size_t count, off_t offset, 28 | orig_pread_t pread_fn) { 29 | printf("Read called!\n"); 30 | return pread_fn(fd, buf, count, offset); 31 | } 32 | 33 | LDPRELOAD_FUSE_MAIN { 34 | struct ldp_fuse_funcs funcs = {.read = my_read }; 35 | ldp_fuse_init(&funcs); 36 | } 37 | ``` 38 | 39 | Compile this to a shared object (.so) file using `gcc`. You must dynamically link `libdl`, e.g. by specifying `-ldl`. 40 | 41 | ## Using the CLI 42 | 43 | If you have installed the CLI, you can now run your file system like this: 44 | 45 | ```bash 46 | ldpfuse -m -s -- myprogram 47 | ``` 48 | 49 | Where `` is the path your file system is mounted under. File system operations on paths that do not have this mount path as an ancestor are ignored by LDP_FUSE and passed to the original functions instead. 50 | The `` is your LDP_FUSE file system shared object. Paths may be relative. 51 | 52 | Full example, reading directory contents: 53 | 54 | ```bash 55 | ldpfuse -m /tmp/test -s ./my_fs.so -- ls -la /tmp/test 56 | ``` 57 | 58 | ## Environment variables 59 | 60 | Rather than using the CLI to manage the environment variables and run the program, you can also set them yourself: 61 | 62 | - `LD_PRELOAD` - Must be the absolute path to your LDP_FUSE file system shared library. 63 | - `LDP_FUSE_PATH` - Must be set to the absolute path your file system is 'mounted' under. E.g. `/tmp/ldpfuse`. If not set, any path is treated as if it were in the file system. 64 | 65 | # Documentation 66 | 67 | ## Functions, macros and structs 68 | 69 | ### `LDPRELOAD_FUSE_MAIN` 70 | 71 | Define any setup code within the scope of this function macro. This macro should be called in any LDP_FUSE file system, and include a call to `ldp_fuse_init`. 72 | 73 | ```c 74 | LDPRELOAD_FUSE_MAIN { 75 | // Any one-time setup code... 76 | ldp_fuse_init(&funcs); 77 | } 78 | ``` 79 | 80 | ### `ldp_fuse_init` 81 | 82 | ```c 83 | void ldp_fuse_init(ldp_fuse_funcs* funcs) 84 | ``` 85 | 86 | Initializes the file system and sets its functions to those described in the `funcs` parameter. This should be called at some point during `LDPRELOAD_FUSE_MAIN`. 87 | 88 | ### `ldp_fuse_funcs` 89 | 90 | A struct that defines LDP_FUSE's operations. Each member is a pointer to a function you wish to replace the regular filesystem I/O function with. See the [Overwritten functions overview](#overwritten_functions) to see what original functions you can overwrite. 91 | 92 | ## Overwritten functions overview 93 | 94 | Next is an overview of the file system functions you can overwrite using LDP_FUSE. Similar system calls are redirected to a single user function - the most generic one. E.g., `stat`, `lstat`, `fstat` and `fstatat` are all redirected to `fstatat`. You will therefore only have to write an `fstatat` implementation. 95 | 96 | | Original function | LDP_FUSE function | Notes | 97 | | ----------------- | ----------------- | --------------------------------------------------------------------------------------- | 98 | | access | access | | 99 | | faccessat | access | | 100 | | euidaccess | access | | 101 | | mkdir | mkdir | | 102 | | close | close | | 103 | | close_range | close | Flags argument is ignored. Will call close once on each fd in the range. | 104 | | opendir | opendir | | 105 | | creat | open | | 106 | | open | open | | 107 | | openat | openat | | 108 | | truncate | truncate | | 109 | | chmod | chmod | | 110 | | chown | chown | | 111 | | symlink | symlink | | 112 | | rename | rename | | 113 | | link | link | | 114 | | rmdir | rmdir | | 115 | | unlink | unlink | | 116 | | mknod | mknod | | 117 | | readlink | readlink | | 118 | | write | write | | 119 | | pwrite | write | | 120 | | stat | stat | | 121 | | lstat | stat | | 122 | | fstat | stat | | 123 | | fstatat | stat | | 124 | | read | read | | 125 | | pread | read | | 126 | | getxattr | getxattr | | 127 | | lgetxattr | getxattr | WILL follow symbolic links, contrary to the default which interrogates the link itself. | 128 | | fgetxattr | getxattr | | 129 | 130 | ## Conditional compilation 131 | 132 | You may specify any of the following flags: 133 | 134 | - `-D LDP_FUSE_DEBUG` - Log debug output to stderr. 135 | - `-D LDP_FUSE_THREAD_SAFE` - Make LDP_FUSE's internal datastructures thread safe. Should be included if the LDP_FUSE file system will be used to run programs that perform multithreaded file I/O. If included, `pthreads` must be dynamically linked. 136 | - `-D LDP_FUSE_OFT_SIZE ` - Set the size of LDP_FUSE's open file descriptor table (see [Open File Descriptor Table](#open-file-descriptor-table-oft)). Defaults to 200. 137 | 138 | ## Open File Descriptor Table (OFT) 139 | 140 | LDP_FUSE has its own open file descriptor table to keep track of read offsets, cache whether a path is in the file system, and read-write locking if necessary. Users of the library should not have to interact with this directly. 141 | The maximum amount of entries is determined at compile time (`LDP_FUSE_OFT_SIZE`), and when it is exceeded the application will exit with an error message. 142 | 143 | ## Pitfalls 144 | 145 | In your custom file system functions, do NOT use the original function. E.g., in `my_read`, do not call `read`. Doing so will cause an infinite loop, as LDP_FUSE redirects `read` to `my_read`. 146 | Instead, use the last argument, which is a function pointer to the original `pread` function. 147 | 148 | If the program that uses the LDP_FUSE is multithreaded, include the `LDP_FUSE_THREAD_SAFE` flag (see [Conditional Compilation](#conditional-compilation)] 149 | 150 | # Known issues & limitations 151 | 152 | LDP_FUSE may not work with certain binaries. A non-exhaustive list of reasons is given here. 153 | 154 | ## Mmap 155 | 156 | Memory-mapped file I/O cannot be intercepted using `LD_PRELOAD`. Any program that uses `mmap` to access files can therefore not use LDP_FUSE. 157 | 158 | ## Inlined syscalls, statically linked glibc 159 | 160 | Any program with either inlined syscalls (e.g. using `syscall` directly) or that statically links glibc, can not use LDP_FUSE. 161 | 162 | ## Setuid binaries 163 | 164 | Linux executes setuid binaries in secure execution mode. Under this mode, `LD_PRELOAD` is ignored for safety reasons. As a result, you cannot use LDP_FUSE with setuid binaries. 165 | 166 | ## Functions with no glibc wrapper 167 | 168 | Certain file system I/O functions do not have a glibc wrapper (e.g. `openat2`). These function calls can therefore not be intercepted. 169 | 170 | ## LDP_FUSE file systems without a backing regular file system 171 | 172 | LDP_FUSE maintains one `OFDT` per process. There is no ipc or r/w mechanism that avoids two processes accessing a file at the same time yet. This means you will still need a regular file system backing it, so that it can take care of this. This issue might be alleviated in the future, with a single `OFDT` in shared memory. 173 | 174 | # Credits 175 | 176 | This library includes likle's great [cwalk](https://github.com/likle/cwalk) library for path resolution. 177 | 178 | # License 179 | MIT 180 | -------------------------------------------------------------------------------- /benchmarks/ssd_fuse_ext4/file-cr-wr-4KB-32th-4Mf/file-cr-wr-4KB-32th-4Mf.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/fuse/tmp/filebench 3 | #Fixing I/O amount to be 0.4M files 4 | set $nfiles=400000 5 | set $meandirwidth=1000 6 | set $nthreads=32 7 | 8 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirgamma=0, dirwidth=$meandirwidth, size=4k 9 | define process name=fileopen, instances=1 10 | { 11 | thread name=fileopener, memsize=4k, instances=$nthreads 12 | { 13 | flowop createfile name=create1, filesetname=bigfileset 14 | flowop writewholefile name=write-file, filesetname=bigfileset 15 | flowop closefile name=close-file,filesetname=bigfileset 16 | } 17 | } 18 | 19 | create files 20 | 21 | psrun -10 22 | -------------------------------------------------------------------------------- /benchmarks/ssd_fuse_ext4/file-sq-re-4KB-1th-1f/file-sq-re-4KB-1th-1f.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/fuse/tmp/filebench 3 | set $nfiles=1 4 | set $meandirwidth=1 5 | set $nthreads=1 6 | 7 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirwidth=$meandirwidth, size=60g, prealloc 8 | 9 | define process name=fileopen, instances=1 10 | { 11 | thread name=fileopener, memsize=4k, instances=$nthreads 12 | { 13 | flowop openfile name=open1, filesetname=bigfileset, fd=1 14 | flowop read name=read-file, filesetname=bigfileset, iosize=4k, iters=15728640, fd=1 15 | flowop closefile name=close1, fd=1 16 | flowop finishoncount name=finish, value=1 17 | } 18 | } 19 | 20 | create files 21 | 22 | 23 | psrun -10 24 | -------------------------------------------------------------------------------- /benchmarks/ssd_fuse_ext4/file-sq-re-4KB-32th-32f/file-sq-re-4KB-32th-32f.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/fuse/tmp/filebench 3 | set $nfiles=32 4 | set $meandirwidth=32 5 | set $nthreads=1 6 | #Each thread reading 1.875 G 7 | set $io_size=4k 8 | set $iterations=491520 9 | 10 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirwidth=$meandirwidth, dirgamma=0, size=2g, prealloc 11 | 12 | define process name=filereader,instances=1 13 | { 14 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 15 | { 16 | flowop openfile name=open1, indexed=1, filesetname=bigfileset, fd=1 17 | flowop read name=read-file-1, indexed=1, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 18 | flowop closefile name=close1, indexed=1, fd=1 19 | flowop finishoncount name=finish, value=1 20 | } 21 | 22 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 23 | { 24 | flowop openfile name=open2, indexed=2, filesetname=bigfileset, fd=1 25 | flowop read name=read-file-2, indexed=2, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 26 | flowop closefile name=close2, indexed=2, fd=1 27 | flowop finishoncount name=finish, value=1 28 | } 29 | 30 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 31 | { 32 | flowop openfile name=open3, indexed=3, filesetname=bigfileset, fd=1 33 | flowop read name=read-file-3, indexed=3, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 34 | flowop closefile name=close3, indexed=3, fd=1 35 | flowop finishoncount name=finish,value=1 36 | } 37 | 38 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 39 | { 40 | flowop openfile name=open4, indexed=4, filesetname=bigfileset, fd=1 41 | flowop read name=read-file-4, indexed=4, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 42 | flowop closefile name=close4, indexed=4, fd=1 43 | flowop finishoncount name=finish,value=1 44 | } 45 | 46 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 47 | { 48 | flowop openfile name=open5, indexed=5, filesetname=bigfileset, fd=1 49 | flowop read name=read-file-5, indexed=5, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 50 | flowop closefile name=close5, indexed=5, fd=1 51 | flowop finishoncount name=finish, value=1 52 | } 53 | 54 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 55 | { 56 | flowop openfile name=open6, indexed=6, filesetname=bigfileset, fd=1 57 | flowop read name=read-file-6, indexed=6, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 58 | flowop closefile name=close6, indexed=6, fd=1 59 | flowop finishoncount name=finish,value=1 60 | } 61 | 62 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 63 | { 64 | flowop openfile name=open7, filesetname=bigfileset, fd=1, indexed=7 65 | flowop read name=read-file-7, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=7 66 | flowop closefile name=close7, fd=1, indexed=7 67 | flowop finishoncount name=finish,value=1 68 | } 69 | 70 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 71 | { 72 | flowop openfile name=open8, filesetname=bigfileset, fd=1, indexed=8 73 | flowop read name=read-file-8,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=8 74 | flowop closefile name=close8, fd=1, indexed=8 75 | flowop finishoncount name=finish,value=1 76 | } 77 | 78 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 79 | { 80 | flowop openfile name=open9, filesetname=bigfileset, fd=1, indexed=9 81 | flowop read name=read-file-9, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=9 82 | flowop closefile name=close9, fd=1, indexed=9 83 | flowop finishoncount name=finish, value=1 84 | } 85 | 86 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 87 | { 88 | flowop openfile name=open10, filesetname=bigfileset, fd=1, indexed=10 89 | flowop read name=read-file-10,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=10 90 | flowop closefile name=close10, fd=1, indexed=10 91 | flowop finishoncount name=finish,value=1 92 | } 93 | 94 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 95 | { 96 | flowop openfile name=open11, filesetname=bigfileset, fd=1, indexed=11 97 | flowop read name=read-file-11,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=11 98 | flowop closefile name=close11, fd=1, indexed=11 99 | flowop finishoncount name=finish,value=1 100 | } 101 | 102 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 103 | { 104 | flowop openfile name=open12, filesetname=bigfileset, fd=1, indexed=12 105 | flowop read name=read-file-12,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=12 106 | flowop closefile name=close12, fd=1, indexed=12 107 | flowop finishoncount name=finish,value=1 108 | } 109 | 110 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 111 | { 112 | flowop openfile name=open13, indexed=13, filesetname=bigfileset, fd=1 113 | flowop read name=read-file-13, indexed=13, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 114 | flowop closefile name=close13, indexed=13, fd=1 115 | flowop finishoncount name=finish, value=1 116 | } 117 | 118 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 119 | { 120 | flowop openfile name=open14, indexed=14, filesetname=bigfileset, fd=1 121 | flowop read name=read-file-14, indexed=14, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 122 | flowop closefile name=close14, indexed=14, fd=1 123 | flowop finishoncount name=finish, value=1 124 | } 125 | 126 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 127 | { 128 | flowop openfile name=open15, indexed=15, filesetname=bigfileset, fd=1 129 | flowop read name=read-file-15, indexed=15, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 130 | flowop closefile name=close15, indexed=15, fd=1 131 | flowop finishoncount name=finish,value=1 132 | } 133 | 134 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 135 | { 136 | flowop openfile name=open16, indexed=16, filesetname=bigfileset, fd=1 137 | flowop read name=read-file-16, indexed=16, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 138 | flowop closefile name=close16, indexed=16, fd=1 139 | flowop finishoncount name=finish,value=1 140 | } 141 | 142 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 143 | { 144 | flowop openfile name=open17, indexed=17, filesetname=bigfileset, fd=1 145 | flowop read name=read-file-17, indexed=17, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 146 | flowop closefile name=close17, indexed=17, fd=1 147 | flowop finishoncount name=finish, value=1 148 | } 149 | 150 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 151 | { 152 | flowop openfile name=open18, indexed=18, filesetname=bigfileset, fd=1 153 | flowop read name=read-file-18, indexed=18, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 154 | flowop closefile name=close18, indexed=18, fd=1 155 | flowop finishoncount name=finish,value=1 156 | } 157 | 158 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 159 | { 160 | flowop openfile name=open19, filesetname=bigfileset, fd=1, indexed=19 161 | flowop read name=read-file-19, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=19 162 | flowop closefile name=close19, fd=1, indexed=19 163 | flowop finishoncount name=finish,value=1 164 | } 165 | 166 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 167 | { 168 | flowop openfile name=open20, filesetname=bigfileset, fd=1, indexed=20 169 | flowop read name=read-file-20,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=20 170 | flowop closefile name=close20, fd=1, indexed=20 171 | flowop finishoncount name=finish,value=1 172 | } 173 | 174 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 175 | { 176 | flowop openfile name=open21, filesetname=bigfileset, fd=1, indexed=21 177 | flowop read name=read-file-21, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=21 178 | flowop closefile name=close21, fd=1, indexed=21 179 | flowop finishoncount name=finish, value=1 180 | } 181 | 182 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 183 | { 184 | flowop openfile name=open22, indexed=22, filesetname=bigfileset, fd=1 185 | flowop read name=read-file-22, indexed=22, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 186 | flowop closefile name=close22, indexed=22, fd=1 187 | flowop finishoncount name=finish,value=1 188 | } 189 | 190 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 191 | { 192 | flowop openfile name=open23, indexed=23, filesetname=bigfileset, fd=1 193 | flowop read name=read-file-23, indexed=23, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 194 | flowop closefile name=close23, indexed=23, fd=1 195 | flowop finishoncount name=finish, value=1 196 | } 197 | 198 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 199 | { 200 | flowop openfile name=open24, indexed=24, filesetname=bigfileset, fd=1 201 | flowop read name=read-file-24, indexed=24, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 202 | flowop closefile name=close24, indexed=24, fd=1 203 | flowop finishoncount name=finish, value=1 204 | } 205 | 206 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 207 | { 208 | flowop openfile name=open25, indexed=25, filesetname=bigfileset, fd=1 209 | flowop read name=read-file-25, indexed=25, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 210 | flowop closefile name=close25, indexed=25, fd=1 211 | flowop finishoncount name=finish,value=1 212 | } 213 | 214 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 215 | { 216 | flowop openfile name=open26, indexed=26, filesetname=bigfileset, fd=1 217 | flowop read name=read-file-26, indexed=26, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 218 | flowop closefile name=close26, indexed=26, fd=1 219 | flowop finishoncount name=finish,value=1 220 | } 221 | 222 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 223 | { 224 | flowop openfile name=open27, indexed=27, filesetname=bigfileset, fd=1 225 | flowop read name=read-file-27, indexed=27, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 226 | flowop closefile name=close27, indexed=27, fd=1 227 | flowop finishoncount name=finish, value=1 228 | } 229 | 230 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 231 | { 232 | flowop openfile name=open28, indexed=28, filesetname=bigfileset, fd=1 233 | flowop read name=read-file-28, indexed=28, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 234 | flowop closefile name=close28, indexed=28, fd=1 235 | flowop finishoncount name=finish,value=1 236 | } 237 | 238 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 239 | { 240 | flowop openfile name=open29, filesetname=bigfileset, fd=1, indexed=29 241 | flowop read name=read-file-29, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=29 242 | flowop closefile name=close29, fd=1, indexed=29 243 | flowop finishoncount name=finish,value=1 244 | } 245 | 246 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 247 | { 248 | flowop openfile name=open30, filesetname=bigfileset, fd=1, indexed=30 249 | flowop read name=read-file-30,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=30 250 | flowop closefile name=close30, fd=1, indexed=30 251 | flowop finishoncount name=finish,value=1 252 | } 253 | 254 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 255 | { 256 | flowop openfile name=open31, filesetname=bigfileset, fd=1, indexed=31 257 | flowop read name=read-file-31, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=31 258 | flowop closefile name=close31, fd=1, indexed=31 259 | flowop finishoncount name=finish, value=1 260 | } 261 | 262 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 263 | { 264 | flowop openfile name=open32, filesetname=bigfileset, fd=1, indexed=32 265 | flowop read name=read-file-32,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=32 266 | flowop closefile name=close32, fd=1, indexed=32 267 | flowop finishoncount name=finish,value=1 268 | } 269 | } 270 | 271 | psrun -10 272 | -------------------------------------------------------------------------------- /benchmarks/ssd_fuse_ext4/file_server_50th/file_server_50th.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=200000 4 | set $meandirwidth=20 5 | set $nthreads=50 6 | set $size1=128k 7 | 8 | define fileset name=bigfileset, path=$dir, size=$size1, entries=$nfiles, dirwidth=$meandirwidth, prealloc=80 9 | 10 | define process name=fileserver,instances=1 11 | { 12 | thread name=fileserverthread, memsize=10m, instances=$nthreads 13 | { 14 | flowop createfile name=createfile1,filesetname=bigfileset,fd=1 15 | flowop writewholefile name=wrtfile1,srcfd=1,fd=1,iosize=1m 16 | flowop closefile name=closefile1,fd=1 17 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 18 | flowop appendfilerand name=appendfilerand1,iosize=16k,fd=1 19 | flowop closefile name=closefile2,fd=1 20 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 21 | flowop readwholefile name=readfile1,fd=1,iosize=1m 22 | flowop closefile name=closefile3,fd=1 23 | flowop deletefile name=deletefile1,filesetname=bigfileset 24 | flowop statfile name=statfile1,filesetname=bigfileset 25 | flowop finishoncount name=finish, value=3000000 26 | #So all the above operations will happen together for 3 M (SSD) times 27 | } 28 | } 29 | 30 | create files 31 | #unmount and mount for better stability results 32 | #system "sync" 33 | 34 | #system "umount /tmp/filebench" 35 | #change accordingly for HDD (sdb) and SSD(sdd) 36 | #system "mount -t ext4 /dev/sdd /tmp/filebench" 37 | 38 | #system "sync" 39 | #system "echo 3 > /proc/sys/vm/drop_caches" 40 | 41 | system "echo started >> cpustats.txt" 42 | system "echo started >> diskstats.txt" 43 | 44 | psrun -10 45 | -------------------------------------------------------------------------------- /benchmarks/ssd_fuse_ext4/web_server_100th/web_server.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/fuse/tmp/filebench_fuse 3 | set $nfiles=125000 4 | set $meandirwidth=20 5 | set $nthreads=100 6 | set $size1=16k 7 | 8 | define fileset name=bigfileset, path=$dir, size=$size1, entries=$nfiles, dirwidth=$meandirwidth, prealloc=100 9 | define fileset name=logfiles, path=$dir, size=$size1, entries=1, dirwidth=$meandirwidth, prealloc 10 | 11 | define process name=webserver,instances=1 12 | { 13 | thread name=webserverthread,memsize=10m,instances=$nthreads 14 | { 15 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 16 | flowop readwholefile name=readfile1,fd=1,iosize=1m 17 | flowop closefile name=closefile1,fd=1 18 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 19 | flowop readwholefile name=readfile2,fd=1,iosize=1m 20 | flowop closefile name=closefile2,fd=1 21 | flowop openfile name=openfile3,filesetname=bigfileset,fd=1 22 | flowop readwholefile name=readfile3,fd=1,iosize=1m 23 | flowop closefile name=closefile3,fd=1 24 | flowop openfile name=openfile4,filesetname=bigfileset,fd=1 25 | flowop readwholefile name=readfile4,fd=1,iosize=1m 26 | flowop closefile name=closefile4,fd=1 27 | flowop openfile name=openfile5,filesetname=bigfileset,fd=1 28 | flowop readwholefile name=readfile5,fd=1,iosize=1m 29 | flowop closefile name=closefile5,fd=1 30 | flowop openfile name=openfile6,filesetname=bigfileset,fd=1 31 | flowop readwholefile name=readfile6,fd=1,iosize=1m 32 | flowop closefile name=closefile6,fd=1 33 | flowop openfile name=openfile7,filesetname=bigfileset,fd=1 34 | flowop readwholefile name=readfile7,fd=1,iosize=1m 35 | flowop closefile name=closefile7,fd=1 36 | flowop openfile name=openfile8,filesetname=bigfileset,fd=1 37 | flowop readwholefile name=readfile8,fd=1,iosize=1m 38 | flowop closefile name=closefile8,fd=1 39 | flowop openfile name=openfile9,filesetname=bigfileset,fd=1 40 | flowop readwholefile name=readfile9,fd=1,iosize=1m 41 | flowop closefile name=closefile9,fd=1 42 | flowop openfile name=openfile10,filesetname=bigfileset,fd=1 43 | flowop readwholefile name=readfile10,fd=1,iosize=1m 44 | flowop closefile name=closefile10,fd=1 45 | flowop appendfilerand name=appendlog,filesetname=logfiles,iosize=16k,fd=2 46 | flowop finishoncount name=finish, value=8000000 47 | #so that all the above operations will together complete 8 M(SSD) ops 48 | } 49 | } 50 | 51 | create files 52 | 53 | psrun -10 54 | -------------------------------------------------------------------------------- /benchmarks/ssd_ldpfuse_ext4/file-cr-wr-4KB-1th-4Mf/file-cr-wr-4KB-1th-4Mf.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=800000 4 | set $meandirwidth=1000 5 | set $nthreads=1 6 | 7 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirgamma=0, dirwidth=$meandirwidth, size=4k 8 | define process name=fileopen, instances=1 9 | { 10 | thread name=fileopener, memsize=4k, instances=$nthreads 11 | { 12 | flowop createfile name=create1, filesetname=bigfileset 13 | flowop writewholefile name=write-file, filesetname=bigfileset 14 | flowop closefile name=close-file,filesetname=bigfileset 15 | } 16 | } 17 | create files 18 | system "sync" 19 | system "echo 3 > /proc/sys/vm/drop_caches" 20 | run 21 | -------------------------------------------------------------------------------- /benchmarks/ssd_ldpfuse_ext4/file-cr-wr-4KB-32th-4Mf/file-cr-wr-4KB-32th-4Mf.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | #Fixing I/O amount to be 0.4M files 4 | set $nfiles=400000 5 | set $meandirwidth=1000 6 | set $nthreads=32 7 | 8 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirgamma=0, dirwidth=$meandirwidth, size=4k 9 | define process name=fileopen, instances=1 10 | { 11 | thread name=fileopener, memsize=4k, instances=$nthreads 12 | { 13 | flowop createfile name=create1, filesetname=bigfileset 14 | flowop writewholefile name=write-file, filesetname=bigfileset 15 | flowop closefile name=close-file,filesetname=bigfileset 16 | } 17 | } 18 | 19 | create files 20 | 21 | psrun -10 22 | -------------------------------------------------------------------------------- /benchmarks/ssd_ldpfuse_ext4/file-sq-re-4KB-1th-1f/file-sq-re-4KB-1th-1f.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=1 4 | set $meandirwidth=1 5 | set $nthreads=1 6 | 7 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirwidth=$meandirwidth, size=60g, prealloc 8 | 9 | define process name=fileopen, instances=1 10 | { 11 | thread name=fileopener, memsize=4k, instances=$nthreads 12 | { 13 | flowop openfile name=open1, filesetname=bigfileset, fd=1 14 | flowop read name=read-file, filesetname=bigfileset, iosize=4k, iters=15728640, fd=1 15 | flowop closefile name=close1, fd=1 16 | flowop finishoncount name=finish, value=1 17 | } 18 | } 19 | 20 | create files 21 | 22 | 23 | psrun -10 24 | -------------------------------------------------------------------------------- /benchmarks/ssd_ldpfuse_ext4/file-sq-re-4KB-32th-32f/file-sq-re-4KB-32th-32f.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=32 4 | set $meandirwidth=32 5 | set $nthreads=1 6 | #Each thread reading 1.875 G 7 | set $io_size=4k 8 | set $iterations=491520 9 | 10 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirwidth=$meandirwidth, dirgamma=0, size=2g, prealloc 11 | 12 | define process name=filereader,instances=1 13 | { 14 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 15 | { 16 | flowop openfile name=open1, indexed=1, filesetname=bigfileset, fd=1 17 | flowop read name=read-file-1, indexed=1, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 18 | flowop closefile name=close1, indexed=1, fd=1 19 | flowop finishoncount name=finish, value=1 20 | } 21 | 22 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 23 | { 24 | flowop openfile name=open2, indexed=2, filesetname=bigfileset, fd=1 25 | flowop read name=read-file-2, indexed=2, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 26 | flowop closefile name=close2, indexed=2, fd=1 27 | flowop finishoncount name=finish, value=1 28 | } 29 | 30 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 31 | { 32 | flowop openfile name=open3, indexed=3, filesetname=bigfileset, fd=1 33 | flowop read name=read-file-3, indexed=3, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 34 | flowop closefile name=close3, indexed=3, fd=1 35 | flowop finishoncount name=finish,value=1 36 | } 37 | 38 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 39 | { 40 | flowop openfile name=open4, indexed=4, filesetname=bigfileset, fd=1 41 | flowop read name=read-file-4, indexed=4, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 42 | flowop closefile name=close4, indexed=4, fd=1 43 | flowop finishoncount name=finish,value=1 44 | } 45 | 46 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 47 | { 48 | flowop openfile name=open5, indexed=5, filesetname=bigfileset, fd=1 49 | flowop read name=read-file-5, indexed=5, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 50 | flowop closefile name=close5, indexed=5, fd=1 51 | flowop finishoncount name=finish, value=1 52 | } 53 | 54 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 55 | { 56 | flowop openfile name=open6, indexed=6, filesetname=bigfileset, fd=1 57 | flowop read name=read-file-6, indexed=6, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 58 | flowop closefile name=close6, indexed=6, fd=1 59 | flowop finishoncount name=finish,value=1 60 | } 61 | 62 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 63 | { 64 | flowop openfile name=open7, filesetname=bigfileset, fd=1, indexed=7 65 | flowop read name=read-file-7, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=7 66 | flowop closefile name=close7, fd=1, indexed=7 67 | flowop finishoncount name=finish,value=1 68 | } 69 | 70 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 71 | { 72 | flowop openfile name=open8, filesetname=bigfileset, fd=1, indexed=8 73 | flowop read name=read-file-8,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=8 74 | flowop closefile name=close8, fd=1, indexed=8 75 | flowop finishoncount name=finish,value=1 76 | } 77 | 78 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 79 | { 80 | flowop openfile name=open9, filesetname=bigfileset, fd=1, indexed=9 81 | flowop read name=read-file-9, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=9 82 | flowop closefile name=close9, fd=1, indexed=9 83 | flowop finishoncount name=finish, value=1 84 | } 85 | 86 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 87 | { 88 | flowop openfile name=open10, filesetname=bigfileset, fd=1, indexed=10 89 | flowop read name=read-file-10,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=10 90 | flowop closefile name=close10, fd=1, indexed=10 91 | flowop finishoncount name=finish,value=1 92 | } 93 | 94 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 95 | { 96 | flowop openfile name=open11, filesetname=bigfileset, fd=1, indexed=11 97 | flowop read name=read-file-11,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=11 98 | flowop closefile name=close11, fd=1, indexed=11 99 | flowop finishoncount name=finish,value=1 100 | } 101 | 102 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 103 | { 104 | flowop openfile name=open12, filesetname=bigfileset, fd=1, indexed=12 105 | flowop read name=read-file-12,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=12 106 | flowop closefile name=close12, fd=1, indexed=12 107 | flowop finishoncount name=finish,value=1 108 | } 109 | 110 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 111 | { 112 | flowop openfile name=open13, indexed=13, filesetname=bigfileset, fd=1 113 | flowop read name=read-file-13, indexed=13, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 114 | flowop closefile name=close13, indexed=13, fd=1 115 | flowop finishoncount name=finish, value=1 116 | } 117 | 118 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 119 | { 120 | flowop openfile name=open14, indexed=14, filesetname=bigfileset, fd=1 121 | flowop read name=read-file-14, indexed=14, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 122 | flowop closefile name=close14, indexed=14, fd=1 123 | flowop finishoncount name=finish, value=1 124 | } 125 | 126 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 127 | { 128 | flowop openfile name=open15, indexed=15, filesetname=bigfileset, fd=1 129 | flowop read name=read-file-15, indexed=15, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 130 | flowop closefile name=close15, indexed=15, fd=1 131 | flowop finishoncount name=finish,value=1 132 | } 133 | 134 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 135 | { 136 | flowop openfile name=open16, indexed=16, filesetname=bigfileset, fd=1 137 | flowop read name=read-file-16, indexed=16, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 138 | flowop closefile name=close16, indexed=16, fd=1 139 | flowop finishoncount name=finish,value=1 140 | } 141 | 142 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 143 | { 144 | flowop openfile name=open17, indexed=17, filesetname=bigfileset, fd=1 145 | flowop read name=read-file-17, indexed=17, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 146 | flowop closefile name=close17, indexed=17, fd=1 147 | flowop finishoncount name=finish, value=1 148 | } 149 | 150 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 151 | { 152 | flowop openfile name=open18, indexed=18, filesetname=bigfileset, fd=1 153 | flowop read name=read-file-18, indexed=18, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 154 | flowop closefile name=close18, indexed=18, fd=1 155 | flowop finishoncount name=finish,value=1 156 | } 157 | 158 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 159 | { 160 | flowop openfile name=open19, filesetname=bigfileset, fd=1, indexed=19 161 | flowop read name=read-file-19, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=19 162 | flowop closefile name=close19, fd=1, indexed=19 163 | flowop finishoncount name=finish,value=1 164 | } 165 | 166 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 167 | { 168 | flowop openfile name=open20, filesetname=bigfileset, fd=1, indexed=20 169 | flowop read name=read-file-20,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=20 170 | flowop closefile name=close20, fd=1, indexed=20 171 | flowop finishoncount name=finish,value=1 172 | } 173 | 174 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 175 | { 176 | flowop openfile name=open21, filesetname=bigfileset, fd=1, indexed=21 177 | flowop read name=read-file-21, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=21 178 | flowop closefile name=close21, fd=1, indexed=21 179 | flowop finishoncount name=finish, value=1 180 | } 181 | 182 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 183 | { 184 | flowop openfile name=open22, indexed=22, filesetname=bigfileset, fd=1 185 | flowop read name=read-file-22, indexed=22, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 186 | flowop closefile name=close22, indexed=22, fd=1 187 | flowop finishoncount name=finish,value=1 188 | } 189 | 190 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 191 | { 192 | flowop openfile name=open23, indexed=23, filesetname=bigfileset, fd=1 193 | flowop read name=read-file-23, indexed=23, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 194 | flowop closefile name=close23, indexed=23, fd=1 195 | flowop finishoncount name=finish, value=1 196 | } 197 | 198 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 199 | { 200 | flowop openfile name=open24, indexed=24, filesetname=bigfileset, fd=1 201 | flowop read name=read-file-24, indexed=24, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 202 | flowop closefile name=close24, indexed=24, fd=1 203 | flowop finishoncount name=finish, value=1 204 | } 205 | 206 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 207 | { 208 | flowop openfile name=open25, indexed=25, filesetname=bigfileset, fd=1 209 | flowop read name=read-file-25, indexed=25, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 210 | flowop closefile name=close25, indexed=25, fd=1 211 | flowop finishoncount name=finish,value=1 212 | } 213 | 214 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 215 | { 216 | flowop openfile name=open26, indexed=26, filesetname=bigfileset, fd=1 217 | flowop read name=read-file-26, indexed=26, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 218 | flowop closefile name=close26, indexed=26, fd=1 219 | flowop finishoncount name=finish,value=1 220 | } 221 | 222 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 223 | { 224 | flowop openfile name=open27, indexed=27, filesetname=bigfileset, fd=1 225 | flowop read name=read-file-27, indexed=27, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 226 | flowop closefile name=close27, indexed=27, fd=1 227 | flowop finishoncount name=finish, value=1 228 | } 229 | 230 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 231 | { 232 | flowop openfile name=open28, indexed=28, filesetname=bigfileset, fd=1 233 | flowop read name=read-file-28, indexed=28, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 234 | flowop closefile name=close28, indexed=28, fd=1 235 | flowop finishoncount name=finish,value=1 236 | } 237 | 238 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 239 | { 240 | flowop openfile name=open29, filesetname=bigfileset, fd=1, indexed=29 241 | flowop read name=read-file-29, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=29 242 | flowop closefile name=close29, fd=1, indexed=29 243 | flowop finishoncount name=finish,value=1 244 | } 245 | 246 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 247 | { 248 | flowop openfile name=open30, filesetname=bigfileset, fd=1, indexed=30 249 | flowop read name=read-file-30,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=30 250 | flowop closefile name=close30, fd=1, indexed=30 251 | flowop finishoncount name=finish,value=1 252 | } 253 | 254 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 255 | { 256 | flowop openfile name=open31, filesetname=bigfileset, fd=1, indexed=31 257 | flowop read name=read-file-31, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=31 258 | flowop closefile name=close31, fd=1, indexed=31 259 | flowop finishoncount name=finish, value=1 260 | } 261 | 262 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 263 | { 264 | flowop openfile name=open32, filesetname=bigfileset, fd=1, indexed=32 265 | flowop read name=read-file-32,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=32 266 | flowop closefile name=close32, fd=1, indexed=32 267 | flowop finishoncount name=finish,value=1 268 | } 269 | } 270 | 271 | psrun -10 272 | -------------------------------------------------------------------------------- /benchmarks/ssd_ldpfuse_ext4/file_server_50th/file_server_50th.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=200000 4 | set $meandirwidth=20 5 | set $nthreads=50 6 | set $size1=128k 7 | 8 | define fileset name=bigfileset, path=$dir, size=$size1, entries=$nfiles, dirwidth=$meandirwidth, prealloc=80 9 | 10 | define process name=fileserver,instances=1 11 | { 12 | thread name=fileserverthread, memsize=10m, instances=$nthreads 13 | { 14 | flowop createfile name=createfile1,filesetname=bigfileset,fd=1 15 | flowop writewholefile name=wrtfile1,srcfd=1,fd=1,iosize=1m 16 | flowop closefile name=closefile1,fd=1 17 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 18 | flowop appendfilerand name=appendfilerand1,iosize=16k,fd=1 19 | flowop closefile name=closefile2,fd=1 20 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 21 | flowop readwholefile name=readfile1,fd=1,iosize=1m 22 | flowop closefile name=closefile3,fd=1 23 | flowop deletefile name=deletefile1,filesetname=bigfileset 24 | flowop statfile name=statfile1,filesetname=bigfileset 25 | flowop finishoncount name=finish, value=3000000 26 | #So all the above operations will happen together for 3 M (SSD) times 27 | } 28 | } 29 | 30 | create files 31 | #unmount and mount for better stability results 32 | #system "sync" 33 | 34 | #system "umount /tmp/filebench" 35 | #change accordingly for HDD (sdb) and SSD(sdd) 36 | #system "mount -t ext4 /dev/sdd /tmp/filebench" 37 | 38 | #system "sync" 39 | #system "echo 3 > /proc/sys/vm/drop_caches" 40 | 41 | system "echo started >> cpustats.txt" 42 | system "echo started >> diskstats.txt" 43 | 44 | psrun -10 45 | -------------------------------------------------------------------------------- /benchmarks/ssd_ldpfuse_ext4/web_server_100th/web_server_100th.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=125000 4 | set $meandirwidth=20 5 | set $nthreads=100 6 | set $size1=16k 7 | 8 | define fileset name=bigfileset, path=$dir, size=$size1, entries=$nfiles, dirwidth=$meandirwidth, prealloc=100 9 | define fileset name=logfiles, path=$dir, size=$size1, entries=1, dirwidth=$meandirwidth, prealloc 10 | 11 | define process name=webserver,instances=1 12 | { 13 | thread name=webserverthread,memsize=10m,instances=$nthreads 14 | { 15 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 16 | flowop readwholefile name=readfile1,fd=1,iosize=1m 17 | flowop closefile name=closefile1,fd=1 18 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 19 | flowop readwholefile name=readfile2,fd=1,iosize=1m 20 | flowop closefile name=closefile2,fd=1 21 | flowop openfile name=openfile3,filesetname=bigfileset,fd=1 22 | flowop readwholefile name=readfile3,fd=1,iosize=1m 23 | flowop closefile name=closefile3,fd=1 24 | flowop openfile name=openfile4,filesetname=bigfileset,fd=1 25 | flowop readwholefile name=readfile4,fd=1,iosize=1m 26 | flowop closefile name=closefile4,fd=1 27 | flowop openfile name=openfile5,filesetname=bigfileset,fd=1 28 | flowop readwholefile name=readfile5,fd=1,iosize=1m 29 | flowop closefile name=closefile5,fd=1 30 | flowop openfile name=openfile6,filesetname=bigfileset,fd=1 31 | flowop readwholefile name=readfile6,fd=1,iosize=1m 32 | flowop closefile name=closefile6,fd=1 33 | flowop openfile name=openfile7,filesetname=bigfileset,fd=1 34 | flowop readwholefile name=readfile7,fd=1,iosize=1m 35 | flowop closefile name=closefile7,fd=1 36 | flowop openfile name=openfile8,filesetname=bigfileset,fd=1 37 | flowop readwholefile name=readfile8,fd=1,iosize=1m 38 | flowop closefile name=closefile8,fd=1 39 | flowop openfile name=openfile9,filesetname=bigfileset,fd=1 40 | flowop readwholefile name=readfile9,fd=1,iosize=1m 41 | flowop closefile name=closefile9,fd=1 42 | flowop openfile name=openfile10,filesetname=bigfileset,fd=1 43 | flowop readwholefile name=readfile10,fd=1,iosize=1m 44 | flowop closefile name=closefile10,fd=1 45 | flowop appendfilerand name=appendlog,filesetname=logfiles,iosize=16k,fd=2 46 | flowop finishoncount name=finish, value=8000000 47 | #so that all the above operations will together complete 8 M(SSD) ops 48 | } 49 | } 50 | 51 | create files 52 | 53 | psrun -10 54 | -------------------------------------------------------------------------------- /benchmarks/ssd_native_ext4/file-cr-wr-4KB-1th-4Mf/file-cr-wr-4KB-1th-4Mf.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=800000 4 | set $meandirwidth=1000 5 | set $nthreads=1 6 | 7 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirgamma=0, dirwidth=$meandirwidth, size=4k 8 | define process name=fileopen, instances=1 9 | { 10 | thread name=fileopener, memsize=4k, instances=$nthreads 11 | { 12 | flowop createfile name=create1, filesetname=bigfileset 13 | flowop writewholefile name=write-file, filesetname=bigfileset 14 | flowop closefile name=close-file,filesetname=bigfileset 15 | } 16 | } 17 | create files 18 | system "sync" 19 | system "echo 3 > /proc/sys/vm/drop_caches" 20 | run 21 | -------------------------------------------------------------------------------- /benchmarks/ssd_native_ext4/file-cr-wr-4KB-32th-4Mf/file-cr-wr-4KB-32th-4Mf.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | #Fixing I/O amount to be 0.4M files 4 | set $nfiles=400000 5 | set $meandirwidth=1000 6 | set $nthreads=32 7 | 8 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirgamma=0, dirwidth=$meandirwidth, size=4k 9 | define process name=fileopen, instances=1 10 | { 11 | thread name=fileopener, memsize=4k, instances=$nthreads 12 | { 13 | flowop createfile name=create1, filesetname=bigfileset 14 | flowop writewholefile name=write-file, filesetname=bigfileset 15 | flowop closefile name=close-file,filesetname=bigfileset 16 | } 17 | } 18 | 19 | create files 20 | 21 | psrun -10 22 | -------------------------------------------------------------------------------- /benchmarks/ssd_native_ext4/file-sq-re-4KB-1th-1f/file-sq-re-4KB-1th-1f.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=1 4 | set $meandirwidth=1 5 | set $nthreads=1 6 | 7 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirwidth=$meandirwidth, size=60g, prealloc 8 | 9 | define process name=fileopen, instances=1 10 | { 11 | thread name=fileopener, memsize=4k, instances=$nthreads 12 | { 13 | flowop openfile name=open1, filesetname=bigfileset, fd=1 14 | flowop read name=read-file, filesetname=bigfileset, iosize=4k, iters=15728640, fd=1 15 | flowop closefile name=close1, fd=1 16 | flowop finishoncount name=finish, value=1 17 | } 18 | } 19 | 20 | create files 21 | 22 | 23 | psrun -10 24 | -------------------------------------------------------------------------------- /benchmarks/ssd_native_ext4/file-sq-re-4KB-32th-32f/file-sq-re-4KB-32th-32f.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=32 4 | set $meandirwidth=32 5 | set $nthreads=1 6 | #Each thread reading 1.875 G 7 | set $io_size=4k 8 | set $iterations=491520 9 | 10 | define fileset name=bigfileset, path=$dir, entries=$nfiles, dirwidth=$meandirwidth, dirgamma=0, size=2g, prealloc 11 | 12 | define process name=filereader,instances=1 13 | { 14 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 15 | { 16 | flowop openfile name=open1, indexed=1, filesetname=bigfileset, fd=1 17 | flowop read name=read-file-1, indexed=1, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 18 | flowop closefile name=close1, indexed=1, fd=1 19 | flowop finishoncount name=finish, value=1 20 | } 21 | 22 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 23 | { 24 | flowop openfile name=open2, indexed=2, filesetname=bigfileset, fd=1 25 | flowop read name=read-file-2, indexed=2, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 26 | flowop closefile name=close2, indexed=2, fd=1 27 | flowop finishoncount name=finish, value=1 28 | } 29 | 30 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 31 | { 32 | flowop openfile name=open3, indexed=3, filesetname=bigfileset, fd=1 33 | flowop read name=read-file-3, indexed=3, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 34 | flowop closefile name=close3, indexed=3, fd=1 35 | flowop finishoncount name=finish,value=1 36 | } 37 | 38 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 39 | { 40 | flowop openfile name=open4, indexed=4, filesetname=bigfileset, fd=1 41 | flowop read name=read-file-4, indexed=4, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 42 | flowop closefile name=close4, indexed=4, fd=1 43 | flowop finishoncount name=finish,value=1 44 | } 45 | 46 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 47 | { 48 | flowop openfile name=open5, indexed=5, filesetname=bigfileset, fd=1 49 | flowop read name=read-file-5, indexed=5, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 50 | flowop closefile name=close5, indexed=5, fd=1 51 | flowop finishoncount name=finish, value=1 52 | } 53 | 54 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 55 | { 56 | flowop openfile name=open6, indexed=6, filesetname=bigfileset, fd=1 57 | flowop read name=read-file-6, indexed=6, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 58 | flowop closefile name=close6, indexed=6, fd=1 59 | flowop finishoncount name=finish,value=1 60 | } 61 | 62 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 63 | { 64 | flowop openfile name=open7, filesetname=bigfileset, fd=1, indexed=7 65 | flowop read name=read-file-7, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=7 66 | flowop closefile name=close7, fd=1, indexed=7 67 | flowop finishoncount name=finish,value=1 68 | } 69 | 70 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 71 | { 72 | flowop openfile name=open8, filesetname=bigfileset, fd=1, indexed=8 73 | flowop read name=read-file-8,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=8 74 | flowop closefile name=close8, fd=1, indexed=8 75 | flowop finishoncount name=finish,value=1 76 | } 77 | 78 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 79 | { 80 | flowop openfile name=open9, filesetname=bigfileset, fd=1, indexed=9 81 | flowop read name=read-file-9, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=9 82 | flowop closefile name=close9, fd=1, indexed=9 83 | flowop finishoncount name=finish, value=1 84 | } 85 | 86 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 87 | { 88 | flowop openfile name=open10, filesetname=bigfileset, fd=1, indexed=10 89 | flowop read name=read-file-10,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=10 90 | flowop closefile name=close10, fd=1, indexed=10 91 | flowop finishoncount name=finish,value=1 92 | } 93 | 94 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 95 | { 96 | flowop openfile name=open11, filesetname=bigfileset, fd=1, indexed=11 97 | flowop read name=read-file-11,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=11 98 | flowop closefile name=close11, fd=1, indexed=11 99 | flowop finishoncount name=finish,value=1 100 | } 101 | 102 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 103 | { 104 | flowop openfile name=open12, filesetname=bigfileset, fd=1, indexed=12 105 | flowop read name=read-file-12,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=12 106 | flowop closefile name=close12, fd=1, indexed=12 107 | flowop finishoncount name=finish,value=1 108 | } 109 | 110 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 111 | { 112 | flowop openfile name=open13, indexed=13, filesetname=bigfileset, fd=1 113 | flowop read name=read-file-13, indexed=13, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 114 | flowop closefile name=close13, indexed=13, fd=1 115 | flowop finishoncount name=finish, value=1 116 | } 117 | 118 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 119 | { 120 | flowop openfile name=open14, indexed=14, filesetname=bigfileset, fd=1 121 | flowop read name=read-file-14, indexed=14, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 122 | flowop closefile name=close14, indexed=14, fd=1 123 | flowop finishoncount name=finish, value=1 124 | } 125 | 126 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 127 | { 128 | flowop openfile name=open15, indexed=15, filesetname=bigfileset, fd=1 129 | flowop read name=read-file-15, indexed=15, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 130 | flowop closefile name=close15, indexed=15, fd=1 131 | flowop finishoncount name=finish,value=1 132 | } 133 | 134 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 135 | { 136 | flowop openfile name=open16, indexed=16, filesetname=bigfileset, fd=1 137 | flowop read name=read-file-16, indexed=16, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 138 | flowop closefile name=close16, indexed=16, fd=1 139 | flowop finishoncount name=finish,value=1 140 | } 141 | 142 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 143 | { 144 | flowop openfile name=open17, indexed=17, filesetname=bigfileset, fd=1 145 | flowop read name=read-file-17, indexed=17, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 146 | flowop closefile name=close17, indexed=17, fd=1 147 | flowop finishoncount name=finish, value=1 148 | } 149 | 150 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 151 | { 152 | flowop openfile name=open18, indexed=18, filesetname=bigfileset, fd=1 153 | flowop read name=read-file-18, indexed=18, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 154 | flowop closefile name=close18, indexed=18, fd=1 155 | flowop finishoncount name=finish,value=1 156 | } 157 | 158 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 159 | { 160 | flowop openfile name=open19, filesetname=bigfileset, fd=1, indexed=19 161 | flowop read name=read-file-19, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=19 162 | flowop closefile name=close19, fd=1, indexed=19 163 | flowop finishoncount name=finish,value=1 164 | } 165 | 166 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 167 | { 168 | flowop openfile name=open20, filesetname=bigfileset, fd=1, indexed=20 169 | flowop read name=read-file-20,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=20 170 | flowop closefile name=close20, fd=1, indexed=20 171 | flowop finishoncount name=finish,value=1 172 | } 173 | 174 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 175 | { 176 | flowop openfile name=open21, filesetname=bigfileset, fd=1, indexed=21 177 | flowop read name=read-file-21, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=21 178 | flowop closefile name=close21, fd=1, indexed=21 179 | flowop finishoncount name=finish, value=1 180 | } 181 | 182 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 183 | { 184 | flowop openfile name=open22, indexed=22, filesetname=bigfileset, fd=1 185 | flowop read name=read-file-22, indexed=22, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 186 | flowop closefile name=close22, indexed=22, fd=1 187 | flowop finishoncount name=finish,value=1 188 | } 189 | 190 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 191 | { 192 | flowop openfile name=open23, indexed=23, filesetname=bigfileset, fd=1 193 | flowop read name=read-file-23, indexed=23, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 194 | flowop closefile name=close23, indexed=23, fd=1 195 | flowop finishoncount name=finish, value=1 196 | } 197 | 198 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 199 | { 200 | flowop openfile name=open24, indexed=24, filesetname=bigfileset, fd=1 201 | flowop read name=read-file-24, indexed=24, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 202 | flowop closefile name=close24, indexed=24, fd=1 203 | flowop finishoncount name=finish, value=1 204 | } 205 | 206 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 207 | { 208 | flowop openfile name=open25, indexed=25, filesetname=bigfileset, fd=1 209 | flowop read name=read-file-25, indexed=25, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 210 | flowop closefile name=close25, indexed=25, fd=1 211 | flowop finishoncount name=finish,value=1 212 | } 213 | 214 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 215 | { 216 | flowop openfile name=open26, indexed=26, filesetname=bigfileset, fd=1 217 | flowop read name=read-file-26, indexed=26, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 218 | flowop closefile name=close26, indexed=26, fd=1 219 | flowop finishoncount name=finish,value=1 220 | } 221 | 222 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 223 | { 224 | flowop openfile name=open27, indexed=27, filesetname=bigfileset, fd=1 225 | flowop read name=read-file-27, indexed=27, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 226 | flowop closefile name=close27, indexed=27, fd=1 227 | flowop finishoncount name=finish, value=1 228 | } 229 | 230 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 231 | { 232 | flowop openfile name=open28, indexed=28, filesetname=bigfileset, fd=1 233 | flowop read name=read-file-28, indexed=28, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1 234 | flowop closefile name=close28, indexed=28, fd=1 235 | flowop finishoncount name=finish,value=1 236 | } 237 | 238 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 239 | { 240 | flowop openfile name=open29, filesetname=bigfileset, fd=1, indexed=29 241 | flowop read name=read-file-29, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=29 242 | flowop closefile name=close29, fd=1, indexed=29 243 | flowop finishoncount name=finish,value=1 244 | } 245 | 246 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 247 | { 248 | flowop openfile name=open30, filesetname=bigfileset, fd=1, indexed=30 249 | flowop read name=read-file-30,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=30 250 | flowop closefile name=close30, fd=1, indexed=30 251 | flowop finishoncount name=finish,value=1 252 | } 253 | 254 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 255 | { 256 | flowop openfile name=open31, filesetname=bigfileset, fd=1, indexed=31 257 | flowop read name=read-file-31, filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=31 258 | flowop closefile name=close31, fd=1, indexed=31 259 | flowop finishoncount name=finish, value=1 260 | } 261 | 262 | thread name=filereaderthread,memsize=$io_size, instances=$nthreads 263 | { 264 | flowop openfile name=open32, filesetname=bigfileset, fd=1, indexed=32 265 | flowop read name=read-file-32,filesetname=bigfileset, iosize=$io_size, iters=$iterations, fd=1, indexed=32 266 | flowop closefile name=close32, fd=1, indexed=32 267 | flowop finishoncount name=finish,value=1 268 | } 269 | } 270 | 271 | psrun -10 272 | -------------------------------------------------------------------------------- /benchmarks/ssd_native_ext4/file_server_50th/file_server_50th.f: -------------------------------------------------------------------------------- 1 | set mode quit alldone 2 | set $dir=/tmp/filebench 3 | set $nfiles=200000 4 | set $meandirwidth=20 5 | set $nthreads=50 6 | set $size1=128k 7 | 8 | define fileset name=bigfileset, path=$dir, size=$size1, entries=$nfiles, dirwidth=$meandirwidth, prealloc=80 9 | 10 | define process name=fileserver,instances=1 11 | { 12 | thread name=fileserverthread, memsize=10m, instances=$nthreads 13 | { 14 | flowop createfile name=createfile1,filesetname=bigfileset,fd=1 15 | flowop writewholefile name=wrtfile1,srcfd=1,fd=1,iosize=1m 16 | flowop closefile name=closefile1,fd=1 17 | flowop openfile name=openfile1,filesetname=bigfileset,fd=1 18 | flowop appendfilerand name=appendfilerand1,iosize=16k,fd=1 19 | flowop closefile name=closefile2,fd=1 20 | flowop openfile name=openfile2,filesetname=bigfileset,fd=1 21 | flowop readwholefile name=readfile1,fd=1,iosize=1m 22 | flowop closefile name=closefile3,fd=1 23 | flowop deletefile name=deletefile1,filesetname=bigfileset 24 | flowop statfile name=statfile1,filesetname=bigfileset 25 | flowop finishoncount name=finish, value=3000000 26 | #So all the above operations will happen together for 3 M (SSD) times 27 | } 28 | } 29 | 30 | create files 31 | #unmount and mount for better stability results 32 | #system "sync" 33 | 34 | #system "umount /tmp/filebench" 35 | #change accordingly for HDD (sdb) and SSD(sdd) 36 | #system "mount -t ext4 /dev/sdd /tmp/filebench" 37 | 38 | #system "sync" 39 | #system "echo 3 > /proc/sys/vm/drop_caches" 40 | 41 | system "echo started >> cpustats.txt" 42 | system "echo started >> diskstats.txt" 43 | 44 | psrun -10 45 | -------------------------------------------------------------------------------- /cli/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /cli/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.57" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.14" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 16 | dependencies = [ 17 | "hermit-abi", 18 | "libc", 19 | "winapi", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.1.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "1.3.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 33 | 34 | [[package]] 35 | name = "cfg-if" 36 | version = "1.0.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 39 | 40 | [[package]] 41 | name = "clap" 42 | version = "3.0.14" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" 45 | dependencies = [ 46 | "atty", 47 | "bitflags", 48 | "clap_derive", 49 | "indexmap", 50 | "lazy_static", 51 | "os_str_bytes", 52 | "strsim", 53 | "termcolor", 54 | "textwrap", 55 | ] 56 | 57 | [[package]] 58 | name = "clap_derive" 59 | version = "3.0.14" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "9a1132dc3944b31c20dd8b906b3a9f0a5d0243e092d59171414969657ac6aa85" 62 | dependencies = [ 63 | "heck", 64 | "proc-macro-error", 65 | "proc-macro2", 66 | "quote", 67 | "syn", 68 | ] 69 | 70 | [[package]] 71 | name = "hashbrown" 72 | version = "0.11.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 75 | 76 | [[package]] 77 | name = "heck" 78 | version = "0.4.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 81 | 82 | [[package]] 83 | name = "hermit-abi" 84 | version = "0.1.19" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 87 | dependencies = [ 88 | "libc", 89 | ] 90 | 91 | [[package]] 92 | name = "indexmap" 93 | version = "1.8.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 96 | dependencies = [ 97 | "autocfg", 98 | "hashbrown", 99 | ] 100 | 101 | [[package]] 102 | name = "lazy_static" 103 | version = "1.4.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 106 | 107 | [[package]] 108 | name = "ldpfuse" 109 | version = "0.1.0" 110 | dependencies = [ 111 | "anyhow", 112 | "clap", 113 | "nix", 114 | ] 115 | 116 | [[package]] 117 | name = "libc" 118 | version = "0.2.126" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 121 | 122 | [[package]] 123 | name = "memchr" 124 | version = "2.4.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 127 | 128 | [[package]] 129 | name = "memoffset" 130 | version = "0.6.5" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 133 | dependencies = [ 134 | "autocfg", 135 | ] 136 | 137 | [[package]] 138 | name = "nix" 139 | version = "0.24.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" 142 | dependencies = [ 143 | "bitflags", 144 | "cfg-if", 145 | "libc", 146 | "memoffset", 147 | ] 148 | 149 | [[package]] 150 | name = "os_str_bytes" 151 | version = "6.0.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 154 | dependencies = [ 155 | "memchr", 156 | ] 157 | 158 | [[package]] 159 | name = "proc-macro-error" 160 | version = "1.0.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 163 | dependencies = [ 164 | "proc-macro-error-attr", 165 | "proc-macro2", 166 | "quote", 167 | "syn", 168 | "version_check", 169 | ] 170 | 171 | [[package]] 172 | name = "proc-macro-error-attr" 173 | version = "1.0.4" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 176 | dependencies = [ 177 | "proc-macro2", 178 | "quote", 179 | "version_check", 180 | ] 181 | 182 | [[package]] 183 | name = "proc-macro2" 184 | version = "1.0.36" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 187 | dependencies = [ 188 | "unicode-xid", 189 | ] 190 | 191 | [[package]] 192 | name = "quote" 193 | version = "1.0.15" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 196 | dependencies = [ 197 | "proc-macro2", 198 | ] 199 | 200 | [[package]] 201 | name = "strsim" 202 | version = "0.10.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 205 | 206 | [[package]] 207 | name = "syn" 208 | version = "1.0.86" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 211 | dependencies = [ 212 | "proc-macro2", 213 | "quote", 214 | "unicode-xid", 215 | ] 216 | 217 | [[package]] 218 | name = "termcolor" 219 | version = "1.1.2" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 222 | dependencies = [ 223 | "winapi-util", 224 | ] 225 | 226 | [[package]] 227 | name = "textwrap" 228 | version = "0.14.2" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 231 | 232 | [[package]] 233 | name = "unicode-xid" 234 | version = "0.2.2" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 237 | 238 | [[package]] 239 | name = "version_check" 240 | version = "0.9.4" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 243 | 244 | [[package]] 245 | name = "winapi" 246 | version = "0.3.9" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 249 | dependencies = [ 250 | "winapi-i686-pc-windows-gnu", 251 | "winapi-x86_64-pc-windows-gnu", 252 | ] 253 | 254 | [[package]] 255 | name = "winapi-i686-pc-windows-gnu" 256 | version = "0.4.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 259 | 260 | [[package]] 261 | name = "winapi-util" 262 | version = "0.1.5" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 265 | dependencies = [ 266 | "winapi", 267 | ] 268 | 269 | [[package]] 270 | name = "winapi-x86_64-pc-windows-gnu" 271 | version = "0.4.0" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 274 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ldpfuse" 3 | version = "0.1.0" 4 | authors = ["Sjors Holtrop "] 5 | edition = "2021" 6 | keywords = ["filesystem", "fuse", "hpc"] 7 | categories = ["command-line-utilities", "filesystem"] 8 | description = "CLI utility to be used with LDP_FUSE. See https://github.com/sholtrop/ldpfuse for more info." 9 | documentation = "https://github.com/sholtrop/ldpfuse" 10 | repository = "https://github.com/sholtrop/ldpfuse" 11 | license = "MIT" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | anyhow = "1.0.57" 17 | clap = { version = "3.0.14", features = ["derive"] } 18 | nix = "0.24.1" -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use std::os::unix::process::CommandExt; 4 | use std::process::Command; 5 | use std::{fmt::Display, fs, path::PathBuf, str::FromStr}; 6 | 7 | const LDP_FUSE_PATH: &str = "LDP_FUSE_PATH"; 8 | 9 | #[derive(Parser, Debug)] 10 | #[clap( 11 | about = "This is a CLI tool for running programs under LDP_FUSE file systems. See https://github.com/sholtrop/ldpfuse for info on how to make an LDP_FUSE file system." 12 | )] 13 | pub struct CliArgs { 14 | /// Program to execute, with the specified `.so` as its filesystem. 15 | #[clap(multiple_occurrences = true)] 16 | program: Vec, 17 | 18 | #[clap(short, long)] 19 | /// Path to the shared object `.so` file that has been compiled using the `ldpfuse.h` library. 20 | /// This file will be used as a filesystem for the executed program. 21 | so_path: AbsolutePath, 22 | 23 | #[clap(short, long)] 24 | /// Path under which the filesystem is mounted (= active). 25 | /// The program that is executed will ignore the ldpfuse filesystem for paths that do not match this one. 26 | mount_path: AbsolutePath, 27 | 28 | #[clap(short = 'v')] 29 | /// List debug output. 30 | verbose: bool, 31 | } 32 | 33 | #[derive(Debug)] 34 | struct AbsolutePath(PathBuf); 35 | 36 | impl FromStr for AbsolutePath { 37 | type Err = anyhow::Error; 38 | 39 | fn from_str(s: &str) -> Result { 40 | let path = fs::canonicalize(s) 41 | .map_err(|e| anyhow::anyhow!("Error making path {} absolute: {}", s, e))?; 42 | Ok(Self(path)) 43 | } 44 | } 45 | 46 | impl Display for AbsolutePath { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | write!(f, "{}", self.0.display()) 49 | } 50 | } 51 | 52 | fn main() { 53 | let CliArgs { 54 | mount_path, 55 | so_path, 56 | program, 57 | verbose, 58 | } = CliArgs::parse(); 59 | let env = std::env::vars().chain([ 60 | ("LD_PRELOAD".into(), so_path.to_string()), 61 | (LDP_FUSE_PATH.into(), mount_path.to_string()), 62 | ]); 63 | 64 | if verbose { 65 | eprintln!( 66 | "Running {}\nFilesystem shared object: {so_path}\nMounted under: {mount_path}", 67 | program.join(" ") 68 | ); 69 | } 70 | let err = Command::new(&program[0]) 71 | .args(&program[1..]) 72 | .envs(env) 73 | .exec(); 74 | eprintln!("{err}"); 75 | } 76 | -------------------------------------------------------------------------------- /examples/encrypted/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so -------------------------------------------------------------------------------- /examples/encrypted/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -g -I /home/sholtrop/development/cpp/bsc/ldpreload_fuse/include 3 | SOFLAGS = -shared -fPIC 4 | LDFLAGS = -ldl 5 | HEADERS = /home/sholtrop/development/cpp/bsc/ldpreload_fuse/include/ldpfuse.h 6 | SO_NAME = encrypted 7 | # TEST_NAME = test 8 | 9 | all: $(SO_NAME).so # $(TEST_NAME) 10 | 11 | # $(TEST_NAME): $(TEST_NAME).o 12 | # $(CC) $(CFLAGS) $(TEST_NAME).o -o $(TEST_NAME) 13 | 14 | $(SO_NAME).so: $(SO_NAME).o 15 | $(CC) $(CFLAGS) $(SOFLAGS) $(HEADERS) -o $@ $(LDFLAGS) 16 | 17 | %.o: %.c 18 | $(CC) $(CFLAGS) -c $< 19 | 20 | .PHONY: clean 21 | clean: 22 | rm -f *.o $(SO_NAME).so -------------------------------------------------------------------------------- /examples/encrypted/encrypted.c: -------------------------------------------------------------------------------- 1 | // WARNING: This is not cryptographically secure 2 | 3 | #include "ldpfuse.h" 4 | #include 5 | #include 6 | 7 | #define KEY 0b11110000 8 | 9 | ssize_t encrypted_read(int fd, void *buf, size_t count, off_t offset, 10 | orig_pread_t read_fn) { 11 | 12 | ssize_t read_bytes = read_fn(fd, buf, count, offset); 13 | for (size_t i = 0; i < read_bytes; i++) { 14 | ((unsigned char *)buf)[i] ^= KEY; 15 | } 16 | return read_bytes; 17 | } 18 | 19 | ssize_t encrypted_write(int fd, const void *buf, size_t count, off_t offset, 20 | orig_pwrite_t write_fn) { 21 | 22 | for (size_t i = 0; i < count; i++) { 23 | ((unsigned char *)buf)[i] ^= KEY; 24 | } 25 | 26 | return write_fn(fd, buf, count, offset); 27 | } 28 | 29 | LDPRELOAD_FUSE_MAIN { 30 | struct ldp_fuse_funcs funcs; 31 | memset(&funcs, 0, sizeof(funcs)); 32 | funcs.read = encrypted_read; 33 | funcs.write = encrypted_write; 34 | ldp_fuse_init(&funcs); 35 | } -------------------------------------------------------------------------------- /examples/passthrough/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so -------------------------------------------------------------------------------- /examples/passthrough/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -I ../../ldp_fuse/include -fPIC 3 | SOFLAGS = -shared 4 | LDFLAGS = -ldl -pthread 5 | DEF_FLAGS = -D LDP_FUSE_THREAD_SAFE 6 | DEBUG_FLAGS = -D LDP_FUSE_DEBUG -g 7 | RELEASE_FLAGS = -O3 8 | SO_NAME=passthrough 9 | 10 | all: $(SO_NAME).so 11 | 12 | debug: $(SO_NAME)_debug.so 13 | 14 | $(SO_NAME)_debug.so: 15 | $(CC) $(DEF_FLAGS) $(DEBUG_FLAGS) $(CFLAGS) $(SOFLAGS) -o $@ $(SO_NAME).c $(LDFLAGS) 16 | 17 | $(SO_NAME).so: $(SO_NAME).o 18 | $(CC) $(DEF_FLAGS) $(RELEASE_FLAGS) $(CFLAGS) $(SOFLAGS) -o $@ $< $(LDFLAGS) 19 | 20 | %.o: %.c 21 | $(CC) $(DEF_FLAGS) $(CFLAGS) -c $< 22 | 23 | .PHONY: clean 24 | clean: 25 | rm -f *.o $(SO_NAME).so $(SO_NAME)_debug.so -------------------------------------------------------------------------------- /examples/passthrough/passthrough.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ldpfuse.h" 3 | 4 | ssize_t simple_read(int fd, void *buf, size_t count, off_t offset, 5 | orig_pread_t pread_fn) { 6 | LDP_FUSE_DEBUG_PRINT("simple_read\n"); 7 | return pread_fn(fd, buf, count, offset); 8 | } 9 | 10 | int simple_stat(int dirfd, const char *restrict pathname, 11 | struct stat *restrict statbuf, int flags, 12 | orig_fstatat_t fstatat_fn) { 13 | 14 | LDP_FUSE_DEBUG_PRINT("simple_stat\n"); 15 | return fstatat_fn(dirfd, pathname, statbuf, flags); 16 | } 17 | 18 | int simple_open(int dirfd, const char *pathname, int flags, mode_t mode, 19 | orig_openat_t openat_fn) { 20 | LDP_FUSE_DEBUG_PRINT("simple_open\n"); 21 | return openat_fn(dirfd, pathname, flags, mode); 22 | } 23 | 24 | ssize_t simple_write(int fd, const void *buf, size_t count, off_t offset, 25 | orig_pwrite_t pwrite_fn) { 26 | return pwrite_fn(fd, buf, count, 0); 27 | } 28 | 29 | ssize_t simple_readlink(const char *path, char *buf, size_t bufsize, 30 | orig_readlink_t readlink_fn) { 31 | LDP_FUSE_DEBUG_PRINT("simple_readlink\n"); 32 | return readlink_fn(path, buf, bufsize); 33 | } 34 | 35 | int simple_mknod(int fd, const char *path, mode_t mode, dev_t dev, 36 | orig_mknodat_t mknod_fn) { 37 | LDP_FUSE_DEBUG_PRINT("simple_mknod\n"); 38 | return mknod_fn(fd, path, mode, dev); 39 | } 40 | 41 | int simple_mkdir(const char *path, mode_t mode, orig_mkdir_t mkdir_fn) { 42 | LDP_FUSE_DEBUG_PRINT("simple_mkdir\n"); 43 | return mkdir_fn(path, mode); 44 | } 45 | 46 | int simple_rmdir(const char *path, orig_rmdir_t rmdir_fn) { 47 | LDP_FUSE_DEBUG_PRINT("simple_rmdir\n"); 48 | return rmdir_fn(path); 49 | } 50 | 51 | int simple_symlink(const char *path1, const char *path2, 52 | orig_symlink_t symlink_fn) { 53 | LDP_FUSE_DEBUG_PRINT("simple_symlink\n"); 54 | return symlink_fn(path1, path2); 55 | } 56 | 57 | int simple_rename(const char *path1, const char *path2, 58 | orig_rename_t rename_fn) { 59 | LDP_FUSE_DEBUG_PRINT("simple_rename\n"); 60 | return rename_fn(path1, path2); 61 | } 62 | 63 | int simple_link(const char *path1, const char *path2, orig_link_t link_fn) { 64 | LDP_FUSE_DEBUG_PRINT("simple_link\n"); 65 | return link_fn(path1, path2); 66 | } 67 | 68 | int simple_chmod(const char *path, mode_t mode, orig_chmod_t chmod_fn) { 69 | LDP_FUSE_DEBUG_PRINT("simple_chmod\n"); 70 | return chmod_fn(path, mode); 71 | } 72 | 73 | int simple_chown(const char *path, uid_t owner, gid_t group, 74 | orig_chown_t chown_fn) { 75 | LDP_FUSE_DEBUG_PRINT("simple_chown\n"); 76 | return chown_fn(path, owner, group); 77 | } 78 | 79 | int simple_truncate(const char *path, off_t size, orig_truncate_t truncate_fn) { 80 | LDP_FUSE_DEBUG_PRINT("simple_truncate\n"); 81 | return truncate_fn(path, size); 82 | } 83 | 84 | int simple_close(int fd, orig_close_t close_fn) { 85 | LDP_FUSE_DEBUG_PRINT("simple_close\n"); 86 | return close_fn(fd); 87 | } 88 | 89 | DIR *simple_opendir(const char *path, orig_opendir_t opendir_fn) { 90 | LDP_FUSE_DEBUG_PRINT("simple_opendir\n"); 91 | return opendir_fn(path); 92 | } 93 | 94 | int simple_faccessat(const char *path, int mode, orig_faccessat_t access_fn) { 95 | LDP_FUSE_DEBUG_PRINT("simple_faccessat\n"); 96 | return access_fn(path, mode); 97 | } 98 | 99 | int simple_unlink(const char *path, orig_unlink_t unlink_fn) { 100 | LDP_FUSE_DEBUG_PRINT("simple_unlink\n"); 101 | return unlink_fn(path); 102 | } 103 | 104 | ssize_t simple_getxattr(const char *path, const char *name, void *value, 105 | size_t size, orig_getxattr_t getxattr_fn) { 106 | LDP_FUSE_DEBUG_PRINT("simple_getxattr\n"); 107 | return getxattr_fn(path, name, value, size); 108 | } 109 | 110 | LDPRELOAD_FUSE_MAIN { 111 | struct ldp_fuse_funcs funcs = {.read = simple_read, 112 | .stat = simple_stat, 113 | .open = simple_open, 114 | .write = simple_write, 115 | .readlink = simple_readlink, 116 | .mknod = simple_mknod, 117 | .mkdir = simple_mkdir, 118 | .unlink = simple_unlink, 119 | .rmdir = simple_rmdir, 120 | .symlink = simple_symlink, 121 | .rename = simple_rename, 122 | .link = simple_link, 123 | .chmod = simple_chmod, 124 | .chown = simple_chown, 125 | .truncate = simple_truncate, 126 | .close = simple_close, 127 | .opendir = simple_opendir, 128 | .access = simple_faccessat, 129 | .getxattr = simple_getxattr}; 130 | ldp_fuse_init(&funcs); 131 | } -------------------------------------------------------------------------------- /ldp_fuse/include/cwalk.c: -------------------------------------------------------------------------------- 1 | // This library originates from: 2 | // https://github.com/likle/cwalk 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * We try to default to a different path style depending on the operating 13 | * system. So this should detect whether we should use windows or unix paths. 14 | */ 15 | #if defined(WIN32) || defined(_WIN32) || \ 16 | defined(__WIN32) && !defined(__CYGWIN__) 17 | static enum cwk_path_style path_style = CWK_STYLE_WINDOWS; 18 | #else 19 | static enum cwk_path_style path_style = CWK_STYLE_UNIX; 20 | #endif 21 | 22 | /** 23 | * This is a list of separators used in different styles. Windows can read 24 | * multiple separators, but it generally outputs just a backslash. The output 25 | * will always use the first character for the output. 26 | */ 27 | static const char *separators[] = { 28 | "\\/", // CWK_STYLE_WINDOWS 29 | "/" // CWK_STYLE_UNIX 30 | }; 31 | 32 | /** 33 | * A joined path represents multiple path strings which are concatenated, but 34 | * not (necessarily) stored in contiguous memory. The joined path allows to 35 | * iterate over the segments as if it was one piece of path. 36 | */ 37 | struct cwk_segment_joined { 38 | struct cwk_segment segment; 39 | const char **paths; 40 | size_t path_index; 41 | }; 42 | 43 | static size_t cwk_path_output_sized(char *buffer, size_t buffer_size, 44 | size_t position, const char *str, 45 | size_t length) { 46 | size_t amount_written; 47 | 48 | // First we determine the amount which we can write to the buffer. There are 49 | // three cases. In the first case we have enough to store the whole string in 50 | // it. In the second one we can only store a part of it, and in the third we 51 | // have no space left. 52 | if (buffer_size > position + length) { 53 | amount_written = length; 54 | } else if (buffer_size > position) { 55 | amount_written = buffer_size - position; 56 | } else { 57 | amount_written = 0; 58 | } 59 | 60 | // If we actually want to write out something we will do that here. We will 61 | // always append a '\0', this way we are guaranteed to have a valid string at 62 | // all times. 63 | if (amount_written > 0) { 64 | memmove(&buffer[position], str, amount_written); 65 | } 66 | 67 | // Return the theoretical length which would have been written when everything 68 | // would have fit in the buffer. 69 | return length; 70 | } 71 | 72 | static size_t cwk_path_output_current(char *buffer, size_t buffer_size, 73 | size_t position) { 74 | // We output a "current" directory, which is a single character. This 75 | // character is currently not style dependant. 76 | return cwk_path_output_sized(buffer, buffer_size, position, ".", 1); 77 | } 78 | 79 | static size_t cwk_path_output_back(char *buffer, size_t buffer_size, 80 | size_t position) { 81 | // We output a "back" directory, which ahs two characters. This 82 | // character is currently not style dependant. 83 | return cwk_path_output_sized(buffer, buffer_size, position, "..", 2); 84 | } 85 | 86 | static size_t cwk_path_output_separator(char *buffer, size_t buffer_size, 87 | size_t position) { 88 | // We output a separator, which is a single character. 89 | return cwk_path_output_sized(buffer, buffer_size, position, 90 | separators[path_style], 1); 91 | } 92 | 93 | static size_t cwk_path_output_dot(char *buffer, size_t buffer_size, 94 | size_t position) { 95 | // We output a dot, which is a single character. This is used for extensions. 96 | return cwk_path_output_sized(buffer, buffer_size, position, ".", 1); 97 | } 98 | 99 | static size_t cwk_path_output(char *buffer, size_t buffer_size, size_t position, 100 | const char *str) { 101 | size_t length; 102 | 103 | // This just does a sized output internally, but first measuring the 104 | // null-terminated string. 105 | length = strlen(str); 106 | return cwk_path_output_sized(buffer, buffer_size, position, str, length); 107 | } 108 | 109 | static void cwk_path_terminate_output(char *buffer, size_t buffer_size, 110 | size_t pos) { 111 | if (buffer_size > 0) { 112 | if (pos >= buffer_size) { 113 | buffer[buffer_size - 1] = '\0'; 114 | } else { 115 | buffer[pos] = '\0'; 116 | } 117 | } 118 | } 119 | 120 | static bool cwk_path_is_string_equal(const char *first, const char *second, 121 | size_t first_size, size_t second_size) { 122 | bool are_both_separators; 123 | 124 | // The two strings are not equal if the sizes are not equal. 125 | if (first_size != second_size) { 126 | return false; 127 | } 128 | 129 | // If the path style is UNIX, we will compare case sensitively. This can be 130 | // done easily using strncmp. 131 | if (path_style == CWK_STYLE_UNIX) { 132 | return strncmp(first, second, first_size) == 0; 133 | } 134 | 135 | // However, if this is windows we will have to compare case insensitively. 136 | // Since there is no standard method to do that we will have to do it on our 137 | // own. 138 | while (*first && *second && first_size > 0) { 139 | // We can consider the string to be not equal if the two lowercase 140 | // characters are not equal. The two chars may also be separators, which 141 | // means they would be equal. 142 | are_both_separators = strchr(separators[path_style], *first) != NULL && 143 | strchr(separators[path_style], *second) != NULL; 144 | 145 | if (tolower(*first) != tolower(*second) && !are_both_separators) { 146 | return false; 147 | } 148 | 149 | first++; 150 | second++; 151 | 152 | --first_size; 153 | } 154 | 155 | // The string must be equal since they both have the same length and all the 156 | // characters are the same. 157 | return true; 158 | } 159 | 160 | static const char *cwk_path_find_next_stop(const char *c) { 161 | // We just move forward until we find a '\0' or a separator, which will be our 162 | // next "stop". 163 | while (*c != '\0' && !cwk_path_is_separator(c)) { 164 | ++c; 165 | } 166 | 167 | // Return the pointer of the next stop. 168 | return c; 169 | } 170 | 171 | static const char *cwk_path_find_previous_stop(const char *begin, 172 | const char *c) { 173 | // We just move back until we find a separator or reach the beginning of the 174 | // path, which will be our previous "stop". 175 | while (c > begin && !cwk_path_is_separator(c)) { 176 | --c; 177 | } 178 | 179 | // Return the pointer to the previous stop. We have to return the first 180 | // character after the separator, not on the separator itself. 181 | if (cwk_path_is_separator(c)) { 182 | return c + 1; 183 | } else { 184 | return c; 185 | } 186 | } 187 | 188 | static bool 189 | cwk_path_get_first_segment_without_root(const char *path, const char *segments, 190 | struct cwk_segment *segment) { 191 | // Let's remember the path. We will move the path pointer afterwards, that's 192 | // why this has to be done first. 193 | segment->path = path; 194 | segment->segments = segments; 195 | segment->begin = segments; 196 | segment->end = segments; 197 | segment->size = 0; 198 | 199 | // Now let's check whether this is an empty string. An empty string has no 200 | // segment it could use. 201 | if (*segments == '\0') { 202 | return false; 203 | } 204 | 205 | // If the string starts with separators, we will jump over those. If there is 206 | // only a slash and a '\0' after it, we can't determine the first segment 207 | // since there is none. 208 | while (cwk_path_is_separator(segments)) { 209 | ++segments; 210 | if (*segments == '\0') { 211 | return false; 212 | } 213 | } 214 | 215 | // So this is the beginning of our segment. 216 | segment->begin = segments; 217 | 218 | // Now let's determine the end of the segment, which we do by moving the path 219 | // pointer further until we find a separator. 220 | segments = cwk_path_find_next_stop(segments); 221 | 222 | // And finally, calculate the size of the segment by subtracting the position 223 | // from the end. 224 | segment->size = (size_t)(segments - segment->begin); 225 | segment->end = segments; 226 | 227 | // Tell the caller that we found a segment. 228 | return true; 229 | } 230 | 231 | static bool 232 | cwk_path_get_last_segment_without_root(const char *path, 233 | struct cwk_segment *segment) { 234 | // Now this is fairly similar to the normal algorithm, however, it will assume 235 | // that there is no root in the path. So we grab the first segment at this 236 | // position, assuming there is no root. 237 | if (!cwk_path_get_first_segment_without_root(path, path, segment)) { 238 | return false; 239 | } 240 | 241 | // Now we find our last segment. The segment struct of the caller 242 | // will contain the last segment, since the function we call here will not 243 | // change the segment struct when it reaches the end. 244 | while (cwk_path_get_next_segment(segment)) { 245 | // We just loop until there is no other segment left. 246 | } 247 | 248 | return true; 249 | } 250 | 251 | static bool cwk_path_get_first_segment_joined(const char **paths, 252 | struct cwk_segment_joined *sj) { 253 | bool result; 254 | 255 | // Prepare the first segment. We position the joined segment on the first path 256 | // and assign the path array to the struct. 257 | sj->path_index = 0; 258 | sj->paths = paths; 259 | 260 | // We loop through all paths until we find one which has a segment. The result 261 | // is stored in a variable, so we can let the caller know whether we found one 262 | // or not. 263 | result = false; 264 | while (paths[sj->path_index] != NULL && 265 | (result = cwk_path_get_first_segment(paths[sj->path_index], 266 | &sj->segment)) == false) { 267 | ++sj->path_index; 268 | } 269 | 270 | return result; 271 | } 272 | 273 | static bool cwk_path_get_next_segment_joined(struct cwk_segment_joined *sj) { 274 | bool result; 275 | 276 | if (sj->paths[sj->path_index] == NULL) { 277 | // We reached already the end of all paths, so there is no other segment 278 | // left. 279 | return false; 280 | } else if (cwk_path_get_next_segment(&sj->segment)) { 281 | // There was another segment on the current path, so we are good to 282 | // continue. 283 | return true; 284 | } 285 | 286 | // We try to move to the next path which has a segment available. We must at 287 | // least move one further since the current path reached the end. 288 | result = false; 289 | 290 | do { 291 | ++sj->path_index; 292 | 293 | // And we obviously have to stop this loop if there are no more paths left. 294 | if (sj->paths[sj->path_index] == NULL) { 295 | break; 296 | } 297 | 298 | // Grab the first segment of the next path and determine whether this path 299 | // has anything useful in it. There is one more thing we have to consider 300 | // here - for the first time we do this we want to skip the root, but 301 | // afterwards we will consider that to be part of the segments. 302 | result = cwk_path_get_first_segment_without_root( 303 | sj->paths[sj->path_index], sj->paths[sj->path_index], &sj->segment); 304 | 305 | } while (!result); 306 | 307 | // Finally, report the result back to the caller. 308 | return result; 309 | } 310 | 311 | static bool 312 | cwk_path_get_previous_segment_joined(struct cwk_segment_joined *sj) { 313 | bool result; 314 | 315 | if (*sj->paths == NULL) { 316 | // It's possible that there is no initialized segment available in the 317 | // struct since there are no paths. In that case we can return false, since 318 | // there is no previous segment. 319 | return false; 320 | } else if (cwk_path_get_previous_segment(&sj->segment)) { 321 | // Now we try to get the previous segment from the current path. If we can 322 | // do that successfully, we can let the caller know that we found one. 323 | return true; 324 | } 325 | 326 | result = false; 327 | 328 | do { 329 | // We are done once we reached index 0. In that case there are no more 330 | // segments left. 331 | if (sj->path_index == 0) { 332 | break; 333 | } 334 | 335 | // There is another path which we have to inspect. So we decrease the path 336 | // index. 337 | --sj->path_index; 338 | 339 | // If this is the first path we will have to consider that this path might 340 | // include a root, otherwise we just treat is as a segment. 341 | if (sj->path_index == 0) { 342 | result = 343 | cwk_path_get_last_segment(sj->paths[sj->path_index], &sj->segment); 344 | } else { 345 | result = cwk_path_get_last_segment_without_root(sj->paths[sj->path_index], 346 | &sj->segment); 347 | } 348 | 349 | } while (!result); 350 | 351 | return result; 352 | } 353 | 354 | static bool 355 | cwk_path_segment_back_will_be_removed(struct cwk_segment_joined *sj) { 356 | enum cwk_segment_type type; 357 | int counter; 358 | 359 | // We are handling back segments here. We must verify how many back segments 360 | // and how many normal segments come before this one to decide whether we keep 361 | // or remove it. 362 | 363 | // The counter determines how many normal segments are our current segment, 364 | // which will popped off before us. If the counter goes above zero it means 365 | // that our segment will be popped as well. 366 | counter = 0; 367 | 368 | // We loop over all previous segments until we either reach the beginning, 369 | // which means our segment will not be dropped or the counter goes above zero. 370 | while (cwk_path_get_previous_segment_joined(sj)) { 371 | 372 | // Now grab the type. The type determines whether we will increase or 373 | // decrease the counter. We don't handle a CWK_CURRENT frame here since it 374 | // has no influence. 375 | type = cwk_path_get_segment_type(&sj->segment); 376 | if (type == CWK_NORMAL) { 377 | // This is a normal segment. The normal segment will increase the counter 378 | // since it neutralizes one back segment. If we go above zero we can 379 | // return immediately. 380 | ++counter; 381 | if (counter > 0) { 382 | return true; 383 | } 384 | } else if (type == CWK_BACK) { 385 | // A CWK_BACK segment will reduce the counter by one. We can not remove a 386 | // back segment as long we are not above zero since we don't have the 387 | // opposite normal segment which we would remove. 388 | --counter; 389 | } 390 | } 391 | 392 | // We never got a count larger than zero, so we will keep this segment alive. 393 | return false; 394 | } 395 | 396 | static bool 397 | cwk_path_segment_normal_will_be_removed(struct cwk_segment_joined *sj) { 398 | enum cwk_segment_type type; 399 | int counter; 400 | 401 | // The counter determines how many segments are above our current segment, 402 | // which will popped off before us. If the counter goes below zero it means 403 | // that our segment will be popped as well. 404 | counter = 0; 405 | 406 | // We loop over all following segments until we either reach the end, which 407 | // means our segment will not be dropped or the counter goes below zero. 408 | while (cwk_path_get_next_segment_joined(sj)) { 409 | 410 | // First, grab the type. The type determines whether we will increase or 411 | // decrease the counter. We don't handle a CWK_CURRENT frame here since it 412 | // has no influence. 413 | type = cwk_path_get_segment_type(&sj->segment); 414 | if (type == CWK_NORMAL) { 415 | // This is a normal segment. The normal segment will increase the counter 416 | // since it will be removed by a "../" before us. 417 | ++counter; 418 | } else if (type == CWK_BACK) { 419 | // A CWK_BACK segment will reduce the counter by one. If we are below zero 420 | // we can return immediately. 421 | --counter; 422 | if (counter < 0) { 423 | return true; 424 | } 425 | } 426 | } 427 | 428 | // We never got a negative count, so we will keep this segment alive. 429 | return false; 430 | } 431 | 432 | static bool 433 | cwk_path_segment_will_be_removed(const struct cwk_segment_joined *sj, 434 | bool absolute) { 435 | enum cwk_segment_type type; 436 | struct cwk_segment_joined sjc; 437 | 438 | // We copy the joined path so we don't need to modify it. 439 | sjc = *sj; 440 | 441 | // First we check whether this is a CWK_CURRENT or CWK_BACK segment, since 442 | // those will always be dropped. 443 | type = cwk_path_get_segment_type(&sj->segment); 444 | if (type == CWK_CURRENT || (type == CWK_BACK && absolute)) { 445 | return true; 446 | } else if (type == CWK_BACK) { 447 | return cwk_path_segment_back_will_be_removed(&sjc); 448 | } else { 449 | return cwk_path_segment_normal_will_be_removed(&sjc); 450 | } 451 | } 452 | 453 | static bool 454 | cwk_path_segment_joined_skip_invisible(struct cwk_segment_joined *sj, 455 | bool absolute) { 456 | while (cwk_path_segment_will_be_removed(sj, absolute)) { 457 | if (!cwk_path_get_next_segment_joined(sj)) { 458 | return false; 459 | } 460 | } 461 | 462 | return true; 463 | } 464 | 465 | static void cwk_path_get_root_windows(const char *path, size_t *length) { 466 | const char *c; 467 | bool is_device_path; 468 | 469 | // We can not determine the root if this is an empty string. So we set the 470 | // root to NULL and the length to zero and cancel the whole thing. 471 | c = path; 472 | *length = 0; 473 | if (!*c) { 474 | return; 475 | } 476 | 477 | // Now we have to verify whether this is a windows network path (UNC), which 478 | // we will consider our root. 479 | if (cwk_path_is_separator(c)) { 480 | ++c; 481 | 482 | // Check whether the path starts with a single backslash, which means this 483 | // is not a network path - just a normal path starting with a backslash. 484 | if (!cwk_path_is_separator(c)) { 485 | // Okay, this is not a network path but we still use the backslash as a 486 | // root. 487 | ++(*length); 488 | return; 489 | } 490 | 491 | // A device path is a path which starts with "\\." or "\\?". A device path 492 | // can be a UNC path as well, in which case it will take up one more 493 | // segment. So, this is a network or device path. Skip the previous 494 | // separator. Now we need to determine whether this is a device path. We 495 | // might advance one character here if the server name starts with a '?' or 496 | // a '.', but that's fine since we will search for a separator afterwards 497 | // anyway. 498 | ++c; 499 | is_device_path = (*c == '?' || *c == '.') && cwk_path_is_separator(++c); 500 | if (is_device_path) { 501 | // That's a device path, and the root must be either "\\.\" or "\\?\" 502 | // which is 4 characters long. (at least that's how Windows 503 | // GetFullPathName behaves.) 504 | *length = 4; 505 | return; 506 | } 507 | 508 | // We will grab anything up to the next stop. The next stop might be a '\0' 509 | // or another separator. That will be the server name. 510 | c = cwk_path_find_next_stop(c); 511 | 512 | // If this is a separator and not the end of a string we wil have to include 513 | // it. However, if this is a '\0' we must not skip it. 514 | while (cwk_path_is_separator(c)) { 515 | ++c; 516 | } 517 | 518 | // We are now skipping the shared folder name, which will end after the 519 | // next stop. 520 | c = cwk_path_find_next_stop(c); 521 | 522 | // Then there might be a separator at the end. We will include that as well, 523 | // it will mark the path as absolute. 524 | if (cwk_path_is_separator(c)) { 525 | ++c; 526 | } 527 | 528 | // Finally, calculate the size of the root. 529 | *length = (size_t)(c - path); 530 | return; 531 | } 532 | 533 | // Move to the next and check whether this is a colon. 534 | if (*++c == ':') { 535 | *length = 2; 536 | 537 | // Now check whether this is a backslash (or slash). If it is not, we could 538 | // assume that the next character is a '\0' if it is a valid path. However, 539 | // we will not assume that - since ':' is not valid in a path it must be a 540 | // mistake by the caller than. We will try to understand it anyway. 541 | if (cwk_path_is_separator(++c)) { 542 | *length = 3; 543 | } 544 | } 545 | } 546 | 547 | static void cwk_path_get_root_unix(const char *path, size_t *length) { 548 | // The slash of the unix path represents the root. There is no root if there 549 | // is no slash. 550 | if (cwk_path_is_separator(path)) { 551 | *length = 1; 552 | } else { 553 | *length = 0; 554 | } 555 | } 556 | 557 | static bool cwk_path_is_root_absolute(const char *path, size_t length) { 558 | // This is definitely not absolute if there is no root. 559 | if (length == 0) { 560 | return false; 561 | } 562 | 563 | // If there is a separator at the end of the root, we can safely consider this 564 | // to be an absolute path. 565 | return cwk_path_is_separator(&path[length - 1]); 566 | } 567 | 568 | static void cwk_path_fix_root(char *buffer, size_t buffer_size, size_t length) { 569 | size_t i; 570 | 571 | // This only affects windows. 572 | if (path_style != CWK_STYLE_WINDOWS) { 573 | return; 574 | } 575 | 576 | // Make sure we are not writing further than we are actually allowed to. 577 | if (length > buffer_size) { 578 | length = buffer_size; 579 | } 580 | 581 | // Replace all forward slashes with backwards slashes. Since this is windows 582 | // we can't have any forward slashes in the root. 583 | for (i = 0; i < length; ++i) { 584 | if (cwk_path_is_separator(&buffer[i])) { 585 | buffer[i] = *separators[CWK_STYLE_WINDOWS]; 586 | } 587 | } 588 | } 589 | 590 | static size_t cwk_path_join_and_normalize_multiple(const char **paths, 591 | char *buffer, 592 | size_t buffer_size) { 593 | size_t pos; 594 | bool absolute, has_segment_output; 595 | struct cwk_segment_joined sj; 596 | 597 | // We initialize the position after the root, which should get us started. 598 | cwk_path_get_root(paths[0], &pos); 599 | 600 | // Determine whether the path is absolute or not. We need that to determine 601 | // later on whether we can remove superfluous "../" or not. 602 | absolute = cwk_path_is_root_absolute(paths[0], pos); 603 | 604 | // First copy the root to the output. After copying, we will normalize the 605 | // root. 606 | cwk_path_output_sized(buffer, buffer_size, 0, paths[0], pos); 607 | cwk_path_fix_root(buffer, buffer_size, pos); 608 | 609 | // So we just grab the first segment. If there is no segment we will always 610 | // output a "/", since we currently only support absolute paths here. 611 | if (!cwk_path_get_first_segment_joined(paths, &sj)) { 612 | goto done; 613 | } 614 | 615 | // Let's assume that we don't have any segment output for now. We will toggle 616 | // this flag once there is some output. 617 | has_segment_output = false; 618 | 619 | do { 620 | // Check whether we have to drop this segment because of resolving a 621 | // relative path or because it is a CWK_CURRENT segment. 622 | if (cwk_path_segment_will_be_removed(&sj, absolute)) { 623 | continue; 624 | } 625 | 626 | // We add a separator if we previously wrote a segment. The last segment 627 | // must not have a trailing separator. This must happen before the segment 628 | // output, since we would override the null terminating character with 629 | // reused buffers if this was done afterwards. 630 | if (has_segment_output) { 631 | pos += cwk_path_output_separator(buffer, buffer_size, pos); 632 | } 633 | 634 | // Remember that we have segment output, so we can handle the trailing slash 635 | // later on. This is necessary since we might have segments but they are all 636 | // removed. 637 | has_segment_output = true; 638 | 639 | // Write out the segment but keep in mind that we need to follow the 640 | // buffer size limitations. That's why we use the path output functions 641 | // here. 642 | pos += cwk_path_output_sized(buffer, buffer_size, pos, sj.segment.begin, 643 | sj.segment.size); 644 | } while (cwk_path_get_next_segment_joined(&sj)); 645 | 646 | // Remove the trailing slash, but only if we have segment output. We don't 647 | // want to remove anything from the root. 648 | if (!has_segment_output && pos == 0) { 649 | // This may happen if the path is absolute and all segments have been 650 | // removed. We can not have an empty output - and empty output means we stay 651 | // in the current directory. So we will output a ".". 652 | assert(absolute == false); 653 | pos += cwk_path_output_current(buffer, buffer_size, pos); 654 | } 655 | 656 | // We must append a '\0' in any case, unless the buffer size is zero. If the 657 | // buffer size is zero, which means we can not. 658 | done: 659 | cwk_path_terminate_output(buffer, buffer_size, pos); 660 | 661 | // And finally let our caller know about the total size of the normalized 662 | // path. 663 | return pos; 664 | } 665 | 666 | size_t cwk_path_get_absolute(const char *base, const char *path, char *buffer, 667 | size_t buffer_size) { 668 | size_t i; 669 | const char *paths[4]; 670 | 671 | // The basename should be an absolute path if the caller is using the API 672 | // correctly. However, he might not and in that case we will append a fake 673 | // root at the beginning. 674 | if (cwk_path_is_absolute(base)) { 675 | i = 0; 676 | } else if (path_style == CWK_STYLE_WINDOWS) { 677 | paths[0] = "\\"; 678 | i = 1; 679 | } else { 680 | paths[0] = "/"; 681 | i = 1; 682 | } 683 | 684 | if (cwk_path_is_absolute(path)) { 685 | // If the submitted path is not relative the base path becomes irrelevant. 686 | // We will only normalize the submitted path instead. 687 | paths[i++] = path; 688 | paths[i] = NULL; 689 | } else { 690 | // Otherwise we append the relative path to the base path and normalize it. 691 | // The result will be a new absolute path. 692 | paths[i++] = base; 693 | paths[i++] = path; 694 | paths[i] = NULL; 695 | } 696 | 697 | // Finally join everything together and normalize it. 698 | return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); 699 | } 700 | 701 | static void cwk_path_skip_segments_until_diverge(struct cwk_segment_joined *bsj, 702 | struct cwk_segment_joined *osj, 703 | bool absolute, 704 | bool *base_available, 705 | bool *other_available) { 706 | // Now looping over all segments until they start to diverge. A path may 707 | // diverge if two segments are not equal or if one path reaches the end. 708 | do { 709 | 710 | // Check whether there is anything available after we skip everything which 711 | // is invisible. We do that for both paths, since we want to let the caller 712 | // know which path has some trailing segments after they diverge. 713 | *base_available = cwk_path_segment_joined_skip_invisible(bsj, absolute); 714 | *other_available = cwk_path_segment_joined_skip_invisible(osj, absolute); 715 | 716 | // We are done if one or both of those paths reached the end. They either 717 | // diverge or both reached the end - but in both cases we can not continue 718 | // here. 719 | if (!*base_available || !*other_available) { 720 | break; 721 | } 722 | 723 | // Compare the content of both segments. We are done if they are not equal, 724 | // since they diverge. 725 | if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin, 726 | bsj->segment.size, osj->segment.size)) { 727 | break; 728 | } 729 | 730 | // We keep going until one of those segments reached the end. The next 731 | // segment might be invisible, but we will check for that in the beginning 732 | // of the loop once again. 733 | *base_available = cwk_path_get_next_segment_joined(bsj); 734 | *other_available = cwk_path_get_next_segment_joined(osj); 735 | } while (*base_available && *other_available); 736 | } 737 | 738 | size_t cwk_path_get_relative(const char *base_directory, const char *path, 739 | char *buffer, size_t buffer_size) { 740 | size_t pos, base_root_length, path_root_length; 741 | bool absolute, base_available, other_available, has_output; 742 | const char *base_paths[2], *other_paths[2]; 743 | struct cwk_segment_joined bsj, osj; 744 | 745 | pos = 0; 746 | 747 | // First we compare the roots of those two paths. If the roots are not equal 748 | // we can't continue, since there is no way to get a relative path from 749 | // different roots. 750 | cwk_path_get_root(base_directory, &base_root_length); 751 | cwk_path_get_root(path, &path_root_length); 752 | if (base_root_length != path_root_length || 753 | !cwk_path_is_string_equal(base_directory, path, base_root_length, 754 | path_root_length)) { 755 | cwk_path_terminate_output(buffer, buffer_size, pos); 756 | return pos; 757 | } 758 | 759 | // Verify whether this is an absolute path. We need to know that since we can 760 | // remove all back-segments if it is. 761 | absolute = cwk_path_is_root_absolute(base_directory, base_root_length); 762 | 763 | // Initialize our joined segments. This will allow us to use the internal 764 | // functions to skip until diverge and invisible. We only have one path in 765 | // them though. 766 | base_paths[0] = base_directory; 767 | base_paths[1] = NULL; 768 | other_paths[0] = path; 769 | other_paths[1] = NULL; 770 | cwk_path_get_first_segment_joined(base_paths, &bsj); 771 | cwk_path_get_first_segment_joined(other_paths, &osj); 772 | 773 | // Okay, now we skip until the segments diverge. We don't have anything to do 774 | // with the segments which are equal. 775 | cwk_path_skip_segments_until_diverge(&bsj, &osj, absolute, &base_available, 776 | &other_available); 777 | 778 | // Assume there is no output until we have got some. We will need this 779 | // information later on to remove trailing slashes or alternatively output a 780 | // current-segment. 781 | has_output = false; 782 | 783 | // So if we still have some segments left in the base path we will now output 784 | // a back segment for all of them. 785 | if (base_available) { 786 | do { 787 | // Skip any invisible segment. We don't care about those and we don't need 788 | // to navigate back because of them. 789 | if (!cwk_path_segment_joined_skip_invisible(&bsj, absolute)) { 790 | break; 791 | } 792 | 793 | // Toggle the flag if we have output. We need to remember that, since we 794 | // want to remove the trailing slash. 795 | has_output = true; 796 | 797 | // Output the back segment and a separator. No need to worry about the 798 | // superfluous segment since it will be removed later on. 799 | pos += cwk_path_output_back(buffer, buffer_size, pos); 800 | pos += cwk_path_output_separator(buffer, buffer_size, pos); 801 | } while (cwk_path_get_next_segment_joined(&bsj)); 802 | } 803 | 804 | // And if we have some segments available of the target path we will output 805 | // all of those. 806 | if (other_available) { 807 | do { 808 | // Again, skip any invisible segments since we don't need to navigate into 809 | // them. 810 | if (!cwk_path_segment_joined_skip_invisible(&osj, absolute)) { 811 | break; 812 | } 813 | 814 | // Toggle the flag if we have output. We need to remember that, since we 815 | // want to remove the trailing slash. 816 | has_output = true; 817 | 818 | // Output the current segment and a separator. No need to worry about the 819 | // superfluous segment since it will be removed later on. 820 | pos += cwk_path_output_sized(buffer, buffer_size, pos, osj.segment.begin, 821 | osj.segment.size); 822 | pos += cwk_path_output_separator(buffer, buffer_size, pos); 823 | } while (cwk_path_get_next_segment_joined(&osj)); 824 | } 825 | 826 | // If we have some output by now we will have to remove the trailing slash. We 827 | // simply do that by moving back one character. The terminate output function 828 | // will then place the '\0' on this position. Otherwise, if there is no 829 | // output, we will have to output a "current directory", since the target path 830 | // points to the base path. 831 | if (has_output) { 832 | --pos; 833 | } else { 834 | pos += cwk_path_output_current(buffer, buffer_size, pos); 835 | } 836 | 837 | // Finally, we can terminate the output - which means we place a '\0' at the 838 | // current position or at the end of the buffer. 839 | cwk_path_terminate_output(buffer, buffer_size, pos); 840 | 841 | return pos; 842 | } 843 | 844 | size_t cwk_path_join(const char *path_a, const char *path_b, char *buffer, 845 | size_t buffer_size) { 846 | const char *paths[3]; 847 | 848 | // This is simple. We will just create an array with the two paths which we 849 | // wish to join. 850 | paths[0] = path_a; 851 | paths[1] = path_b; 852 | paths[2] = NULL; 853 | 854 | // And then call the join and normalize function which will do the hard work 855 | // for us. 856 | return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); 857 | } 858 | 859 | size_t cwk_path_join_multiple(const char **paths, char *buffer, 860 | size_t buffer_size) { 861 | // We can just call the internal join and normalize function for this one, 862 | // since it will handle everything. 863 | return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); 864 | } 865 | 866 | void cwk_path_get_root(const char *path, size_t *length) { 867 | // We use a different implementation here based on the configuration of the 868 | // library. 869 | if (path_style == CWK_STYLE_WINDOWS) { 870 | cwk_path_get_root_windows(path, length); 871 | } else { 872 | cwk_path_get_root_unix(path, length); 873 | } 874 | } 875 | 876 | size_t cwk_path_change_root(const char *path, const char *new_root, 877 | char *buffer, size_t buffer_size) { 878 | const char *tail; 879 | size_t root_length, path_length, tail_length, new_root_length, new_path_size; 880 | 881 | // First we need to determine the actual size of the root which we will 882 | // change. 883 | cwk_path_get_root(path, &root_length); 884 | 885 | // Now we determine the sizes of the new root and the path. We need that to 886 | // determine the size of the part after the root (the tail). 887 | new_root_length = strlen(new_root); 888 | path_length = strlen(path); 889 | 890 | // Okay, now we calculate the position of the tail and the length of it. 891 | tail = path + root_length; 892 | tail_length = path_length - root_length; 893 | 894 | // We first output the tail and then the new root, that's because the source 895 | // path and the buffer may be overlapping. This way the root will not 896 | // overwrite the tail. 897 | cwk_path_output_sized(buffer, buffer_size, new_root_length, tail, 898 | tail_length); 899 | cwk_path_output_sized(buffer, buffer_size, 0, new_root, new_root_length); 900 | 901 | // Finally we calculate the size o the new path and terminate the output with 902 | // a '\0'. 903 | new_path_size = tail_length + new_root_length; 904 | cwk_path_terminate_output(buffer, buffer_size, new_path_size); 905 | 906 | return new_path_size; 907 | } 908 | 909 | bool cwk_path_is_absolute(const char *path) { 910 | size_t length; 911 | 912 | // We grab the root of the path. This root does not include the first 913 | // separator of a path. 914 | cwk_path_get_root(path, &length); 915 | 916 | // Now we can determine whether the root is absolute or not. 917 | return cwk_path_is_root_absolute(path, length); 918 | } 919 | 920 | bool cwk_path_is_relative(const char *path) { 921 | // The path is relative if it is not absolute. 922 | return !cwk_path_is_absolute(path); 923 | } 924 | 925 | void cwk_path_get_basename(const char *path, const char **basename, 926 | size_t *length) { 927 | struct cwk_segment segment; 928 | 929 | // We get the last segment of the path. The last segment will contain the 930 | // basename if there is any. If there are no segments we will set the basename 931 | // to NULL and the length to 0. 932 | if (!cwk_path_get_last_segment(path, &segment)) { 933 | *basename = NULL; 934 | if (length) { 935 | *length = 0; 936 | } 937 | return; 938 | } 939 | 940 | // Now we can just output the segment contents, since that's our basename. 941 | // There might be trailing separators after the basename, but the size does 942 | // not include those. 943 | *basename = segment.begin; 944 | if (length) { 945 | *length = segment.size; 946 | } 947 | } 948 | 949 | size_t cwk_path_change_basename(const char *path, const char *new_basename, 950 | char *buffer, size_t buffer_size) { 951 | struct cwk_segment segment; 952 | size_t pos, root_size, new_basename_size; 953 | 954 | // First we try to get the last segment. We may only have a root without any 955 | // segments, in which case we will create one. 956 | if (!cwk_path_get_last_segment(path, &segment)) { 957 | 958 | // So there is no segment in this path. First we grab the root and output 959 | // that. We are not going to modify the root in any way. 960 | cwk_path_get_root(path, &root_size); 961 | pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size); 962 | 963 | // We have to trim the separators from the beginning of the new basename. 964 | // This is quite easy to do. 965 | while (cwk_path_is_separator(new_basename)) { 966 | ++new_basename; 967 | } 968 | 969 | // Now we measure the length of the new basename, this is a two step 970 | // process. First we find the '\0' character at the end of the string. 971 | new_basename_size = 0; 972 | while (new_basename[new_basename_size]) { 973 | ++new_basename_size; 974 | } 975 | 976 | // And then we trim the separators at the end of the basename until we reach 977 | // the first valid character. 978 | while (new_basename_size > 0 && 979 | cwk_path_is_separator(&new_basename[new_basename_size - 1])) { 980 | --new_basename_size; 981 | } 982 | 983 | // Now we will output the new basename after the root. 984 | pos += cwk_path_output_sized(buffer, buffer_size, pos, new_basename, 985 | new_basename_size); 986 | 987 | // And finally terminate the output and return the total size of the path. 988 | cwk_path_terminate_output(buffer, buffer_size, pos); 989 | return pos; 990 | } 991 | 992 | // If there is a last segment we can just forward this call, which is fairly 993 | // easy. 994 | return cwk_path_change_segment(&segment, new_basename, buffer, buffer_size); 995 | } 996 | 997 | void cwk_path_get_dirname(const char *path, size_t *length) { 998 | struct cwk_segment segment; 999 | 1000 | // We get the last segment of the path. The last segment will contain the 1001 | // basename if there is any. If there are no segments we will set the length 1002 | // to 0. 1003 | if (!cwk_path_get_last_segment(path, &segment)) { 1004 | *length = 0; 1005 | return; 1006 | } 1007 | 1008 | // We can now return the length from the beginning of the string up to the 1009 | // beginning of the last segment. 1010 | *length = (size_t)(segment.begin - path); 1011 | } 1012 | 1013 | bool cwk_path_get_extension(const char *path, const char **extension, 1014 | size_t *length) { 1015 | struct cwk_segment segment; 1016 | const char *c; 1017 | 1018 | // We get the last segment of the path. The last segment will contain the 1019 | // extension if there is any. 1020 | if (!cwk_path_get_last_segment(path, &segment)) { 1021 | return false; 1022 | } 1023 | 1024 | // Now we search for a dot within the segment. If there is a dot, we consider 1025 | // the rest of the segment the extension. We do this from the end towards the 1026 | // beginning, since we want to find the last dot. 1027 | for (c = segment.end; c >= segment.begin; --c) { 1028 | if (*c == '.') { 1029 | // Okay, we found an extension. We can stop looking now. 1030 | *extension = c; 1031 | *length = (size_t)(segment.end - c); 1032 | return true; 1033 | } 1034 | } 1035 | 1036 | // We couldn't find any extension. 1037 | return false; 1038 | } 1039 | 1040 | bool cwk_path_has_extension(const char *path) { 1041 | const char *extension; 1042 | size_t length; 1043 | 1044 | // We just wrap the get_extension call which will then do the work for us. 1045 | return cwk_path_get_extension(path, &extension, &length); 1046 | } 1047 | 1048 | size_t cwk_path_change_extension(const char *path, const char *new_extension, 1049 | char *buffer, size_t buffer_size) { 1050 | struct cwk_segment segment; 1051 | const char *c, *old_extension; 1052 | size_t pos, root_size, trail_size, new_extension_size; 1053 | 1054 | // First we try to get the last segment. We may only have a root without any 1055 | // segments, in which case we will create one. 1056 | if (!cwk_path_get_last_segment(path, &segment)) { 1057 | 1058 | // So there is no segment in this path. First we grab the root and output 1059 | // that. We are not going to modify the root in any way. If there is no 1060 | // root, this will end up with a root size 0, and nothing will be written. 1061 | cwk_path_get_root(path, &root_size); 1062 | pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size); 1063 | 1064 | // Add a dot if the submitted value doesn't have any. 1065 | if (*new_extension != '.') { 1066 | pos += cwk_path_output_dot(buffer, buffer_size, pos); 1067 | } 1068 | 1069 | // And finally terminate the output and return the total size of the path. 1070 | pos += cwk_path_output(buffer, buffer_size, pos, new_extension); 1071 | cwk_path_terminate_output(buffer, buffer_size, pos); 1072 | return pos; 1073 | } 1074 | 1075 | // Now we seek the old extension in the last segment, which we will replace 1076 | // with the new one. If there is no old extension, it will point to the end of 1077 | // the segment. 1078 | old_extension = segment.end; 1079 | for (c = segment.begin; c < segment.end; ++c) { 1080 | if (*c == '.') { 1081 | old_extension = c; 1082 | } 1083 | } 1084 | 1085 | pos = cwk_path_output_sized(buffer, buffer_size, 0, segment.path, 1086 | (size_t)(old_extension - segment.path)); 1087 | 1088 | // If the new extension starts with a dot, we will skip that dot. We always 1089 | // output exactly one dot before the extension. If the extension contains 1090 | // multiple dots, we will output those as part of the extension. 1091 | if (*new_extension == '.') { 1092 | ++new_extension; 1093 | } 1094 | 1095 | // We calculate the size of the new extension, including the dot, in order to 1096 | // output the trail - which is any part of the path coming after the 1097 | // extension. We must output this first, since the buffer may overlap with the 1098 | // submitted path - and it would be overridden by longer extensions. 1099 | new_extension_size = strlen(new_extension) + 1; 1100 | trail_size = cwk_path_output(buffer, buffer_size, pos + new_extension_size, 1101 | segment.end); 1102 | 1103 | // Finally we output the dot and the new extension. The new extension itself 1104 | // doesn't contain the dot anymore, so we must output that first. 1105 | pos += cwk_path_output_dot(buffer, buffer_size, pos); 1106 | pos += cwk_path_output(buffer, buffer_size, pos, new_extension); 1107 | 1108 | // Now we terminate the output with a null-terminating character, but before 1109 | // we do that we must add the size of the trail to the position which we 1110 | // output before. 1111 | pos += trail_size; 1112 | cwk_path_terminate_output(buffer, buffer_size, pos); 1113 | 1114 | // And the position is our output size now. 1115 | return pos; 1116 | } 1117 | 1118 | size_t cwk_path_normalize(const char *path, char *buffer, size_t buffer_size) { 1119 | const char *paths[2]; 1120 | 1121 | // Now we initialize the paths which we will normalize. Since this function 1122 | // only supports submitting a single path, we will only add that one. 1123 | paths[0] = path; 1124 | paths[1] = NULL; 1125 | 1126 | return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); 1127 | } 1128 | 1129 | size_t cwk_path_get_intersection(const char *path_base, 1130 | const char *path_other) { 1131 | bool absolute; 1132 | size_t base_root_length, other_root_length; 1133 | const char *end; 1134 | const char *paths_base[2], *paths_other[2]; 1135 | struct cwk_segment_joined base, other; 1136 | 1137 | // We first compare the two roots. We just return zero if they are not equal. 1138 | // This will also happen to return zero if the paths are mixed relative and 1139 | // absolute. 1140 | cwk_path_get_root(path_base, &base_root_length); 1141 | cwk_path_get_root(path_other, &other_root_length); 1142 | if (!cwk_path_is_string_equal(path_base, path_other, base_root_length, 1143 | other_root_length)) { 1144 | return 0; 1145 | } 1146 | 1147 | // Configure our paths. We just have a single path in here for now. 1148 | paths_base[0] = path_base; 1149 | paths_base[1] = NULL; 1150 | paths_other[0] = path_other; 1151 | paths_other[1] = NULL; 1152 | 1153 | // So we get the first segment of both paths. If one of those paths don't have 1154 | // any segment, we will return 0. 1155 | if (!cwk_path_get_first_segment_joined(paths_base, &base) || 1156 | !cwk_path_get_first_segment_joined(paths_other, &other)) { 1157 | return base_root_length; 1158 | } 1159 | 1160 | // We now determine whether the path is absolute or not. This is required 1161 | // because if will ignore removed segments, and this behaves differently if 1162 | // the path is absolute. However, we only need to check the base path because 1163 | // we are guaranteed that both paths are either relative or absolute. 1164 | absolute = cwk_path_is_root_absolute(path_base, base_root_length); 1165 | 1166 | // We must keep track of the end of the previous segment. Initially, this is 1167 | // set to the beginning of the path. This means that 0 is returned if the 1168 | // first segment is not equal. 1169 | end = path_base + base_root_length; 1170 | 1171 | // Now we loop over both segments until one of them reaches the end or their 1172 | // contents are not equal. 1173 | do { 1174 | // We skip all segments which will be removed in each path, since we want to 1175 | // know about the true path. 1176 | if (!cwk_path_segment_joined_skip_invisible(&base, absolute) || 1177 | !cwk_path_segment_joined_skip_invisible(&other, absolute)) { 1178 | break; 1179 | } 1180 | 1181 | if (!cwk_path_is_string_equal(base.segment.begin, other.segment.begin, 1182 | base.segment.size, other.segment.size)) { 1183 | // So the content of those two segments are not equal. We will return the 1184 | // size up to the beginning. 1185 | return (size_t)(end - path_base); 1186 | } 1187 | 1188 | // Remember the end of the previous segment before we go to the next one. 1189 | end = base.segment.end; 1190 | } while (cwk_path_get_next_segment_joined(&base) && 1191 | cwk_path_get_next_segment_joined(&other)); 1192 | 1193 | // Now we calculate the length up to the last point where our paths pointed to 1194 | // the same place. 1195 | return (size_t)(end - path_base); 1196 | } 1197 | 1198 | bool cwk_path_get_first_segment(const char *path, struct cwk_segment *segment) { 1199 | size_t length; 1200 | const char *segments; 1201 | 1202 | // We skip the root since that's not part of the first segment. The root is 1203 | // treated as a separate entity. 1204 | cwk_path_get_root(path, &length); 1205 | segments = path + length; 1206 | 1207 | // Now, after we skipped the root we can continue and find the actual segment 1208 | // content. 1209 | return cwk_path_get_first_segment_without_root(path, segments, segment); 1210 | } 1211 | 1212 | bool cwk_path_get_last_segment(const char *path, struct cwk_segment *segment) { 1213 | // We first grab the first segment. This might be our last segment as well, 1214 | // but we don't know yet. There is no last segment if there is no first 1215 | // segment, so we return false in that case. 1216 | if (!cwk_path_get_first_segment(path, segment)) { 1217 | return false; 1218 | } 1219 | 1220 | // Now we find our last segment. The segment struct of the caller 1221 | // will contain the last segment, since the function we call here will not 1222 | // change the segment struct when it reaches the end. 1223 | while (cwk_path_get_next_segment(segment)) { 1224 | // We just loop until there is no other segment left. 1225 | } 1226 | 1227 | return true; 1228 | } 1229 | 1230 | bool cwk_path_get_next_segment(struct cwk_segment *segment) { 1231 | const char *c; 1232 | 1233 | // First we jump to the end of the previous segment. The first character must 1234 | // be either a '\0' or a separator. 1235 | c = segment->begin + segment->size; 1236 | if (*c == '\0') { 1237 | return false; 1238 | } 1239 | 1240 | // Now we skip all separator until we reach something else. We are not yet 1241 | // guaranteed to have a segment, since the string could just end afterwards. 1242 | assert(cwk_path_is_separator(c)); 1243 | do { 1244 | ++c; 1245 | } while (cwk_path_is_separator(c)); 1246 | 1247 | // If the string ends here, we can safely assume that there is no other 1248 | // segment after this one. 1249 | if (*c == '\0') { 1250 | return false; 1251 | } 1252 | 1253 | // Now we are safe to assume there is a segment. We store the beginning of 1254 | // this segment in the segment struct of the caller. 1255 | segment->begin = c; 1256 | 1257 | // And now determine the size of this segment, and store it in the struct of 1258 | // the caller as well. 1259 | c = cwk_path_find_next_stop(c); 1260 | segment->end = c; 1261 | segment->size = (size_t)(c - segment->begin); 1262 | 1263 | // Tell the caller that we found a segment. 1264 | return true; 1265 | } 1266 | 1267 | bool cwk_path_get_previous_segment(struct cwk_segment *segment) { 1268 | const char *c; 1269 | 1270 | // The current position might point to the first character of the path, which 1271 | // means there are no previous segments available. 1272 | c = segment->begin; 1273 | if (c <= segment->segments) { 1274 | return false; 1275 | } 1276 | 1277 | // We move towards the beginning of the path until we either reached the 1278 | // beginning or the character is no separator anymore. 1279 | do { 1280 | --c; 1281 | if (c < segment->segments) { 1282 | // So we reached the beginning here and there is no segment. So we return 1283 | // false and don't change the segment structure submitted by the caller. 1284 | return false; 1285 | } 1286 | } while (cwk_path_is_separator(c)); 1287 | 1288 | // We are guaranteed now that there is another segment, since we moved before 1289 | // the previous separator and did not reach the segment path beginning. 1290 | segment->end = c + 1; 1291 | segment->begin = cwk_path_find_previous_stop(segment->segments, c); 1292 | segment->size = (size_t)(segment->end - segment->begin); 1293 | 1294 | return true; 1295 | } 1296 | 1297 | enum cwk_segment_type 1298 | cwk_path_get_segment_type(const struct cwk_segment *segment) { 1299 | // We just make a string comparison with the segment contents and return the 1300 | // appropriate type. 1301 | if (strncmp(segment->begin, ".", segment->size) == 0) { 1302 | return CWK_CURRENT; 1303 | } else if (strncmp(segment->begin, "..", segment->size) == 0) { 1304 | return CWK_BACK; 1305 | } 1306 | 1307 | return CWK_NORMAL; 1308 | } 1309 | 1310 | bool cwk_path_is_separator(const char *str) { 1311 | const char *c; 1312 | 1313 | // We loop over all characters in the read symbols. 1314 | c = separators[path_style]; 1315 | while (*c) { 1316 | if (*c == *str) { 1317 | return true; 1318 | } 1319 | 1320 | ++c; 1321 | } 1322 | 1323 | return false; 1324 | } 1325 | 1326 | size_t cwk_path_change_segment(struct cwk_segment *segment, const char *value, 1327 | char *buffer, size_t buffer_size) { 1328 | size_t pos, value_size, tail_size; 1329 | 1330 | // First we have to output the head, which is the whole string up to the 1331 | // beginning of the segment. This part of the path will just stay the same. 1332 | pos = cwk_path_output_sized(buffer, buffer_size, 0, segment->path, 1333 | (size_t)(segment->begin - segment->path)); 1334 | 1335 | // In order to trip the submitted value, we will skip any separator at the 1336 | // beginning of it and behave as if it was never there. 1337 | while (cwk_path_is_separator(value)) { 1338 | ++value; 1339 | } 1340 | 1341 | // Now we determine the length of the value. In order to do that we first 1342 | // locate the '\0'. 1343 | value_size = 0; 1344 | while (value[value_size]) { 1345 | ++value_size; 1346 | } 1347 | 1348 | // Since we trim separators at the beginning and in the end of the value we 1349 | // have to subtract from the size until there are either no more characters 1350 | // left or the last character is no separator. 1351 | while (value_size > 0 && cwk_path_is_separator(&value[value_size - 1])) { 1352 | --value_size; 1353 | } 1354 | 1355 | // We also have to determine the tail size, which is the part of the string 1356 | // following the current segment. This part will not change. 1357 | tail_size = strlen(segment->end); 1358 | 1359 | // Now we output the tail. We have to do that, because if the buffer and the 1360 | // source are overlapping we would override the tail if the value is 1361 | // increasing in length. 1362 | cwk_path_output_sized(buffer, buffer_size, pos + value_size, segment->end, 1363 | tail_size); 1364 | 1365 | // Finally we can output the value in the middle of the head and the tail, 1366 | // where we have enough space to fit the whole trimmed value. 1367 | pos += cwk_path_output_sized(buffer, buffer_size, pos, value, value_size); 1368 | 1369 | // Now we add the tail size to the current position and terminate the output - 1370 | // basically, ensure that there is a '\0' at the end of the buffer. 1371 | pos += tail_size; 1372 | cwk_path_terminate_output(buffer, buffer_size, pos); 1373 | 1374 | // And now tell the caller how long the whole path would be. 1375 | return pos; 1376 | } 1377 | 1378 | enum cwk_path_style cwk_path_guess_style(const char *path) { 1379 | const char *c; 1380 | size_t root_length; 1381 | struct cwk_segment segment; 1382 | 1383 | // First we determine the root. Only windows roots can be longer than a single 1384 | // slash, so if we can determine that it starts with something like "C:", we 1385 | // know that this is a windows path. 1386 | cwk_path_get_root_windows(path, &root_length); 1387 | if (root_length > 1) { 1388 | return CWK_STYLE_WINDOWS; 1389 | } 1390 | 1391 | // Next we check for slashes. Windows uses backslashes, while unix uses 1392 | // forward slashes. Windows actually supports both, but our best guess is to 1393 | // assume windows with backslashes and unix with forward slashes. 1394 | for (c = path; *c; ++c) { 1395 | if (*c == *separators[CWK_STYLE_UNIX]) { 1396 | return CWK_STYLE_UNIX; 1397 | } else if (*c == *separators[CWK_STYLE_WINDOWS]) { 1398 | return CWK_STYLE_WINDOWS; 1399 | } 1400 | } 1401 | 1402 | // This path does not have any slashes. We grab the last segment (which 1403 | // actually must be the first one), and determine whether the segment starts 1404 | // with a dot. A dot is a hidden folder or file in the UNIX world, in that 1405 | // case we assume the path to have UNIX style. 1406 | if (!cwk_path_get_last_segment(path, &segment)) { 1407 | // We couldn't find any segments, so we default to a UNIX path style since 1408 | // there is no way to make any assumptions. 1409 | return CWK_STYLE_UNIX; 1410 | } 1411 | 1412 | if (*segment.begin == '.') { 1413 | return CWK_STYLE_UNIX; 1414 | } 1415 | 1416 | // And finally we check whether the last segment contains a dot. If it 1417 | // contains a dot, that might be an extension. Windows is more likely to have 1418 | // file names with extensions, so our guess would be windows. 1419 | for (c = segment.begin; *c; ++c) { 1420 | if (*c == '.') { 1421 | return CWK_STYLE_WINDOWS; 1422 | } 1423 | } 1424 | 1425 | // All our checks failed, so we will return a default value which is currently 1426 | // UNIX. 1427 | return CWK_STYLE_UNIX; 1428 | } 1429 | 1430 | void cwk_path_set_style(enum cwk_path_style style) { 1431 | // We can just set the global path style variable and then the behaviour for 1432 | // all functions will change accordingly. 1433 | assert(style == CWK_STYLE_UNIX || style == CWK_STYLE_WINDOWS); 1434 | path_style = style; 1435 | } 1436 | 1437 | enum cwk_path_style cwk_path_get_style(void) { 1438 | // Simply return the path style which we store in a global variable. 1439 | return path_style; 1440 | } 1441 | -------------------------------------------------------------------------------- /ldp_fuse/include/cwalk.h: -------------------------------------------------------------------------------- 1 | // This library originates from: 2 | // https://github.com/likle/cwalk 3 | 4 | #pragma once 5 | 6 | #ifndef CWK_LIBRARY_H 7 | #define CWK_LIBRARY_H 8 | 9 | #include 10 | #include 11 | 12 | #if defined(_WIN32) || defined(__CYGWIN__) 13 | #define CWK_EXPORT __declspec(dllexport) 14 | #define CWK_IMPORT __declspec(dllimport) 15 | #elif __GNUC__ >= 4 16 | #define CWK_EXPORT __attribute__((visibility("default"))) 17 | #define CWK_IMPORT __attribute__((visibility("default"))) 18 | #else 19 | #define CWK_EXPORT 20 | #define CWK_IMPORT 21 | #endif 22 | 23 | #if defined(CWK_SHARED) 24 | #if defined(CWK_EXPORTS) 25 | #define CWK_PUBLIC CWK_EXPORT 26 | #else 27 | #define CWK_PUBLIC CWK_IMPORT 28 | #endif 29 | #else 30 | #define CWK_PUBLIC 31 | #endif 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | /** 38 | * A segment represents a single component of a path. For instance, on linux a 39 | * path might look like this "/var/log/", which consists of two segments "var" 40 | * and "log". 41 | */ 42 | struct cwk_segment { 43 | const char *path; 44 | const char *segments; 45 | const char *begin; 46 | const char *end; 47 | size_t size; 48 | }; 49 | 50 | /** 51 | * The segment type can be used to identify whether a segment is a special 52 | * segment or not. 53 | * 54 | * CWK_NORMAL - normal folder or file segment 55 | * CWK_CURRENT - "./" current folder segment 56 | * CWK_BACK - "../" relative back navigation segment 57 | */ 58 | enum cwk_segment_type { CWK_NORMAL, CWK_CURRENT, CWK_BACK }; 59 | 60 | /** 61 | * @brief Determines the style which is used for the path parsing and 62 | * generation. 63 | */ 64 | enum cwk_path_style { CWK_STYLE_WINDOWS, CWK_STYLE_UNIX }; 65 | 66 | /** 67 | * @brief Generates an absolute path based on a base. 68 | * 69 | * This function generates an absolute path based on a base path and another 70 | * path. It is guaranteed to return an absolute path. If the second submitted 71 | * path is absolute, it will override the base path. The result will be 72 | * written to a buffer, which might be truncated if the buffer is not large 73 | * enough to hold the full path. However, the truncated result will always be 74 | * null-terminated. The returned value is the amount of characters which the 75 | * resulting path would take if it was not truncated (excluding the 76 | * null-terminating character). 77 | * 78 | * @param base The absolute base path on which the relative path will be 79 | * applied. 80 | * @param path The relative path which will be applied on the base path. 81 | * @param buffer The buffer where the result will be written to. 82 | * @param buffer_size The size of the result buffer. 83 | * @return Returns the total amount of characters of the new absolute path. 84 | */ 85 | CWK_PUBLIC size_t cwk_path_get_absolute(const char *base, const char *path, 86 | char *buffer, size_t buffer_size); 87 | 88 | /** 89 | * @brief Generates a relative path based on a base. 90 | * 91 | * This function generates a relative path based on a base path and another 92 | * path. It determines how to get to the submitted path, starting from the 93 | * base directory. The result will be written to a buffer, which might be 94 | * truncated if the buffer is not large enough to hold the full path. However, 95 | * the truncated result will always be null-terminated. The returned value is 96 | * the amount of characters which the resulting path would take if it was not 97 | * truncated (excluding the null-terminating character). 98 | * 99 | * @param base_directory The base path from which the relative path will 100 | * start. 101 | * @param path The target path where the relative path will point to. 102 | * @param buffer The buffer where the result will be written to. 103 | * @param buffer_size The size of the result buffer. 104 | * @return Returns the total amount of characters of the full path. 105 | */ 106 | CWK_PUBLIC size_t cwk_path_get_relative(const char *base_directory, 107 | const char *path, char *buffer, 108 | size_t buffer_size); 109 | 110 | /** 111 | * @brief Joins two paths together. 112 | * 113 | * This function generates a new path by combining the two submitted paths. It 114 | * will remove double separators, and unlike cwk_path_get_absolute it permits 115 | * the use of two relative paths to combine. The result will be written to a 116 | * buffer, which might be truncated if the buffer is not large enough to hold 117 | * the full path. However, the truncated result will always be 118 | * null-terminated. The returned value is the amount of characters which the 119 | * resulting path would take if it was not truncated (excluding the 120 | * null-terminating character). 121 | * 122 | * @param path_a The first path which comes first. 123 | * @param path_b The second path which comes after the first. 124 | * @param buffer The buffer where the result will be written to. 125 | * @param buffer_size The size of the result buffer. 126 | * @return Returns the total amount of characters of the full, combined path. 127 | */ 128 | CWK_PUBLIC size_t cwk_path_join(const char *path_a, const char *path_b, 129 | char *buffer, size_t buffer_size); 130 | 131 | /** 132 | * @brief Joins multiple paths together. 133 | * 134 | * This function generates a new path by joining multiple paths together. It 135 | * will remove double separators, and unlike cwk_path_get_absolute it permits 136 | * the use of multiple relative paths to combine. The last path of the 137 | * submitted string array must be set to NULL. The result will be written to a 138 | * buffer, which might be truncated if the buffer is not large enough to hold 139 | * the full path. However, the truncated result will always be 140 | * null-terminated. The returned value is the amount of characters which the 141 | * resulting path would take if it was not truncated (excluding the 142 | * null-terminating character). 143 | * 144 | * @param paths An array of paths which will be joined. 145 | * @param buffer The buffer where the result will be written to. 146 | * @param buffer_size The size of the result buffer. 147 | * @return Returns the total amount of characters of the full, combined path. 148 | */ 149 | CWK_PUBLIC size_t cwk_path_join_multiple(const char **paths, char *buffer, 150 | size_t buffer_size); 151 | 152 | /** 153 | * @brief Determines the root of a path. 154 | * 155 | * This function determines the root of a path by finding its length. The 156 | * root always starts at the submitted path. If the path has no root, the 157 | * length will be set to zero. 158 | * 159 | * @param path The path which will be inspected. 160 | * @param length The output of the root length. 161 | */ 162 | CWK_PUBLIC void cwk_path_get_root(const char *path, size_t *length); 163 | 164 | /** 165 | * @brief Changes the root of a path. 166 | * 167 | * This function changes the root of a path. It does not normalize the result. 168 | * The result will be written to a buffer, which might be truncated if the 169 | * buffer is not large enough to hold the full path. However, the truncated 170 | * result will always be null-terminated. The returned value is the amount of 171 | * characters which the resulting path would take if it was not truncated 172 | * (excluding the null-terminating character). 173 | * 174 | * @param path The original path which will get a new root. 175 | * @param new_root The new root which will be placed in the path. 176 | * @param buffer The output buffer where the result is written to. 177 | * @param buffer_size The size of the output buffer where the result is 178 | * written to. 179 | * @return Returns the total amount of characters of the new path. 180 | */ 181 | CWK_PUBLIC size_t cwk_path_change_root(const char *path, const char *new_root, 182 | char *buffer, size_t buffer_size); 183 | 184 | /** 185 | * @brief Determine whether the path is absolute or not. 186 | * 187 | * This function checks whether the path is an absolute path or not. A path is 188 | * considered to be absolute if the root ends with a separator. 189 | * 190 | * @param path The path which will be checked. 191 | * @return Returns true if the path is absolute or false otherwise. 192 | */ 193 | CWK_PUBLIC bool cwk_path_is_absolute(const char *path); 194 | 195 | /** 196 | * @brief Determine whether the path is relative or not. 197 | * 198 | * This function checks whether the path is a relative path or not. A path is 199 | * considered to be relative if the root does not end with a separator. 200 | * 201 | * @param path The path which will be checked. 202 | * @return Returns true if the path is relative or false otherwise. 203 | */ 204 | CWK_PUBLIC bool cwk_path_is_relative(const char *path); 205 | 206 | /** 207 | * @brief Gets the basename of a file path. 208 | * 209 | * This function gets the basename of a file path. A pointer to the beginning 210 | * of the basename will be returned through the basename parameter. This 211 | * pointer will be positioned on the first letter after the separator. The 212 | * length of the file path will be returned through the length parameter. The 213 | * length will be set to zero and the basename to NULL if there is no basename 214 | * available. 215 | * 216 | * @param path The path which will be inspected. 217 | * @param basename The output of the basename pointer. 218 | * @param length The output of the length of the basename. This may be 219 | * null if not required. 220 | */ 221 | CWK_PUBLIC void cwk_path_get_basename(const char *path, const char **basename, 222 | size_t *length); 223 | 224 | /** 225 | * @brief Changes the basename of a file path. 226 | * 227 | * This function changes the basename of a file path. This function will not 228 | * write out more than the specified buffer can contain. However, the 229 | * generated string is always null-terminated - even if not the whole path is 230 | * written out. The function returns the total number of characters the 231 | * complete buffer would have, even if it was not written out completely. The 232 | * path may be the same memory address as the buffer. 233 | * 234 | * @param path The original path which will be used for the modified path. 235 | * @param new_basename The new basename which will replace the old one. 236 | * @param buffer The buffer where the changed path will be written to. 237 | * @param buffer_size The size of the result buffer where the changed path is 238 | * written to. 239 | * @return Returns the size which the complete new path would have if it was 240 | * not truncated. 241 | */ 242 | CWK_PUBLIC size_t cwk_path_change_basename(const char *path, 243 | const char *new_basename, 244 | char *buffer, size_t buffer_size); 245 | 246 | /** 247 | * @brief Gets the dirname of a file path. 248 | * 249 | * This function determines the dirname of a file path and returns the length 250 | * up to which character is considered to be part of it. If no dirname is 251 | * found, the length will be set to zero. The beginning of the dirname is 252 | * always equal to the submitted path pointer. 253 | * 254 | * @param path The path which will be inspected. 255 | * @param length The length of the dirname. 256 | */ 257 | CWK_PUBLIC void cwk_path_get_dirname(const char *path, size_t *length); 258 | 259 | /** 260 | * @brief Gets the extension of a file path. 261 | * 262 | * This function extracts the extension portion of a file path. A pointer to 263 | * the beginning of the extension will be returned through the extension 264 | * parameter if an extension is found and true is returned. This pointer will 265 | * be positioned on the dot. The length of the extension name will be returned 266 | * through the length parameter. If no extension is found both parameters 267 | * won't be touched and false will be returned. 268 | * 269 | * @param path The path which will be inspected. 270 | * @param extension The output of the extension pointer. 271 | * @param length The output of the length of the extension. 272 | * @return Returns true if an extension is found or false otherwise. 273 | */ 274 | CWK_PUBLIC bool cwk_path_get_extension(const char *path, const char **extension, 275 | size_t *length); 276 | 277 | /** 278 | * @brief Determines whether the file path has an extension. 279 | * 280 | * This function determines whether the submitted file path has an extension. 281 | * This will evaluate to true if the last segment of the path contains a dot. 282 | * 283 | * @param path The path which will be inspected. 284 | * @return Returns true if the path has an extension or false otherwise. 285 | */ 286 | CWK_PUBLIC bool cwk_path_has_extension(const char *path); 287 | 288 | /** 289 | * @brief Changes the extension of a file path. 290 | * 291 | * This function changes the extension of a file name. The function will 292 | * append an extension if the basename does not have an extension, or use the 293 | * extension as a basename if the path does not have a basename. This function 294 | * will not write out more than the specified buffer can contain. However, the 295 | * generated string is always null-terminated - even if not the whole path is 296 | * written out. The function returns the total number of characters the 297 | * complete buffer would have, even if it was not written out completely. The 298 | * path may be the same memory address as the buffer. 299 | * 300 | * @param path The path which will be used to make the change. 301 | * @param new_extension The extension which will be placed within the new 302 | * path. 303 | * @param buffer The output buffer where the result will be written to. 304 | * @param buffer_size The size of the output buffer where the result will be 305 | * written to. 306 | * @return Returns the total size which the output would have if it was not 307 | * truncated. 308 | */ 309 | CWK_PUBLIC size_t cwk_path_change_extension(const char *path, 310 | const char *new_extension, 311 | char *buffer, size_t buffer_size); 312 | 313 | /** 314 | * @brief Creates a normalized version of the path. 315 | * 316 | * This function creates a normalized version of the path within the specified 317 | * buffer. This function will not write out more than the specified buffer can 318 | * contain. However, the generated string is always null-terminated - even if 319 | * not the whole path is written out. The function returns the total number of 320 | * characters the complete buffer would have, even if it was not written out 321 | * completely. The path may be the same memory address as the buffer. 322 | * 323 | * The following will be true for the normalized path: 324 | * 1) "../" will be resolved. 325 | * 2) "./" will be removed. 326 | * 3) double separators will be fixed with a single separator. 327 | * 4) separator suffixes will be removed. 328 | * 329 | * @param path The path which will be normalized. 330 | * @param buffer The buffer where the new path is written to. 331 | * @param buffer_size The size of the buffer. 332 | * @return The size which the complete normalized path has if it was not 333 | * truncated. 334 | */ 335 | CWK_PUBLIC size_t cwk_path_normalize(const char *path, char *buffer, 336 | size_t buffer_size); 337 | 338 | /** 339 | * @brief Finds common portions in two paths. 340 | * 341 | * This function finds common portions in two paths and returns the number 342 | * characters from the beginning of the base path which are equal to the other 343 | * path. 344 | * 345 | * @param path_base The base path which will be compared with the other path. 346 | * @param path_other The other path which will compared with the base path. 347 | * @return Returns the number of characters which are common in the base path. 348 | */ 349 | CWK_PUBLIC size_t cwk_path_get_intersection(const char *path_base, 350 | const char *path_other); 351 | 352 | /** 353 | * @brief Gets the first segment of a path. 354 | * 355 | * This function finds the first segment of a path. The position of the 356 | * segment is set to the first character after the separator, and the length 357 | * counts all characters until the next separator (excluding the separator). 358 | * 359 | * @param path The path which will be inspected. 360 | * @param segment The segment which will be extracted. 361 | * @return Returns true if there is a segment or false if there is none. 362 | */ 363 | CWK_PUBLIC bool cwk_path_get_first_segment(const char *path, 364 | struct cwk_segment *segment); 365 | 366 | /** 367 | * @brief Gets the last segment of the path. 368 | * 369 | * This function gets the last segment of a path. This function may return 370 | * false if the path doesn't contain any segments, in which case the submitted 371 | * segment parameter is not modified. The position of the segment is set to 372 | * the first character after the separator, and the length counts all 373 | * characters until the end of the path (excluding the separator). 374 | * 375 | * @param path The path which will be inspected. 376 | * @param segment The segment which will be extracted. 377 | * @return Returns true if there is a segment or false if there is none. 378 | */ 379 | CWK_PUBLIC bool cwk_path_get_last_segment(const char *path, 380 | struct cwk_segment *segment); 381 | 382 | /** 383 | * @brief Advances to the next segment. 384 | * 385 | * This function advances the current segment to the next segment. If there 386 | * are no more segments left, the submitted segment structure will stay 387 | * unchanged and false is returned. 388 | * 389 | * @param segment The current segment which will be advanced to the next one. 390 | * @return Returns true if another segment was found or false otherwise. 391 | */ 392 | CWK_PUBLIC bool cwk_path_get_next_segment(struct cwk_segment *segment); 393 | 394 | /** 395 | * @brief Moves to the previous segment. 396 | * 397 | * This function moves the current segment to the previous segment. If the 398 | * current segment is the first one, the submitted segment structure will stay 399 | * unchanged and false is returned. 400 | * 401 | * @param segment The current segment which will be moved to the previous one. 402 | * @return Returns true if there is a segment before this one or false 403 | * otherwise. 404 | */ 405 | CWK_PUBLIC bool cwk_path_get_previous_segment(struct cwk_segment *segment); 406 | 407 | /** 408 | * @brief Gets the type of the submitted path segment. 409 | * 410 | * This function inspects the contents of the segment and determines the type 411 | * of it. Currently, there are three types CWK_NORMAL, CWK_CURRENT and 412 | * CWK_BACK. A CWK_NORMAL segment is a normal folder or file entry. A 413 | * CWK_CURRENT is a "./" and a CWK_BACK a "../" segment. 414 | * 415 | * @param segment The segment which will be inspected. 416 | * @return Returns the type of the segment. 417 | */ 418 | CWK_PUBLIC enum cwk_segment_type 419 | cwk_path_get_segment_type(const struct cwk_segment *segment); 420 | 421 | /** 422 | * @brief Changes the content of a segment. 423 | * 424 | * This function overrides the content of a segment to the submitted value and 425 | * outputs the whole new path to the submitted buffer. The result might 426 | * require less or more space than before if the new value length differs from 427 | * the original length. The output is truncated if the new path is larger than 428 | * the submitted buffer size, but it is always null-terminated. The source of 429 | * the segment and the submitted buffer may be the same. 430 | * 431 | * @param segment The segment which will be modifier. 432 | * @param value The new content of the segment. 433 | * @param buffer The buffer where the modified path will be written to. 434 | * @param buffer_size The size of the output buffer. 435 | * @return Returns the total size which would have been written if the output 436 | * was not truncated. 437 | */ 438 | CWK_PUBLIC size_t cwk_path_change_segment(struct cwk_segment *segment, 439 | const char *value, char *buffer, 440 | size_t buffer_size); 441 | 442 | /** 443 | * @brief Checks whether the submitted pointer points to a separator. 444 | * 445 | * This function simply checks whether the submitted pointer points to a 446 | * separator, which has to be null-terminated (but not necessarily after the 447 | * separator). The function will return true if it is a separator, or false 448 | * otherwise. 449 | * 450 | * @param symbol A pointer to a string. 451 | * @return Returns true if it is a separator, or false otherwise. 452 | */ 453 | CWK_PUBLIC bool cwk_path_is_separator(const char *str); 454 | 455 | /** 456 | * @brief Guesses the path style. 457 | * 458 | * This function guesses the path style based on a submitted path-string. The 459 | * guessing will look at the root and the type of slashes contained in the 460 | * path and return the style which is more likely used in the path. 461 | * 462 | * @param path The path which will be inspected. 463 | * @return Returns the style which is most likely used for the path. 464 | */ 465 | CWK_PUBLIC enum cwk_path_style cwk_path_guess_style(const char *path); 466 | 467 | /** 468 | * @brief Configures which path style is used. 469 | * 470 | * This function configures which path style is used. The following styles are 471 | * currently supported. 472 | * 473 | * CWK_STYLE_WINDOWS: Use backslashes as a separator and volume for the root. 474 | * CWK_STYLE_UNIX: Use slashes as a separator and a slash for the root. 475 | * 476 | * @param style The style which will be used from now on. 477 | */ 478 | CWK_PUBLIC void cwk_path_set_style(enum cwk_path_style style); 479 | 480 | /** 481 | * @brief Gets the path style configuration. 482 | * 483 | * This function gets the style configuration which is currently used for the 484 | * paths. This configuration determines how paths are parsed and generated. 485 | * 486 | * @return Returns the current path style configuration. 487 | */ 488 | CWK_PUBLIC enum cwk_path_style cwk_path_get_style(void); 489 | 490 | #ifdef __cplusplus 491 | } // extern "C" 492 | #endif 493 | 494 | #endif 495 | -------------------------------------------------------------------------------- /ldp_fuse/include/ldpfuse.h: -------------------------------------------------------------------------------- 1 | #ifndef LDP_FUSE_H 2 | #define LDP_FUSE_H 3 | #define _GNU_SOURCE 4 | #define __USE_GNU 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /* 18 | fcntl.h contains constant definitions that we need, but also `open` and 19 | `openat` This causes conflicts as symbols with those names are also 20 | necessarily defined here. We therefore overwrite them using #define. 21 | */ 22 | #define openat __renamed_openat 23 | #define open __renamed_open 24 | #include 25 | #undef openat 26 | #undef open 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | // Include `cwalk` directly as this lib is header-only 35 | #include "./cwalk.c" 36 | 37 | #ifdef LDP_FUSE_DEBUG 38 | #define LDP_FUSE_DEBUG_PRINT(...) \ 39 | do { \ 40 | fprintf(stderr, __VA_ARGS__); \ 41 | } while (false) 42 | #else 43 | #define LDP_FUSE_DEBUG_PRINT(...) \ 44 | do { \ 45 | } while (false) 46 | #endif 47 | 48 | #ifdef LDP_FUSE_THREAD_SAFE 49 | #include 50 | #endif 51 | 52 | // `readdir` function types 53 | #define READDIR_ARGS DIR* dirp 54 | typedef struct dirent* (*orig_readdir_t)(READDIR_ARGS); 55 | typedef struct dirent* (*ldp_fuse_readdir_t)(READDIR_ARGS, 56 | orig_readdir_t readdir_fn); 57 | 58 | // `read` function types 59 | #define READ_ARGS int fd, void *buf, size_t count 60 | typedef ssize_t (*orig_read_t)(READ_ARGS); 61 | 62 | // `pread` function types. 63 | #define PREAD_ARGS int fd, void *buf, size_t count, off_t offset 64 | typedef ssize_t (*orig_pread_t)(PREAD_ARGS); 65 | typedef ssize_t (*ldp_fuse_pread_t)(PREAD_ARGS, orig_pread_t read_fn); 66 | 67 | // `open` function types 68 | #define OPEN_ARGS const char *pathname, ... 69 | typedef int (*orig_open_t)(OPEN_ARGS); 70 | 71 | // `openat` function types 72 | #define OPENAT_ARGS int dirfd, const char *pathname, int flags, mode_t mode 73 | typedef int (*orig_openat_t)(OPENAT_ARGS); 74 | typedef int (*ldp_fuse_openat_t)(OPENAT_ARGS, orig_openat_t open_fn); 75 | 76 | // `fstatat` function typs 77 | #define FSTATAT_ARGS \ 78 | int dirfd, const char *restrict pathname, struct stat *restrict statbuf, \ 79 | int flags 80 | typedef int (*orig_fxstatat_t)(int ver, FSTATAT_ARGS); 81 | typedef int (*orig_fstatat_t)(FSTATAT_ARGS); 82 | typedef int (*ldp_fuse_fstatat_t)(FSTATAT_ARGS, orig_fstatat_t stat_fn); 83 | 84 | // `getxattr` function types 85 | #define GETXATTR_ARGS \ 86 | const char *path, const char *name, void *value, size_t size 87 | typedef ssize_t (*orig_getxattr_t)(GETXATTR_ARGS); 88 | typedef ssize_t (*ldp_fuse_getxattr_t)(GETXATTR_ARGS, 89 | orig_getxattr_t getxattr_fn); 90 | 91 | // `lgetxattr` function types 92 | #define LGETXATTR_ARGS \ 93 | const char *path, const char *name, void *value, size_t size 94 | typedef ssize_t (*orig_lgetxattr_t)(LGETXATTR_ARGS); 95 | 96 | // `fgetxattr` function types 97 | #define FGETXATTR_ARGS int fd, const char *name, void *value, size_t size 98 | typedef ssize_t (*orig_fgetxattr_t)(FGETXATTR_ARGS); 99 | 100 | // `write` function types 101 | #define WRITE_ARGS int fd, const void *buf, size_t count 102 | typedef ssize_t (*orig_write_t)(WRITE_ARGS); 103 | 104 | // `creat` function types 105 | #define CREAT_ARGS const char *pathname, mode_t mode 106 | typedef int (*orig_creat_t)(CREAT_ARGS); 107 | 108 | // `pwrite` function types 109 | #define PWRITE_ARGS int fd, const void *buf, size_t count, off_t offset 110 | typedef ssize_t (*orig_pwrite_t)(PWRITE_ARGS); 111 | typedef ssize_t (*ldp_fuse_pwrite_t)(PWRITE_ARGS, orig_pwrite_t write_fn); 112 | 113 | // `readlink` function types 114 | #define READLINK_ARGS const char *pathname, char *buf, size_t bufsiz 115 | typedef ssize_t (*orig_readlink_t)(READLINK_ARGS); 116 | typedef ssize_t (*ldp_fuse_readlink_t)(READLINK_ARGS, 117 | orig_readlink_t readlink_fn); 118 | 119 | // `mknod` function types 120 | #define MKNOD_ARGS const char *pathname, mode_t mode, dev_t dev 121 | typedef int (*orig_mknod_t)(MKNOD_ARGS); 122 | typedef int (*ldp_fuse_mknod_t)(MKNOD_ARGS, orig_mknod_t mknod_fn); 123 | 124 | // `mknodat` function types 125 | #define MKNODAT_ARGS int dirfd, const char *pathname, mode_t mode, dev_t dev 126 | typedef int (*orig_mknodat_t)(MKNODAT_ARGS); 127 | typedef int (*ldp_fuse_mknodat_t)(MKNODAT_ARGS, orig_mknodat_t mknodat_fn); 128 | 129 | // `mkdir` function types 130 | #define MKDIR_ARGS const char *pathname, mode_t mode 131 | typedef int (*orig_mkdir_t)(MKDIR_ARGS); 132 | typedef int (*ldp_fuse_mkdir_t)(MKDIR_ARGS, orig_mkdir_t mkdir_fn); 133 | 134 | // `unlink` function types 135 | #define UNLINK_ARGS const char* pathname 136 | typedef int (*orig_unlink_t)(UNLINK_ARGS); 137 | typedef int (*ldp_fuse_unlink_t)(UNLINK_ARGS, orig_unlink_t unlink_fn); 138 | 139 | // `rmdir` function types 140 | #define RMDIR_ARGS const char* pathname 141 | typedef int (*orig_rmdir_t)(RMDIR_ARGS); 142 | typedef int (*ldp_fuse_rmdir_t)(RMDIR_ARGS, orig_rmdir_t rmdir_fn); 143 | 144 | // `symlink` function types 145 | #define SYMLINK_ARGS const char *target, const char *linkpath 146 | typedef int (*orig_symlink_t)(SYMLINK_ARGS); 147 | typedef int (*ldp_fuse_symlink_t)(SYMLINK_ARGS, orig_symlink_t symlink_fn); 148 | 149 | // `rename` function types 150 | #define RENAME_ARGS const char *oldpath, const char *newpath 151 | typedef int (*orig_rename_t)(RENAME_ARGS); 152 | typedef int (*ldp_fuse_rename_t)(RENAME_ARGS, orig_rename_t rename_fn); 153 | 154 | // `link` function types 155 | #define LINK_ARGS const char *oldpath, const char *newpath 156 | typedef int (*orig_link_t)(LINK_ARGS); 157 | typedef int (*ldp_fuse_link_t)(LINK_ARGS, orig_link_t link_fn); 158 | 159 | // `chmod` function types 160 | #define CHMOD_ARGS const char *path, mode_t mode 161 | typedef int (*orig_chmod_t)(CHMOD_ARGS); 162 | typedef int (*ldp_fuse_chmod_t)(CHMOD_ARGS, orig_chmod_t chmod_fn); 163 | 164 | // `chown` function types 165 | #define CHOWN_ARGS const char *path, uid_t owner, gid_t group 166 | typedef int (*orig_chown_t)(CHOWN_ARGS); 167 | typedef int (*ldp_fuse_chown_t)(CHOWN_ARGS, orig_chown_t chown_fn); 168 | 169 | // `truncate` function types 170 | #define TRUNCATE_ARGS const char *path, off_t length 171 | typedef int (*orig_truncate_t)(TRUNCATE_ARGS); 172 | typedef int (*ldp_fuse_truncate_t)(TRUNCATE_ARGS, orig_truncate_t truncate_fn); 173 | 174 | // `close` function types 175 | #define CLOSE_ARGS int fd 176 | typedef int (*orig_close_t)(CLOSE_ARGS); 177 | typedef int (*ldp_fuse_close_t)(CLOSE_ARGS, orig_close_t close_fn); 178 | 179 | // `opendir` function types 180 | #define OPENDIR_ARGS const char* pathname 181 | typedef DIR* (*orig_opendir_t)(OPENDIR_ARGS); 182 | typedef DIR* (*ldp_fuse_opendir_t)(OPENDIR_ARGS, orig_opendir_t opendir_fn); 183 | 184 | // `faccessat` function types 185 | #define FACCESSAT_ARGS const char *pathname, int mode 186 | typedef int (*orig_faccessat_t)(FACCESSAT_ARGS); 187 | typedef int (*ldp_fuse_faccessat_t)(FACCESSAT_ARGS, orig_faccessat_t access_fn); 188 | 189 | // `access` function types 190 | #define ACCESS_ARGS const char *pathname, int mode 191 | typedef int (*orig_access_t)(ACCESS_ARGS); 192 | 193 | // `euidaccess` function types 194 | #define EUIDACCESS_ARGS const char *pathname, int mode 195 | typedef int (*orig_euidaccess_t)(EUIDACCESS_ARGS); 196 | 197 | // `__xstat` function types 198 | #define XSTAT_ARGS int ver, const char *path, struct stat *buf 199 | typedef int (*orig_xstat_t)(XSTAT_ARGS); 200 | 201 | // `__lxstat` function types 202 | #define LXSTAT_ARGS int ver, const char *path, struct stat *buf 203 | typedef int (*orig_lxstat_t)(LXSTAT_ARGS); 204 | 205 | // `__fxstat` function types 206 | #define FXSTAT_ARGS int ver, int fd, struct stat *buf 207 | typedef int (*orig_fxstat_t)(FXSTAT_ARGS); 208 | 209 | struct ldp_fuse_funcs { 210 | ldp_fuse_fstatat_t stat; 211 | ldp_fuse_readdir_t readdir; 212 | ldp_fuse_openat_t open; 213 | ldp_fuse_pread_t read; 214 | ldp_fuse_pwrite_t write; 215 | ldp_fuse_readlink_t readlink; 216 | ldp_fuse_mknodat_t mknod; 217 | ldp_fuse_mkdir_t mkdir; 218 | ldp_fuse_unlink_t unlink; 219 | ldp_fuse_rmdir_t rmdir; 220 | ldp_fuse_symlink_t symlink; 221 | ldp_fuse_rename_t rename; 222 | ldp_fuse_link_t link; 223 | ldp_fuse_chmod_t chmod; 224 | ldp_fuse_chown_t chown; 225 | ldp_fuse_truncate_t truncate; 226 | ldp_fuse_close_t close; 227 | ldp_fuse_opendir_t opendir; 228 | ldp_fuse_faccessat_t access; 229 | ldp_fuse_getxattr_t getxattr; 230 | } funcs = { 231 | .stat = NULL, 232 | .readdir = NULL, 233 | .open = NULL, 234 | .read = NULL, 235 | .write = NULL, 236 | .readlink = NULL, 237 | .mknod = NULL, 238 | .mkdir = NULL, 239 | .unlink = NULL, 240 | .rmdir = NULL, 241 | .symlink = NULL, 242 | .rename = NULL, 243 | .link = NULL, 244 | .chmod = NULL, 245 | .chown = NULL, 246 | .truncate = NULL, 247 | .close = NULL, 248 | .opendir = NULL, 249 | .access = NULL, 250 | .getxattr = NULL, 251 | }; 252 | 253 | orig_read_t orig_read; 254 | orig_pread_t orig_pread; 255 | 256 | orig_fxstat_t orig_fxstat; 257 | orig_lxstat_t orig_lxstat; 258 | orig_xstat_t orig_xstat; 259 | orig_fxstatat_t orig_fxstatat; 260 | 261 | orig_getxattr_t orig_getxattr; 262 | orig_lgetxattr_t orig_lgetxattr; 263 | orig_fgetxattr_t orig_fgetxattr; 264 | 265 | orig_open_t orig_open; 266 | orig_openat_t orig_openat; 267 | orig_pwrite_t orig_pwrite; 268 | orig_creat_t orig_creat; 269 | orig_write_t orig_write; 270 | orig_readlink_t orig_readlink; 271 | orig_mknod_t orig_mknod; 272 | orig_mknodat_t orig_mknodat; 273 | orig_mkdir_t orig_mkdir; 274 | orig_unlink_t orig_unlink; 275 | orig_rmdir_t orig_rmdir; 276 | orig_symlink_t orig_symlink; 277 | orig_rename_t orig_rename; 278 | orig_link_t orig_link; 279 | orig_chmod_t orig_chmod; 280 | orig_chown_t orig_chown; 281 | orig_truncate_t orig_truncate; 282 | orig_close_t orig_close; 283 | orig_opendir_t orig_opendir; 284 | orig_xstat_t orig_xstat; 285 | orig_lxstat_t orig_lxstat; 286 | orig_fxstat_t orig_fxstat; 287 | orig_access_t orig_access; 288 | orig_faccessat_t orig_faccessat; 289 | orig_euidaccess_t orig_euidaccess; 290 | 291 | #define LDP_FUSE_PATH "LDP_FUSE_PATH" 292 | #define LDP_FUSE_DELIM ":" 293 | 294 | // PID_MAX_LIMIT is ~4 million 295 | #define MAX_PID_DIGITS 7 296 | 297 | // Size of the open file descriptor table 298 | #ifndef LDP_FUSE_OFT_SIZE 299 | #define LDP_FUSE_OFT_SIZE 200 300 | #endif 301 | 302 | typedef enum fd_partof_fs { 303 | UNKNOWN = 0, 304 | NOT_IN_FS, 305 | IN_FS, 306 | } fd_partof_fs; 307 | 308 | // LDP_FUSE manually keeps track of open file descriptors and their offsets. 309 | // Also caches whether this fd is part of the LDP_FUSE file system. Conditional 310 | // compilation flag `LDP_FUSE_THREAD_SAFE` adds a pthread readers-writers lock. 311 | typedef struct ldp_fuse_open_fd { 312 | off_t file_position; 313 | char* path; 314 | fd_partof_fs in_fs; 315 | #ifdef LDP_FUSE_THREAD_SAFE 316 | pthread_rwlock_t rw_lock; 317 | #endif 318 | } ldp_fuse_open_fd; 319 | 320 | // Initialized by `ldp_fuse_init` 321 | typedef struct ldp_fuse_open_file_table { 322 | ldp_fuse_open_fd* table; 323 | } ldp_fuse_open_file_table; 324 | 325 | ldp_fuse_open_file_table _oft; 326 | 327 | // LDP_FUSE_PATH environment variable. Initialized in `ldp_fuse_init`. 328 | char* ldp_fuse_path; 329 | 330 | // Open a file descriptor in the `open_file_table`. 331 | // Every opened file is tracked, even if not in the LDP_FUSE filesystem. 332 | static void open_fd(int fd, const char* path) { 333 | unsigned int idx = fd; 334 | ldp_fuse_open_file_table* oft = &_oft; 335 | if (idx >= LDP_FUSE_OFT_SIZE) { 336 | fprintf(stderr, 337 | "ERROR: File descriptor %d exceeds maximum size of LDP_FUSE's open " 338 | "file descriptor table (max: %d", 339 | fd, LDP_FUSE_OFT_SIZE); 340 | exit(-1); 341 | } 342 | oft->table[idx].file_position = 0; 343 | oft->table[idx].in_fs = UNKNOWN; 344 | 345 | if (oft->table[idx].path != NULL) 346 | free(oft->table[idx].path); 347 | oft->table[idx].path = malloc(strlen(path) + 1); 348 | strcpy(oft->table[idx].path, path); 349 | } 350 | 351 | static inline const struct ldp_fuse_open_fd* get_open_fd_read(int fd) { 352 | #ifdef LDP_FUSE_THREAD_SAFE 353 | ldp_fuse_open_fd* entry = &_oft.table[fd]; 354 | int error; 355 | if ((error = pthread_rwlock_rdlock(&entry->rw_lock) != 0)) 356 | fprintf(stderr, 357 | "LDP_FUSE: Error applying read lock on fd %d. Error value: %d\n", 358 | fd, error); 359 | #endif 360 | return &_oft.table[fd]; 361 | } 362 | 363 | static inline struct ldp_fuse_open_fd* get_open_fd_write(int fd) { 364 | #ifdef LDP_FUSE_THREAD_SAFE 365 | ldp_fuse_open_fd* entry = &_oft.table[fd]; 366 | int error; 367 | if ((error = pthread_rwlock_wrlock(&entry->rw_lock) != 0)) 368 | fprintf(stderr, 369 | "LDP_FUSE: Error applying write lock on fd %d. Error value: %d\n", 370 | fd, error); 371 | #endif 372 | return &_oft.table[fd]; 373 | } 374 | 375 | static inline void unlock_open_fd(int fd) { 376 | #ifdef LDP_FUSE_THREAD_SAFE 377 | int error; 378 | ldp_fuse_open_fd* entry = &_oft.table[fd]; 379 | if ((error = pthread_rwlock_unlock(&entry->rw_lock) != 0)) 380 | fprintf(stderr, 381 | "LDP_FUSE: Error releasing lock for fd %d. Error value: %d\n", fd, 382 | error); 383 | #endif 384 | } 385 | 386 | static inline off_t get_file_position(int fd) { 387 | off_t pos = get_open_fd_read(fd)->file_position; 388 | unlock_open_fd(fd); 389 | return pos; 390 | } 391 | 392 | // Increase the file position of the open fd 393 | static inline void increase_file_position(int fd, off_t offset) { 394 | get_open_fd_write(fd)->file_position += offset; 395 | unlock_open_fd(fd); 396 | } 397 | 398 | static bool is_subpath(const char* parent, const char* child) { 399 | int i = 0; 400 | char parent_char, child_char; 401 | while ((child_char = child[i])) { 402 | parent_char = parent[i]; 403 | if (!parent_char) 404 | return true; 405 | if (child_char != parent_char) 406 | return false; 407 | i += 1; 408 | } 409 | return parent[i] == '\0'; 410 | } 411 | 412 | // Return value must be freed. 413 | static char* resolve_fd(int fd) { 414 | char* path = strdup(get_open_fd_read(fd)->path); 415 | unlock_open_fd(fd); 416 | return path; 417 | } 418 | 419 | #define LDP_FUSE_PATH_MAX_LEN 256 420 | 421 | #define STAT_VER _STAT_VER 422 | 423 | // Whether `path` is in the LDP_FUSE filesystem. The LDP_FUSE filesystem is 424 | // mounted under the LDP_FUSE_PATH env variable. 425 | static bool path_in_fs(const char* path) { 426 | if (!path) 427 | return false; 428 | bool in_fs = false; 429 | char* cwd = NULL; 430 | char* fs_paths = getenv(LDP_FUSE_PATH); 431 | if (!fs_paths) { 432 | in_fs = true; 433 | goto out; 434 | } 435 | cwd = getcwd(NULL, 0); 436 | char full_path[PATH_MAX + 1]; 437 | cwk_path_get_absolute(cwd, path, full_path, sizeof(full_path)); 438 | char* fs_path; 439 | while ((fs_path = strtok(fs_paths, LDP_FUSE_DELIM))) { 440 | fs_paths = NULL; // Must be set to NULL for strtok 441 | if (full_path == NULL) { 442 | LDP_FUSE_DEBUG_PRINT("Error resolving path %s: %s\n", fs_path, 443 | strerror(errno)); 444 | in_fs = false; 445 | goto out; 446 | } 447 | if (is_subpath(fs_path, full_path)) { 448 | in_fs = true; 449 | goto out; 450 | } 451 | } 452 | 453 | out: 454 | if (in_fs) 455 | LDP_FUSE_DEBUG_PRINT("Path %s is in filesystem. \n", path); 456 | else 457 | LDP_FUSE_DEBUG_PRINT("Path %s is NOT in the filesystem\n", path); 458 | // getchar(); 459 | if (cwd != NULL) 460 | free(cwd); 461 | return in_fs; 462 | } 463 | 464 | // Whether file descriptor `fd` is in the LDP_FUSE filesystem. The LDP_FUSE 465 | // filesystem is mounted under the LDP_FUSE_PATH env variable. 466 | static bool fd_in_fs(int fd) { 467 | LDP_FUSE_DEBUG_PRINT("fd %d in fs? ", fd); 468 | bool in_fs; 469 | const ldp_fuse_open_fd* open_fd = get_open_fd_read(fd); 470 | bool cached_available = open_fd->in_fs != UNKNOWN; 471 | if (cached_available) { 472 | in_fs = open_fd->in_fs; 473 | } 474 | unlock_open_fd(fd); 475 | if (!cached_available) { 476 | ldp_fuse_open_fd* open_fd = get_open_fd_write(fd); 477 | // Cache whether an opened fd is in this filesystem 478 | // through the `in_fs` field 479 | in_fs = path_in_fs(open_fd->path); 480 | open_fd->in_fs = in_fs ? IN_FS : NOT_IN_FS; 481 | unlock_open_fd(fd); 482 | } 483 | if (in_fs) 484 | LDP_FUSE_DEBUG_PRINT("YES\n"); 485 | else 486 | LDP_FUSE_DEBUG_PRINT("NO\n"); 487 | return in_fs; 488 | } 489 | 490 | ssize_t pread_impl(int fd, void* buf, size_t count, off_t offset) { 491 | if (!orig_pread) 492 | orig_pread = (orig_pread_t)dlsym(RTLD_NEXT, "pread"); 493 | return funcs.read(fd, buf, count, offset, orig_pread); 494 | } 495 | 496 | // Calls user-provided `pread` function 497 | ssize_t pread(int fd, void* buf, size_t count, off_t offset) { 498 | LDP_FUSE_DEBUG_PRINT("pread(%d, %p, %zu, %ld)\n", fd, buf, count, offset); 499 | if (!orig_pread) 500 | orig_pread = (orig_pread_t)dlsym(RTLD_NEXT, "pread"); 501 | 502 | if (!funcs.read || !fd_in_fs(fd)) { 503 | return orig_pread(fd, buf, count, offset); 504 | } 505 | return pread_impl(fd, buf, count, offset); 506 | } 507 | 508 | ssize_t pread64(int fd, void* buf, size_t count, off_t offset) 509 | __attribute__((alias("pread"))); 510 | 511 | // Redirect to `pread` 512 | ssize_t read(int fd, void* buf, size_t count) { 513 | if (!orig_read) 514 | orig_read = (orig_read_t)dlsym(RTLD_NEXT, "read"); 515 | // It is important to NOT redirect to `pread` if the fd is not in the 516 | // filesystem, as non-seekable fds are not supported by `pread`. 517 | if (!fd_in_fs(fd)) { 518 | return orig_read(fd, buf, count); 519 | } 520 | // Seeking needs to be manually tracked as `pread` 521 | // does not increase the open fd offset 522 | off_t offset = get_file_position(fd); 523 | ssize_t nread = pread_impl(fd, buf, count, offset); 524 | increase_file_position(fd, nread); 525 | return nread; 526 | } 527 | 528 | int fstatat_wrapper(int dirfd, 529 | const char* restrict pathname, 530 | struct stat* restrict statbuf, 531 | int flags) { 532 | LDP_FUSE_DEBUG_PRINT("fstatat_wrapper: %s\n", pathname); 533 | if (!orig_fxstatat) 534 | orig_fxstatat = (orig_fxstatat_t)dlsym(RTLD_NEXT, "__fxstatat"); 535 | int ret = orig_fxstatat(STAT_VER, dirfd, pathname, statbuf, flags); 536 | LDP_FUSE_DEBUG_PRINT("fstatat_wrapper: %s -> %d\n", pathname, ret); 537 | return ret; 538 | } 539 | 540 | int fstatat_impl(int ver, 541 | int dirfd, 542 | const char* restrict pathname, 543 | struct stat* restrict statbuf, 544 | int flags) { 545 | return funcs.stat(dirfd, pathname, statbuf, flags, fstatat_wrapper); 546 | } 547 | 548 | // Calls user-provided `fstatat` function 549 | int __fxstatat(int ver, 550 | int dirfd, 551 | const char* restrict pathname, 552 | struct stat* restrict statbuf, 553 | int flags) { 554 | if (!orig_fxstatat) 555 | orig_fxstatat = (orig_fxstatat_t)dlsym(RTLD_NEXT, "__fxstatat"); 556 | 557 | if (!funcs.stat || !path_in_fs(pathname)) 558 | return orig_fxstatat(STAT_VER, dirfd, pathname, statbuf, flags); 559 | return fstatat_impl(STAT_VER, dirfd, pathname, statbuf, flags); 560 | } 561 | 562 | // Redirect to `__fxstatat` 563 | int __xstat(int ver, 564 | const char* restrict pathname, 565 | struct stat* restrict statbuf) { 566 | if (!orig_xstat) 567 | orig_xstat = (orig_xstat_t)dlsym(RTLD_NEXT, "__xstat"); 568 | if (!path_in_fs(pathname)) 569 | return orig_xstat(ver, pathname, statbuf); 570 | int ret = fstatat_impl(ver, AT_FDCWD, pathname, statbuf, 0); 571 | LDP_FUSE_DEBUG_PRINT("__xstat: %s -> %d\n", pathname, ret); 572 | return ret; 573 | } 574 | 575 | // Redirect to `__fxstatat` 576 | int __fxstat(int ver, int fd, struct stat* statbuf) { 577 | if (!orig_fxstat) 578 | orig_fxstat = (orig_fxstat_t)dlsym(RTLD_NEXT, "__fxstat"); 579 | if (!fd_in_fs(fd)) 580 | return orig_fxstat(ver, fd, statbuf); 581 | const char* path = resolve_fd(fd); 582 | int ret = fstatat_impl(ver, AT_FDCWD, path, statbuf, 0); 583 | LDP_FUSE_DEBUG_PRINT("__fxstat: %s -> %d\n", path, ret); 584 | return ret; 585 | } 586 | 587 | // Redirect to `__fxstatat` 588 | int __lxstat(int ver, 589 | const char* restrict pathname, 590 | struct stat* restrict statbuf) { 591 | if (!orig_lxstat) 592 | orig_lxstat = (orig_lxstat_t)dlsym(RTLD_NEXT, "__lxstat"); 593 | if (!path_in_fs(pathname)) 594 | return orig_lxstat(ver, pathname, statbuf); 595 | int ret = fstatat_impl(ver, AT_FDCWD, pathname, statbuf, AT_SYMLINK_NOFOLLOW); 596 | LDP_FUSE_DEBUG_PRINT("__lxstat: %s -> %d\n", pathname, ret); 597 | return ret; 598 | } 599 | 600 | ssize_t getxattr_impl(const char* path, 601 | const char* name, 602 | void* value, 603 | size_t size) { 604 | if (!orig_getxattr) 605 | orig_getxattr = (orig_getxattr_t)dlsym(RTLD_NEXT, "getxattr"); 606 | return funcs.getxattr(path, name, value, size, orig_getxattr); 607 | } 608 | 609 | // Calls user-provided `getxattr` function 610 | ssize_t getxattr(const char* path, const char* name, void* value, size_t size) { 611 | LDP_FUSE_DEBUG_PRINT("getxattr: %s\n", path); 612 | if (!orig_getxattr) 613 | orig_getxattr = (orig_getxattr_t)dlsym(RTLD_NEXT, "getxattr"); 614 | if (!path_in_fs(path) || !funcs.getxattr) 615 | return orig_getxattr(path, name, value, size); 616 | return funcs.getxattr(path, name, value, size, orig_getxattr); 617 | } 618 | 619 | // Redirect to `getxattr_impl`. Does NOT do anything special for symbolic links, 620 | // contrary to the `getxattr` documentation. 621 | ssize_t lgetxattr(const char* path, 622 | const char* name, 623 | void* value, 624 | size_t size) { 625 | LDP_FUSE_DEBUG_PRINT("lgetxattr: %s\n", path); 626 | if (!orig_lgetxattr) 627 | orig_lgetxattr = (orig_lgetxattr_t)dlsym(RTLD_NEXT, "lgetxattr"); 628 | if (!path_in_fs(path)) 629 | return orig_lgetxattr(path, name, value, size); 630 | return getxattr_impl(path, name, value, size); 631 | } 632 | 633 | // Redirect to `getxattr_impl`. 634 | ssize_t fgetxattr(int fd, const char* name, void* value, size_t size) { 635 | LDP_FUSE_DEBUG_PRINT("fgetxattr: %d\n", fd); 636 | if (!orig_fgetxattr) 637 | orig_fgetxattr = (orig_fgetxattr_t)dlsym(RTLD_NEXT, "fgetxattr"); 638 | if (!fd_in_fs(fd)) 639 | return orig_fgetxattr(fd, name, value, size); 640 | const char* path = resolve_fd(fd); 641 | return getxattr_impl(path, name, value, size); 642 | } 643 | 644 | int openat_impl(int dirfd, const char* pathname, int flags, mode_t mode) { 645 | if (!orig_openat) 646 | orig_openat = (orig_openat_t)dlsym(RTLD_NEXT, "openat"); 647 | int fd = funcs.open(dirfd, pathname, flags, mode, orig_openat); 648 | open_fd(fd, pathname); 649 | return fd; 650 | } 651 | 652 | // Redirect to `openat_impl` 653 | int openat(int dirfd, const char* pathname, int flags, ...) { 654 | LDP_FUSE_DEBUG_PRINT("openat_wrapper: %s\n", pathname); 655 | int fd; 656 | if (!orig_openat) 657 | orig_openat = (orig_openat_t)dlsym(RTLD_NEXT, "openat"); 658 | mode_t mode = 0; 659 | 660 | if ((flags & O_CREAT) != 0 || (flags & O_TMPFILE) != 0) { 661 | va_list args; 662 | va_start(args, flags); 663 | mode = va_arg(args, mode_t); 664 | va_end(args); 665 | } 666 | 667 | if (!funcs.open) 668 | fd = orig_openat(dirfd, pathname, flags, mode); 669 | else 670 | fd = openat_impl(dirfd, pathname, flags, mode); 671 | return fd; 672 | } 673 | 674 | int openat64(int fd, const char* path, int flags, ...) 675 | __attribute__((alias("openat"))); 676 | 677 | int __openat64(int fd, const char* path, int flags, ...) 678 | __attribute__((alias("openat"))); 679 | 680 | int __openat64_2(int fd, const char* path, int flags, ...) 681 | __attribute__((alias("openat"))); 682 | 683 | // Redirect to `openat_impl` 684 | int open(const char* pathname, int flags, ...) { 685 | LDP_FUSE_DEBUG_PRINT("open_wrapper: %s\n", pathname); 686 | int fd; 687 | if (!orig_open) { 688 | orig_open = (orig_open_t)dlsym(RTLD_NEXT, "open"); 689 | } 690 | // Additional variadic arg `mode` is only supplied 691 | // when either of these bits are set 692 | if ((flags & O_CREAT) != 0 || (flags & O_TMPFILE) != 0) { 693 | va_list args; 694 | va_start(args, flags); 695 | mode_t mode = va_arg(args, mode_t); 696 | if (!path_in_fs(pathname)) 697 | return orig_open(pathname, flags, mode); 698 | fd = openat_impl(AT_FDCWD, pathname, flags, mode); 699 | va_end(args); 700 | } else { 701 | if (!path_in_fs(pathname)) 702 | return orig_open(pathname, flags); 703 | fd = openat_impl(AT_FDCWD, pathname, flags, 0); 704 | } 705 | return fd; 706 | } 707 | 708 | int __open(const char* pathname, int flags, ...) __attribute__((alias("open"))); 709 | int __open64(const char* pathname, int flags, ...) 710 | __attribute__((alias("open"))); 711 | int __open64_2(const char* pathname, int flags, ...) 712 | __attribute__((alias("open"))); 713 | int __open_2(const char* pathname, int flags, ...) 714 | __attribute__((alias("open"))); 715 | int __open_nocancel(const char* pathname, int flags, ...) 716 | __attribute__((alias("open"))); 717 | int open64(const char* pathname, int flags, ...) __attribute__((alias("open"))); 718 | 719 | // Redirect to `openat_impl` 720 | int creat(const char* pathname, mode_t mode) { 721 | LDP_FUSE_DEBUG_PRINT("creat_wrapper: %s\n", pathname); 722 | if (!orig_creat) { 723 | orig_creat = (orig_creat_t)dlsym(RTLD_NEXT, "creat"); 724 | } 725 | if (!path_in_fs(pathname)) 726 | return orig_creat(pathname, mode); 727 | return openat_impl(AT_FDCWD, pathname, O_CREAT | O_WRONLY | O_TRUNC, mode); 728 | } 729 | 730 | int creat64(const char* pathname, mode_t mode) __attribute__((nonnull)) 731 | __attribute__((alias("creat"))); 732 | 733 | ssize_t pwrite_impl(int fd, const void* buf, size_t count, off_t offset) { 734 | if (!orig_pwrite) 735 | orig_pwrite = (orig_pwrite_t)dlsym(RTLD_NEXT, "pwrite"); 736 | return funcs.write(fd, buf, count, offset, orig_pwrite); 737 | } 738 | 739 | ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset) { 740 | if (!orig_pwrite) 741 | orig_pwrite = (orig_pwrite_t)dlsym(RTLD_NEXT, "pwrite"); 742 | if (!funcs.write || !fd_in_fs(fd)) 743 | return orig_pwrite(fd, buf, count, offset); 744 | return pwrite_impl(fd, buf, count, offset); 745 | } 746 | 747 | // Redirect to `pwrite_impl` 748 | ssize_t write(int fd, const void* buf, size_t count) { 749 | LDP_FUSE_DEBUG_PRINT("write_wrapper: %d\n", fd); 750 | if (!orig_write) 751 | orig_write = (orig_write_t)dlsym(RTLD_NEXT, "write"); 752 | if (!fd_in_fs(fd)) 753 | return orig_write(fd, buf, count); 754 | return pwrite_impl(fd, buf, count, 0); 755 | } 756 | 757 | ssize_t readlink(const char* restrict pathname, 758 | char* restrict buf, 759 | size_t bufsiz) { 760 | LDP_FUSE_DEBUG_PRINT("readlink_wrapper: %s\n", pathname); 761 | if (!orig_readlink) 762 | orig_readlink = (orig_readlink_t)dlsym(RTLD_NEXT, "readlink"); 763 | if (!funcs.readlink || !path_in_fs(pathname)) 764 | return orig_readlink(pathname, buf, bufsiz); 765 | 766 | return funcs.readlink(pathname, buf, bufsiz, orig_readlink); 767 | } 768 | 769 | int mknodat(int dirfd, const char* pathname, mode_t mode, dev_t dev) { 770 | LDP_FUSE_DEBUG_PRINT("mknodat_wrapper: %s\n", pathname); 771 | if (!orig_mknodat) 772 | orig_mknodat = (orig_mknodat_t)dlsym(RTLD_NEXT, "mknodat"); 773 | if (!funcs.mknod || !path_in_fs(pathname)) 774 | return orig_mknodat(dirfd, pathname, mode, dev); 775 | return funcs.mknod(dirfd, pathname, mode, dev, orig_mknodat); 776 | } 777 | 778 | int mknod(const char* pathname, mode_t mode, dev_t dev) { 779 | LDP_FUSE_DEBUG_PRINT("mknod_wrapper: %s\n", pathname); 780 | if (!orig_mknod) 781 | orig_mknod = (orig_mknod_t)dlsym(RTLD_NEXT, "mknod"); 782 | 783 | if (!funcs.mknod || !path_in_fs(pathname)) 784 | return orig_mknod(pathname, mode, dev); 785 | return mknodat(AT_FDCWD, pathname, mode, dev); 786 | } 787 | 788 | int mkdir(const char* pathname, mode_t mode) { 789 | LDP_FUSE_DEBUG_PRINT("mkdir_wrapper: %s\n", pathname); 790 | if (!orig_mkdir) 791 | orig_mkdir = (orig_mkdir_t)dlsym(RTLD_NEXT, "mkdir"); 792 | 793 | if (!funcs.mkdir || !path_in_fs(pathname)) 794 | return orig_mkdir(pathname, mode); 795 | 796 | return funcs.mkdir(pathname, mode, orig_mkdir); 797 | } 798 | 799 | int unlink(const char* pathname) { 800 | LDP_FUSE_DEBUG_PRINT("unlink_wrapper: %s\n", pathname); 801 | if (!orig_unlink) 802 | orig_unlink = (orig_unlink_t)dlsym(RTLD_NEXT, "unlink"); 803 | if (!funcs.unlink || !path_in_fs(pathname)) 804 | return orig_unlink(pathname); 805 | return funcs.unlink(pathname, orig_unlink); 806 | } 807 | 808 | int rmdir(const char* pathname) { 809 | LDP_FUSE_DEBUG_PRINT("rmdir_wrapper: %s\n", pathname); 810 | if (!orig_rmdir) 811 | orig_rmdir = (orig_rmdir_t)dlsym(RTLD_NEXT, "rmdir"); 812 | 813 | if (!funcs.rmdir || !path_in_fs(pathname)) 814 | return orig_rmdir(pathname); 815 | 816 | return funcs.rmdir(pathname, orig_rmdir); 817 | } 818 | 819 | int symlink(const char* target, const char* linkpath) { 820 | LDP_FUSE_DEBUG_PRINT("symlink_wrapper: %s\n", linkpath); 821 | if (!orig_symlink) 822 | orig_symlink = (orig_symlink_t)dlsym(RTLD_NEXT, "symlink"); 823 | if (!funcs.symlink || !path_in_fs(target)) 824 | return orig_symlink(target, linkpath); 825 | return funcs.symlink(target, linkpath, orig_symlink); 826 | } 827 | 828 | int rename(const char* oldpath, const char* newpath) { 829 | LDP_FUSE_DEBUG_PRINT("rename_wrapper: %s\n", newpath); 830 | if (!orig_rename) 831 | orig_rename = (orig_rename_t)dlsym(RTLD_NEXT, "rename"); 832 | 833 | if (!funcs.rename || !path_in_fs(oldpath)) 834 | return orig_rename(oldpath, newpath); 835 | return funcs.rename(oldpath, newpath, orig_rename); 836 | } 837 | 838 | int link(const char* oldpath, const char* newpath) { 839 | LDP_FUSE_DEBUG_PRINT("link_wrapper: %s\n", newpath); 840 | if (!orig_link) 841 | orig_link = (orig_link_t)dlsym(RTLD_NEXT, "link"); 842 | 843 | if (!funcs.link || !path_in_fs(oldpath)) 844 | return orig_link(oldpath, newpath); 845 | return funcs.link(oldpath, newpath, orig_link); 846 | } 847 | 848 | int chmod(const char* path, mode_t mode) { 849 | LDP_FUSE_DEBUG_PRINT("chmod_wrapper: %s\n", path); 850 | if (!orig_chmod) 851 | orig_chmod = (orig_chmod_t)dlsym(RTLD_NEXT, "chmod"); 852 | 853 | if (!funcs.chmod || !path_in_fs(path)) 854 | return orig_chmod(path, mode); 855 | return funcs.chmod(path, mode, orig_chmod); 856 | } 857 | 858 | int chown(const char* path, uid_t owner, gid_t group) { 859 | LDP_FUSE_DEBUG_PRINT("chown_wrapper: %s\n", path); 860 | if (!orig_chown) 861 | orig_chown = (orig_chown_t)dlsym(RTLD_NEXT, "chown"); 862 | 863 | if (!funcs.chown || !path_in_fs(path)) 864 | return orig_chown(path, owner, group); 865 | return funcs.chown(path, owner, group, orig_chown); 866 | } 867 | 868 | int truncate(const char* path, off_t length) { 869 | LDP_FUSE_DEBUG_PRINT("truncate: %s\n", path); 870 | if (!orig_truncate) 871 | orig_truncate = (orig_truncate_t)dlsym(RTLD_NEXT, "truncate"); 872 | 873 | if (!funcs.truncate || !path_in_fs(path)) 874 | return orig_truncate(path, length); 875 | return funcs.truncate(path, length, orig_truncate); 876 | } 877 | 878 | int close(int fd) { 879 | LDP_FUSE_DEBUG_PRINT("close(%d)\n", fd); 880 | if (!orig_close) 881 | orig_close = (orig_close_t)dlsym(RTLD_NEXT, "close"); 882 | 883 | if (!funcs.close || !fd_in_fs(fd)) 884 | return orig_close(fd); 885 | return funcs.close(fd, orig_close); 886 | } 887 | 888 | // Translated to multiple `close` calls. `flags` argument is ignored. 889 | int close_range(unsigned int first, unsigned int last, unsigned int flags) { 890 | LDP_FUSE_DEBUG_PRINT("close_range(%d, %d)\n", first, last); 891 | int retval = 0; 892 | if (first > last) 893 | return -EINVAL; 894 | for (unsigned int current = first; current <= last; current++) { 895 | // If one of the `close` calls errors with -1, try closing the other fd's, 896 | // then return the error value. 897 | retval |= close(current); 898 | } 899 | return retval; 900 | } 901 | 902 | DIR* opendir(const char* name) { 903 | LDP_FUSE_DEBUG_PRINT("opendir_wrapper: %s\n", name); 904 | if (!orig_opendir) 905 | orig_opendir = (orig_opendir_t)dlsym(RTLD_NEXT, "opendir"); 906 | 907 | if (!funcs.opendir || !path_in_fs(name)) 908 | return orig_opendir(name); 909 | return funcs.opendir(name, orig_opendir); 910 | } 911 | 912 | int faccessat_impl(int dirfd, const char* pathname, int mode, int flags) { 913 | LDP_FUSE_DEBUG_PRINT("faccessat impl: %s\n", pathname); 914 | if (!orig_faccessat) 915 | orig_faccessat = (orig_faccessat_t)dlsym(RTLD_NEXT, "faccessat"); 916 | return funcs.access(pathname, mode, orig_faccessat); 917 | } 918 | 919 | // Redirect to `faccessat_impl` 920 | int faccessat(int dirfd, const char* pathname, int mode, int flags) { 921 | LDP_FUSE_DEBUG_PRINT("faccessat_wrapper: %s\n", pathname); 922 | if (!orig_faccessat) 923 | orig_faccessat = (orig_faccessat_t)dlsym(RTLD_NEXT, "faccessat"); 924 | 925 | if (!funcs.access || !path_in_fs(pathname)) 926 | return orig_faccessat(pathname, mode); 927 | return faccessat_impl(dirfd, pathname, mode, flags); 928 | } 929 | 930 | // Redirect to `faccessat_impl` 931 | int access(const char* pathname, int mode) { 932 | LDP_FUSE_DEBUG_PRINT("access_wrapper: %s\n", pathname); 933 | if (!orig_access) 934 | orig_access = (orig_access_t)dlsym(RTLD_NEXT, "access"); 935 | if (!funcs.access || !path_in_fs(pathname)) 936 | return orig_access(pathname, mode); 937 | 938 | return faccessat_impl(AT_FDCWD, pathname, mode, 0); 939 | } 940 | 941 | // Redirect to `faccessat_impl` 942 | int euidaccess(const char* pathname, int mode) { 943 | LDP_FUSE_DEBUG_PRINT("euidaccess_wrapper: %s\n", pathname); 944 | if (!orig_euidaccess) 945 | orig_euidaccess = (orig_euidaccess_t)dlsym(RTLD_NEXT, "euidaccess"); 946 | if (!funcs.access || !path_in_fs(pathname)) 947 | return orig_euidaccess(pathname, mode); 948 | return faccessat_impl(AT_FDCWD, pathname, mode, AT_EACCESS); 949 | } 950 | 951 | // Redirect to `euidaccess` 952 | int eaccess(const char* pathname, int mode) { 953 | return euidaccess(pathname, mode); 954 | } 955 | 956 | // User can not provide `lseek` implementation at the moment. 957 | // Call the original `lseek` and increment LDP_FUSE's own OFT. 958 | typedef off_t (*orig_lseek_t)(int fd, off_t offset, int whence); 959 | orig_lseek_t orig_lseek = NULL; 960 | off_t lseek(int fd, off_t offset, int whence) { 961 | if (!orig_lseek) 962 | orig_lseek = (orig_lseek_t)dlsym(RTLD_NEXT, "lseek"); 963 | int retval = orig_lseek(fd, offset, whence); 964 | ldp_fuse_open_fd* open_fd = NULL; 965 | struct stat statbuf; 966 | if (retval != -1) { 967 | switch (whence) { 968 | case SEEK_SET: 969 | open_fd = get_open_fd_write(fd); 970 | open_fd->file_position = offset; 971 | unlock_open_fd(fd); 972 | break; 973 | case SEEK_CUR: 974 | open_fd = get_open_fd_write(fd); 975 | open_fd->file_position += offset; 976 | unlock_open_fd(fd); 977 | break; 978 | case SEEK_END: 979 | if (fstat(fd, &statbuf) == -1) { 980 | fprintf(stderr, 981 | "LDP_FUSE: Could not determine size of file with fd %d - " 982 | "`stat` failed\n", 983 | fd); 984 | exit(1); 985 | } 986 | int size = statbuf.st_size; 987 | open_fd = get_open_fd_write(fd); 988 | open_fd->file_position = size + offset; 989 | unlock_open_fd(fd); 990 | break; 991 | // SEEK_DATA and SEEK_HOLE are not supported currently 992 | case SEEK_DATA: 993 | fprintf(stderr, 994 | "LDP_FUSE - `SEEK_DATA` is not supported for `lseek`'s " 995 | "`whence` parameter\n"); 996 | exit(1); 997 | case SEEK_HOLE: 998 | fprintf(stderr, 999 | "LDP_FUSE - `SEEK_DATA` is not supported for `lseek`'s " 1000 | "`whence` parameter\n"); 1001 | exit(1); 1002 | } 1003 | } 1004 | return retval; 1005 | } 1006 | 1007 | off_t lseek64(int fd, off_t offset, int whence) __attribute__((nonnull)) 1008 | __attribute__((nothrow)) __attribute__((leaf)) __attribute__((alias("lseek"))); 1009 | 1010 | void ldp_fuse_init_rw_locks() { 1011 | #ifdef LDP_FUSE_THREAD_SAFE 1012 | for (int i = 0; i < LDP_FUSE_OFT_SIZE; i++) { 1013 | pthread_rwlock_init(&_oft.table[i].rw_lock, NULL); 1014 | } 1015 | #endif 1016 | } 1017 | 1018 | void ldp_fuse_init(const struct ldp_fuse_funcs* args) { 1019 | _oft.table = malloc(LDP_FUSE_OFT_SIZE * sizeof(struct ldp_fuse_open_fd)); 1020 | memset(_oft.table, '\0', LDP_FUSE_OFT_SIZE * sizeof(struct ldp_fuse_open_fd)); 1021 | ldp_fuse_path = getenv(LDP_FUSE_PATH); 1022 | funcs = *args; 1023 | 1024 | #ifdef LDP_FUSE_DEBUG 1025 | pid_t pid = getpid(); 1026 | char str[80]; 1027 | sprintf(str, "/tmp/.ldp_fuse_mounted_%d", pid); 1028 | mkdir(str, 0777); 1029 | #endif 1030 | 1031 | #ifdef LDP_FUSE_THREAD_SAFE 1032 | ldp_fuse_init_rw_locks(); 1033 | #endif 1034 | } 1035 | 1036 | #define LDPRELOAD_FUSE_MAIN \ 1037 | static void __attribute__((constructor)) _ldpreload_fuse_main_() 1038 | #endif -------------------------------------------------------------------------------- /scripts/ldpfuse.sh: -------------------------------------------------------------------------------- 1 | cd ./cli && cargo build && cd ..; 2 | 3 | ./cli/target/debug/ldpfuse -v -m /tmp/ldpfuse -s ./examples/passthrough/passthrough.so -- $1 -------------------------------------------------------------------------------- /scripts/run_fuse_benchmark.sh: -------------------------------------------------------------------------------- 1 | # Filebench requires ASLR to be turned off, for some reason... 2 | sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"; 3 | filebench -f $1 &> "$1.out" -------------------------------------------------------------------------------- /scripts/run_ldpfuse_benchmark.sh: -------------------------------------------------------------------------------- 1 | mkdir /tmp/filebench &> /dev/null; 2 | cd ./cli && cargo build && cd ..; 3 | # Filebench requires ASLR to be turned off, for some reason... 4 | sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"; 5 | 6 | sudo ./cli/target/debug/ldpfuse -v -m /tmp/filebench -s ./examples/passthrough/passthrough.so -- filebench -f $1 &> "$1.out" -------------------------------------------------------------------------------- /scripts/run_native_benchmark.sh: -------------------------------------------------------------------------------- 1 | # Filebench requires ASLR to be turned off, for some reason... 2 | sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"; 3 | sudo filebench -f $1 &> "$1.out" -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | test_* 2 | !test_*.c -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -g -I../ldp_fuse/include -D LDP_FUSE_DEBUG 3 | LDFLAGS = -ldl -lpthread 4 | PTHREAD_FLAGS = -D LDP_FUSE_THREAD_SAFE 5 | TEST_NAMES = test_path_in_fs test_oft test_multithread 6 | 7 | 8 | all: st 9 | 10 | # single-threaded 11 | st: $(TEST_NAMES) 12 | 13 | # multi-threaded 14 | mt: LDFLAGS += -lpthread 15 | mt: CFLAGS += -D LDP_FUSE_THREAD_SAFE 16 | mt: $(TEST_NAMES) 17 | 18 | test_%: test_%.o 19 | $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) 20 | 21 | %.o: %.c 22 | $(CC) $(CFLAGS) -c $< $(LDFLAGS) 23 | 24 | .PHONY: clean 25 | clean: 26 | rm -f *.o $(TEST_NAMES) -------------------------------------------------------------------------------- /tests/passthrough_test_fs.c: -------------------------------------------------------------------------------- 1 | // Static version of the passthrough file system for testing purposes. 2 | #include "ldpfuse.h" 3 | #include 4 | 5 | ssize_t simple_read(int fd, void *buf, size_t count, off_t offset, 6 | orig_pread_t pread_fn) { 7 | LDP_FUSE_DEBUG_PRINT("simple_read\n"); 8 | return pread_fn(fd, buf, count, offset); 9 | } 10 | 11 | int simple_stat(int dirfd, const char *restrict pathname, 12 | struct stat *restrict statbuf, int flags, 13 | orig_fstatat_t fstatat_fn) { 14 | 15 | LDP_FUSE_DEBUG_PRINT("simple_stat\n"); 16 | return fstatat_fn(dirfd, pathname, statbuf, flags); 17 | } 18 | 19 | int simple_open(int dirfd, const char *pathname, int flags, mode_t mode, 20 | orig_openat_t openat_fn) { 21 | LDP_FUSE_DEBUG_PRINT("simple_open\n"); 22 | return openat_fn(dirfd, pathname, flags, mode); 23 | } 24 | 25 | ssize_t simple_write(int fd, const void *buf, size_t count, off_t offset, 26 | orig_pwrite_t pwrite_fn) { 27 | return pwrite_fn(fd, buf, count, 0); 28 | } 29 | 30 | ssize_t simple_readlink(const char *path, char *buf, size_t bufsize, 31 | orig_readlink_t readlink_fn) { 32 | LDP_FUSE_DEBUG_PRINT("simple_readlink\n"); 33 | return readlink_fn(path, buf, bufsize); 34 | } 35 | 36 | int simple_mknod(int fd, const char *path, mode_t mode, dev_t dev, 37 | orig_mknodat_t mknod_fn) { 38 | LDP_FUSE_DEBUG_PRINT("simple_mknod\n"); 39 | return mknod_fn(fd, path, mode, dev); 40 | } 41 | 42 | int simple_mkdir(const char *path, mode_t mode, orig_mkdir_t mkdir_fn) { 43 | LDP_FUSE_DEBUG_PRINT("simple_mkdir\n"); 44 | return mkdir_fn(path, mode); 45 | } 46 | 47 | int simple_rmdir(const char *path, orig_rmdir_t rmdir_fn) { 48 | LDP_FUSE_DEBUG_PRINT("simple_rmdir\n"); 49 | return rmdir_fn(path); 50 | } 51 | 52 | int simple_symlink(const char *path1, const char *path2, 53 | orig_symlink_t symlink_fn) { 54 | LDP_FUSE_DEBUG_PRINT("simple_symlink\n"); 55 | return symlink_fn(path1, path2); 56 | } 57 | 58 | int simple_rename(const char *path1, const char *path2, 59 | orig_rename_t rename_fn) { 60 | LDP_FUSE_DEBUG_PRINT("simple_rename\n"); 61 | return rename_fn(path1, path2); 62 | } 63 | 64 | int simple_link(const char *path1, const char *path2, orig_link_t link_fn) { 65 | LDP_FUSE_DEBUG_PRINT("simple_link\n"); 66 | return link_fn(path1, path2); 67 | } 68 | 69 | int simple_chmod(const char *path, mode_t mode, orig_chmod_t chmod_fn) { 70 | LDP_FUSE_DEBUG_PRINT("simple_chmod\n"); 71 | return chmod_fn(path, mode); 72 | } 73 | 74 | int simple_chown(const char *path, uid_t owner, gid_t group, 75 | orig_chown_t chown_fn) { 76 | LDP_FUSE_DEBUG_PRINT("simple_chown\n"); 77 | return chown_fn(path, owner, group); 78 | } 79 | 80 | int simple_truncate(const char *path, off_t size, orig_truncate_t truncate_fn) { 81 | LDP_FUSE_DEBUG_PRINT("simple_truncate\n"); 82 | return truncate_fn(path, size); 83 | } 84 | 85 | int simple_close(int fd, orig_close_t close_fn) { 86 | LDP_FUSE_DEBUG_PRINT("simple_close\n"); 87 | return close_fn(fd); 88 | } 89 | 90 | DIR *simple_opendir(const char *path, orig_opendir_t opendir_fn) { 91 | LDP_FUSE_DEBUG_PRINT("simple_opendir\n"); 92 | return opendir_fn(path); 93 | } 94 | 95 | int simple_faccessat(const char *path, int mode, orig_faccessat_t access_fn) { 96 | LDP_FUSE_DEBUG_PRINT("simple_faccessat\n"); 97 | return access_fn(path, mode); 98 | } 99 | 100 | int simple_unlink(const char *path, orig_unlink_t unlink_fn) { 101 | LDP_FUSE_DEBUG_PRINT("simple_unlink\n"); 102 | return unlink_fn(path); 103 | } 104 | 105 | ssize_t simple_getxattr(const char *path, const char *name, void *value, 106 | size_t size, orig_getxattr_t getxattr_fn) { 107 | LDP_FUSE_DEBUG_PRINT("simple_getxattr\n"); 108 | return getxattr_fn(path, name, value, size); 109 | } 110 | 111 | void init_test_fuse() { 112 | struct ldp_fuse_funcs funcs = {.read = simple_read, 113 | .stat = simple_stat, 114 | .open = simple_open, 115 | .write = simple_write, 116 | .readlink = simple_readlink, 117 | .mknod = simple_mknod, 118 | .mkdir = simple_mkdir, 119 | .unlink = simple_unlink, 120 | .rmdir = simple_rmdir, 121 | .symlink = simple_symlink, 122 | .rename = simple_rename, 123 | .link = simple_link, 124 | .chmod = simple_chmod, 125 | .chown = simple_chown, 126 | .truncate = simple_truncate, 127 | .close = simple_close, 128 | .opendir = simple_opendir, 129 | .access = simple_faccessat, 130 | .getxattr = simple_getxattr}; 131 | ldp_fuse_init(&funcs); 132 | } -------------------------------------------------------------------------------- /tests/run_all.sh: -------------------------------------------------------------------------------- 1 | ./test_oft && 2 | ./test_path_in_fs && 3 | ./test_multithread -------------------------------------------------------------------------------- /tests/test_multithread.c: -------------------------------------------------------------------------------- 1 | // This test statically includes LDP_FUSE to test its inner workings. 2 | // Run this test under valgrind. 3 | 4 | #ifndef LDP_FUSE_THREAD_SAFE 5 | #define LDP_FUSE_THREAD_SAFE 6 | #endif 7 | 8 | #include "./passthrough_test_fs.c" 9 | #include 10 | #include 11 | #include 12 | 13 | #define READ_TOTAL 1000 14 | #define N_THREADS 20 15 | #define READ_PER_THREAD (READ_TOTAL / N_THREADS) 16 | 17 | pthread_t threads[N_THREADS]; 18 | int thread_data[N_THREADS][2]; 19 | int read_amount[N_THREADS]; 20 | 21 | void *read_file(void *arg) { 22 | int *data = (int *)arg; 23 | int fd = data[0]; 24 | int thread_nr = data[1]; 25 | char *buf[READ_PER_THREAD]; 26 | int n_read = read(fd, buf, READ_PER_THREAD); 27 | read_amount[thread_nr] = n_read; 28 | return NULL; 29 | } 30 | 31 | void test_multithread_read() { 32 | char contents[READ_TOTAL]; 33 | memset(contents, 'a', sizeof(char) * READ_TOTAL); 34 | int fd = open("/tmp/mt_test.txt", O_CREAT | O_RDWR | O_APPEND, 0644); 35 | assert(fd != -1); 36 | int res = write(fd, contents, READ_TOTAL); 37 | assert(res == READ_TOTAL); 38 | for (int i = 0; i < N_THREADS; i++) { 39 | // Set up thread data, so each thread knows its number and what fd to read 40 | // from 41 | thread_data[i][0] = fd; 42 | thread_data[i][1] = i; 43 | pthread_create(&threads[i], NULL, read_file, &thread_data[i]); 44 | } 45 | 46 | for (int i = 0; i < N_THREADS; i++) { 47 | pthread_join(threads[i], NULL); 48 | assert(read_amount[i] == READ_PER_THREAD); 49 | } 50 | 51 | LDP_FUSE_DEBUG_PRINT("Final file position: %ld\n", 52 | _oft.table[fd].file_position); 53 | // The threads together should have read exactly this amount, and therefore 54 | // incremented the file position to `READ_TOTAL`. 55 | assert(_oft.table[fd].file_position == READ_TOTAL); 56 | 57 | int wr_lock_result = pthread_rwlock_trywrlock(&_oft.table[fd].rw_lock); 58 | // rw_lock should not be rd or wr locked anymore 59 | assert(wr_lock_result == 0); 60 | } 61 | 62 | int main() { 63 | init_test_fuse(); 64 | test_multithread_read(); 65 | return 0; 66 | } -------------------------------------------------------------------------------- /tests/test_oft.c: -------------------------------------------------------------------------------- 1 | // This test statically includes LDP_FUSE to test its inner workings. 2 | // Run this test under valgrind. 3 | 4 | #include 5 | #include 6 | #include "./passthrough_test_fs.c" 7 | 8 | #define N_OPENED_FILES 103 9 | #define WRITE_AMT 10 10 | #define START_FD 3 11 | 12 | int opened_fds[N_OPENED_FILES - START_FD]; 13 | 14 | void test_oft_alloc() { 15 | char* path = malloc(sizeof("/tmp/file@@@.txt")); 16 | for (int i = START_FD; i < N_OPENED_FILES; i++) { 17 | int result = sprintf(path, "/tmp/file%d.txt", i); 18 | assert(result > 0); 19 | int fd = open(path, O_CREAT | O_RDWR | O_APPEND, 0644); 20 | fprintf(stderr, "%d - fd: %d\n", i, fd); 21 | assert(fd != -1); 22 | assert(_oft.table[fd].in_fs == UNKNOWN); 23 | assert(_oft.table[fd].path != NULL); 24 | opened_fds[i - START_FD] = fd; 25 | } 26 | free(path); 27 | } 28 | 29 | void test_oft_caching() { 30 | char* path = malloc(sizeof("/tmp/file@@@.txt")); 31 | char* readbuff = malloc(1); 32 | for (int i = START_FD; i < N_OPENED_FILES; i++) { 33 | int fd = opened_fds[i - START_FD]; 34 | int result = sprintf(path, "/tmp/file%d.txt", fd); 35 | assert(result > 0); 36 | ssize_t n_read = read(fd, readbuff, 0); 37 | LDP_FUSE_DEBUG_PRINT("n_read %zd fd:%d\n", n_read, fd); 38 | assert(n_read == 0); 39 | assert(_oft.table[fd].in_fs == IN_FS); 40 | } 41 | free(readbuff); 42 | free(path); 43 | } 44 | 45 | void test_oft_offset() { 46 | int fd = opened_fds[0]; 47 | for (int i = 0; i < WRITE_AMT; i++) { 48 | ssize_t n_written = write(fd, "a", 1); 49 | assert(n_written == 1); 50 | } 51 | char contents[WRITE_AMT]; 52 | int n_read = read(fd, contents, WRITE_AMT); 53 | assert(strcmp(contents, "aaaaaaaaaa") == 0); 54 | assert(n_read == WRITE_AMT); 55 | assert(_oft.table[fd].file_position == WRITE_AMT); 56 | } 57 | 58 | int main() { 59 | init_test_fuse(); 60 | test_oft_alloc(); 61 | test_oft_caching(); 62 | test_oft_offset(); 63 | return 0; 64 | } -------------------------------------------------------------------------------- /tests/test_path_in_fs.c: -------------------------------------------------------------------------------- 1 | #include "ldpfuse.h" 2 | #include 3 | 4 | void test_slash() { 5 | setenv(LDP_FUSE_PATH, "/", 1); 6 | assert(path_in_fs("/") == true); 7 | assert(path_in_fs("/tmp") == true); 8 | assert(path_in_fs("/tmp/") == true); 9 | assert(path_in_fs("/tmp/file.txt") == true); 10 | } 11 | 12 | void test_path() { 13 | setenv(LDP_FUSE_PATH, "/some/path", 1); 14 | assert(path_in_fs("/") == false); 15 | assert(path_in_fs("/some") == false); 16 | assert(path_in_fs("/some/") == false); 17 | assert(path_in_fs("/some/path") == true); 18 | assert(path_in_fs("/some/path/") == true); 19 | assert(path_in_fs("/some/path/file.txt") == true); 20 | assert(path_in_fs("/some/path/sub") == true); 21 | assert(path_in_fs("/some/path/sub/") == true); 22 | assert(path_in_fs("/some/path/sub/file.txt") == true); 23 | } 24 | 25 | int main() { 26 | test_slash(); 27 | test_path(); 28 | return 0; 29 | } 30 | --------------------------------------------------------------------------------