├── .github ├── pull_request_template.md └── workflows │ └── release-please.yml ├── .gitignore ├── .rustfmt.toml ├── .vscode └── launch.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── design ├── fs_init.md ├── img │ └── disk.png ├── ls.md ├── mkdir.md ├── mkfs.md ├── stat.md └── tinyfs.md └── src ├── main.rs ├── mkfs.rs └── tiny ├── bitmap.rs ├── constants.rs ├── directory.rs ├── fs.rs ├── fs_extensions.rs ├── inode.rs ├── mod.rs └── superblock.rs /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## overview 2 | 3 | ## testing instructions 4 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Install stable toolchain 20 | uses: dtolnay/rust-toolchain@stable 21 | 22 | - uses: googleapis/release-please-action@v4 23 | id: release 24 | with: 25 | token: ${{ secrets.RELEASE_PLEASE_TOKEN }} 26 | release-type: simple 27 | 28 | - name: upload release artifact 29 | if: ${{ steps.release.outputs.release_created }} 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | run: | 33 | cargo build --release 34 | gh release upload ${{ steps.release.outputs.tag_name }} ./target/release/tinyFS 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # MSVC Windows builds of rustc generate these, which store debugging information 9 | *.pdb 10 | 11 | # RustRover 12 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 13 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 14 | # and can be added to the global gitignore or merged into this file. For a more nuclear 15 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 16 | #.idea/ 17 | /target 18 | *.img 19 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width=120 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug executable 'tinyFS'", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--bin=tinyFS", 15 | "--package=tinyFS" 16 | ], 17 | "filter": { 18 | "name": "tinyFS", 19 | "kind": "bin" 20 | } 21 | }, 22 | "args": [], 23 | "cwd": "${workspaceFolder}" 24 | }, 25 | { 26 | "type": "lldb", 27 | "request": "launch", 28 | "name": "Debug unit tests in executable 'tinyFS'", 29 | "cargo": { 30 | "args": [ 31 | "test", 32 | "--no-run", 33 | "--bin=tinyFS", 34 | "--package=tinyFS" 35 | ], 36 | "filter": { 37 | "name": "tinyFS", 38 | "kind": "bin" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.2.0](https://github.com/jobala/tinyFS/compare/v0.1.0...v0.2.0) (2025-01-22) 4 | 5 | 6 | ### Features 7 | 8 | * add support for ls ([300407c](https://github.com/jobala/tinyFS/commit/300407c5af3d0937031a41fa173658f74bb5b141)) 9 | * support mkdir ([#10](https://github.com/jobala/tinyFS/issues/10)) ([6caeb48](https://github.com/jobala/tinyFS/commit/6caeb48b0142733423bfa9d864421309a32e1436)) 10 | * support start ([a43bd6c](https://github.com/jobala/tinyFS/commit/a43bd6ccec0e0619e49c18b2fcedb05f1fc04586)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * add instructions ([3831f9a](https://github.com/jobala/tinyFS/commit/3831f9a40795f18c6316bde46fc28077fe9a6667)) 16 | * refactor ttl ([4444642](https://github.com/jobala/tinyFS/commit/4444642e65f0bfbff47255c9f3fcf7d68239a777)) 17 | 18 | ## [0.1.0](https://github.com/jobala/tinyFS/compare/v0.0.1...v0.1.0) (2025-01-11) 19 | 20 | 21 | ### Features 22 | 23 | * create root directory on first mount ([b015914](https://github.com/jobala/tinyFS/commit/b01591444d23decd8214538e356f9255caa991a0)) 24 | 25 | ## [0.0.1](https://github.com/jobala/tinyFS/compare/0.0.0...v0.0.1) (2025-01-04) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * invalid workflow file ([025c950](https://github.com/jobala/tinyFS/commit/025c950ec4b2da0c98e66f7f8b56bc96cc35e8f8)) 31 | * test release please ([2f1845f](https://github.com/jobala/tinyFS/commit/2f1845fb5ac9f520b0b216307b03c64d83bc7630)) 32 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "bincode" 7 | version = "1.3.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 10 | dependencies = [ 11 | "serde", 12 | ] 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "2.8.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 19 | 20 | [[package]] 21 | name = "bitvec" 22 | version = "1.0.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 25 | dependencies = [ 26 | "funty", 27 | "radium", 28 | "tap", 29 | "wyz", 30 | ] 31 | 32 | [[package]] 33 | name = "cfg-if" 34 | version = "1.0.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 37 | 38 | [[package]] 39 | name = "cfg_aliases" 40 | version = "0.2.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 43 | 44 | [[package]] 45 | name = "funty" 46 | version = "2.0.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 49 | 50 | [[package]] 51 | name = "fuser" 52 | version = "0.15.1" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "53274f494609e77794b627b1a3cddfe45d675a6b2e9ba9c0fdc8d8eee2184369" 55 | dependencies = [ 56 | "libc", 57 | "log", 58 | "memchr", 59 | "nix", 60 | "page_size", 61 | "pkg-config", 62 | "smallvec", 63 | "zerocopy", 64 | ] 65 | 66 | [[package]] 67 | name = "libc" 68 | version = "0.2.169" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 71 | 72 | [[package]] 73 | name = "log" 74 | version = "0.4.22" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 77 | 78 | [[package]] 79 | name = "memchr" 80 | version = "2.7.4" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 83 | 84 | [[package]] 85 | name = "nix" 86 | version = "0.29.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 89 | dependencies = [ 90 | "bitflags", 91 | "cfg-if", 92 | "cfg_aliases", 93 | "libc", 94 | ] 95 | 96 | [[package]] 97 | name = "page_size" 98 | version = "0.6.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" 101 | dependencies = [ 102 | "libc", 103 | "winapi", 104 | ] 105 | 106 | [[package]] 107 | name = "pkg-config" 108 | version = "0.3.31" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 111 | 112 | [[package]] 113 | name = "proc-macro2" 114 | version = "1.0.92" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 117 | dependencies = [ 118 | "unicode-ident", 119 | ] 120 | 121 | [[package]] 122 | name = "quote" 123 | version = "1.0.37" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 126 | dependencies = [ 127 | "proc-macro2", 128 | ] 129 | 130 | [[package]] 131 | name = "radium" 132 | version = "0.7.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 135 | 136 | [[package]] 137 | name = "serde" 138 | version = "1.0.216" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 141 | dependencies = [ 142 | "serde_derive", 143 | ] 144 | 145 | [[package]] 146 | name = "serde_derive" 147 | version = "1.0.216" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 150 | dependencies = [ 151 | "proc-macro2", 152 | "quote", 153 | "syn", 154 | ] 155 | 156 | [[package]] 157 | name = "smallvec" 158 | version = "1.13.2" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 161 | 162 | [[package]] 163 | name = "syn" 164 | version = "2.0.90" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 167 | dependencies = [ 168 | "proc-macro2", 169 | "quote", 170 | "unicode-ident", 171 | ] 172 | 173 | [[package]] 174 | name = "tap" 175 | version = "1.0.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 178 | 179 | [[package]] 180 | name = "tinyFS" 181 | version = "0.1.0" 182 | dependencies = [ 183 | "bincode", 184 | "bitvec", 185 | "fuser", 186 | "libc", 187 | "serde", 188 | ] 189 | 190 | [[package]] 191 | name = "unicode-ident" 192 | version = "1.0.14" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 195 | 196 | [[package]] 197 | name = "winapi" 198 | version = "0.3.9" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 201 | dependencies = [ 202 | "winapi-i686-pc-windows-gnu", 203 | "winapi-x86_64-pc-windows-gnu", 204 | ] 205 | 206 | [[package]] 207 | name = "winapi-i686-pc-windows-gnu" 208 | version = "0.4.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 211 | 212 | [[package]] 213 | name = "winapi-x86_64-pc-windows-gnu" 214 | version = "0.4.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 217 | 218 | [[package]] 219 | name = "wyz" 220 | version = "0.5.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 223 | dependencies = [ 224 | "tap", 225 | ] 226 | 227 | [[package]] 228 | name = "zerocopy" 229 | version = "0.8.14" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" 232 | dependencies = [ 233 | "zerocopy-derive", 234 | ] 235 | 236 | [[package]] 237 | name = "zerocopy-derive" 238 | version = "0.8.14" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" 241 | dependencies = [ 242 | "proc-macro2", 243 | "quote", 244 | "syn", 245 | ] 246 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tinyFS" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | fuser = "0.15.1" 8 | bincode = "1.3.3" 9 | bitvec = "1" 10 | serde = { version = "1", features = ["derive"] } 11 | libc = "0.2.169" 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Japheth Obala 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | - sudo fusermount -u /tmp/tiny > /dev/null 2>&1 3 | cargo run 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TinyFS: A Little File System 2 | 3 | TinyFS is a little filesystem with no big plans. 4 | 5 | ## Setup 6 | 7 | 1. Run `mkdir /tmp/tiny` 8 | 2. Run `sudo addgroup fuse` 9 | 10 | ## Running 11 | 12 | 1. To start **tinyFS** run `make run` in this project's root dir 13 | 2. To interact with tinyFS, open another terminal and perform file operations on `/tmp/tiny` ie `stat /tmp/tiny` 14 | 15 | ## Resources 16 | 17 | - [Filesystem Implementation](https://pages.cs.wisc.edu/~remzi/OSTEP/file-implementation.pdf) 18 | - [To FUSE or Not To Fuse](https://libfuse.github.io/doxygen/fast17-vangoor.pdf) 19 | - [Fuse Filesystems](https://zsiciarz.github.io/24daysofrust/book/vol1/day15.html) 20 | 21 | ## Supported 22 | 23 | - [x] stat 24 | - [x] ls 25 | - [x] mkdir 26 | - [ ] rmdir 27 | - [ ] create 28 | - [ ] read 29 | - [ ] write 30 | - [ ] unlink 31 | -------------------------------------------------------------------------------- /design/fs_init.md: -------------------------------------------------------------------------------- 1 | ## Filesystem Initialization 2 | 3 | Filesystem initialisation happens whenever the file system is mounted. 4 | 5 | ## Goals 6 | 7 | - Create the filesystem's root directory the first time the filesystem is mounted 8 | 9 | ## Non Goals 10 | 11 | - Adding default files in the default directory 12 | - Using A BTree to store directory entries 13 | 14 | ## Design 15 | 16 | The root directory should be created only if it wasn't created before. We will use the inode bitmap table to track what 17 | inode blocks have been used/allocated and since the root directory's inode is the first to be created we should 18 | check whether the inode bitmap at index zero is allocated. If it is, return. 19 | 20 | ```rust 21 | if bitmap.is_inode_allocated(0) { 22 | return 23 | } 24 | 25 | ... 26 | ``` 27 | 28 | Both files and directories are represented by an Inode, for directories, the Inode's **file_type** attribute is 29 | set to directory. 30 | 31 | ```rust 32 | let inode = Inode::new() 33 | inode.file_type = 1 // 1 == directory 34 | ``` 35 | 36 | Directories have entries which could be files or other directories, we'll use a hashmap that maps a path to some 37 | file or directory inode number. 38 | 39 | ```rust 40 | struct DirData { 41 | entries HashMap 42 | } 43 | ``` 44 | 45 | Each inode tracks where in disk its data is written. The method `allocate_blocks` returns the write location 46 | addresses, which are then stored in the inode's **block_ptrs** property. 47 | 48 | ```rust 49 | let dir_data = DirData::default() 50 | inode.block_ptrs = self.allocate_blocks(dir_data) 51 | ``` 52 | 53 | Finally, we need to save the inode to disk at the first inode block(zero indexed) and make sure the inode bitmap 54 | at index zero is marked as allocated. 55 | 56 | ```rust 57 | inode.save_at(0, &disk) 58 | bitmap.allocate_inode(0) 59 | ``` 60 | -------------------------------------------------------------------------------- /design/img/disk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jobala/tinyFS/95d49d7b5868f4ff485086e0512573428784cb12/design/img/disk.png -------------------------------------------------------------------------------- /design/ls.md: -------------------------------------------------------------------------------- 1 | ## ls 2 | 3 | ## Goals 4 | 5 | ## Non-Goals 6 | 7 | ## Design 8 | -------------------------------------------------------------------------------- /design/mkdir.md: -------------------------------------------------------------------------------- 1 | ## Support mkdir 2 | 3 | The `mkdir` command is used to create directories. 4 | 5 | ## Goals 6 | 7 | - Support mkdir 8 | 9 | ## Non-Goals 10 | 11 | ## Design 12 | 13 | Directory creation is handled by the `mkdir` FUSE function. The method signature specifies the **parent** directory, the 14 | directory **name** and **mode** -- permissions. 15 | 16 | ```rust 17 | fn mkdir(&mut self, _req: &fuse::Request, parent: u64, name: &std::ffi::OsStr, mode: u32, reply: fuse::ReplyEntry) 18 | ``` 19 | 20 | 1. Load the parent inode from disk 21 | 2. Load the parent's dir data 22 | 3. Create a new Inode for the new directory 23 | 4. Update the parent's dir data with a new entry based on the new Inode's data 24 | -------------------------------------------------------------------------------- /design/mkfs.md: -------------------------------------------------------------------------------- 1 | ## mkfs 2 | 3 | TinyFS uses a file as a disk, `mkfs` formats this file and lays out the different parts like the superblock, 4 | bitmap so and so forth. 5 | 6 | ## Goals 7 | - Format disk with Superblock 8 | - Format disk with Bitmap table 9 | 10 | ## Non Goals 11 | - mkfs doesn't intend to format disk with an inode table 12 | - mkfs doesn't intend to format disk with a data blocks table 13 | 14 | Formatting the disk requires to first have the data structure in memory, for example TinyFS' data blocks table 15 | is 56 blocks wide -- that is 56 * 4kb in size -- which will be wasteful to load into memory then write to disk. 16 | So how do we know a blocks location then? Simple, we can calculate it based on the bitmap index and the data 17 | block's table starting address. 18 | 19 | For example, the location of block 35 is; 20 | 21 | block_loc = data_table_start + (35 * sizeof(block)) 22 | 23 | We can then read and write to that location without loading the whole datablock table -- about 224mb -- into memory. 24 | 25 | ## Design 26 | 27 | **mkfs** takes some path which is where it will create its disk. 28 | 29 | ### Superblock 30 | 31 | The superblock holds metadata about the filesystem and knows how to serialize and deserialize itself into a binary format 32 | which `mkfs` then writes to disk. 33 | 34 | ### Bitmaps 35 | 36 | Bitmaps are used to determine whether an inode or block is free or allocated. TinyFS has two bitmap tables; inode and 37 | block bitmaps. However, for TinyFS we have more bits than we can use because each bitmap table uses a block (each block 38 | is 4096 bytes) giving us 4096 * 8 -- 32k-- bits. In reality, TinyFS needs around 80 bits for inodes. 39 | 40 | Bit 0 in the bitmap corresponds to inode 0, Bit 1 to inode 1 so and so forth. Bitmap also knows how to serialize and 41 | deserialize itself to a binary format which `mkfs` uses to write to disk. 42 | 43 | ## Implementation 44 | 45 | ``` 46 | class Superblock: 47 | function serialize_into() 48 | function deserialize_from() 49 | 50 | class Bitmap: 51 | function serialize_into() 52 | function deserialize_from() 53 | 54 | function mkfs(path: string) 55 | file = open(path, create) 56 | 57 | sb = Superblock() 58 | sb.serialize_into(file) 59 | 60 | bm = Bitmap() 61 | bm.serialize_into(file) 62 | 63 | file.flush() 64 | ``` 65 | -------------------------------------------------------------------------------- /design/stat.md: -------------------------------------------------------------------------------- 1 | ## stat 2 | 3 | `stat` is a linux command for showing file information and its sample output is shown below. 4 | 5 | ``` 6 | File: . 7 | Size: 4096 Blocks: 8 IO Block: 4096 directory 8 | Device: 179,2 Inode: 1278537 Links: 7 9 | Access: (0775/drwxrwxr-x) Uid: ( 1000/ jobala) Gid: ( 1003/ jobala) 10 | Access: 2025-01-14 16:05:26.789054116 +0300 11 | Modify: 2025-01-14 16:05:26.463056875 +0300 12 | Change: 2025-01-14 16:05:26.463056875 +0300 13 | Birth: 2024-12-14 15:22:49.207461152 +0300 14 | ``` 15 | 16 | ## Goals 17 | 18 | - Support `stat` for root directory 19 | 20 | ## Non Goals 21 | 22 | - Supporting `lookup`for non-root directories 23 | 24 | ## Design 25 | 26 | For tinyFS to support `stat` it needs to implement two methods `getattr` & `lookup` their signatures are shown below. 27 | 28 | ```rust 29 | lookup(&mut self, _req: &fuse::Request, _parent: u64, _name: &std::ffi::OsStr, reply: fuse::ReplyEntry); 30 | 31 | getattr(&mut self, _req: &fuse::Request, ino: u64, reply: fuse::ReplyAttr); 32 | ``` 33 | 34 | ### Lookup 35 | 36 | `lookup` finds an inode given the parent's inode number and the name of the file/directory. 37 | 38 | #### Steps 39 | 40 | 1. Check if parent is 1 41 | 1. If it is, 42 | - load the root inode from disk 43 | - convert the inode to `FileAttr` 44 | - send a FUSE reply with the `FileAttr` object 45 | 2. If not, 46 | - send a FUSE error reply with `libc::ENOENT` 47 | 48 | 49 | ### getattr 50 | 51 | `getattr` has `ino` as one of its params, it holds the inode number. `lookup` provides `getattr` the inode number which 52 | getattr uses to load an inode from disk. 53 | 54 | #### Steps 55 | 56 | 1. Read inode from disk 57 | 2. Convert `Inode` to a `FileAttr` object 58 | 3. Send a FUSE reply with the `FileAttr` object 59 | `` 60 | -------------------------------------------------------------------------------- /design/tinyfs.md: -------------------------------------------------------------------------------- 1 | 2 | ## TinyFS 3 | TinyFS is a small and simple FUSE based filesystem created mainly for educational purposes and has no ambition of 4 | becoming a production ready filesystem. TinyFS will be implemented in Rust. 5 | 6 | ### Goals 7 | - Support **mkfs** to create the disk layout 8 | - Support the following file operations 9 | - **open** filename flag 10 | - **read** fd size 11 | - **write** fd string 12 | - **seek** fd offset 13 | - **close** fd 14 | - **mkdir** dirname 15 | - **rmdir** dirname 16 | - **cd** dirname 17 | - **ls** 18 | - **cat** 19 | - **tree** 20 | 21 | 22 | 23 | ### Non Goals 24 | - Support Journaling 25 | - Supporting very large files 26 | - Caching 27 | - Becoming a distributed filesystem 28 | 29 | 30 | ## Design 31 | 32 | ### Disk Layout 33 | 34 | TinyFS formats the disk into 4kb blocks and will have 64 blocks in total, which means that TinyFS requires a disk with 35 | 256mb free space. The first block will be used for the superblock, the next two blocks will be used for inode and data 36 | bitmap tables respectively and the following 5 blocks are used for inodes. 37 | 38 |
39 | 40 | ![Disk](img/disk.png) 41 |
TinyFS Disk Layout
42 |
43 | 44 | The disk layout above means that the first 8 blocks are used for managing the filesystem and the remaining 56 blocks are 45 | used for actual data storage. 46 | 47 | ### Datastructures 48 | 49 | #### Superblock 50 | 51 | Superblock contains metadata about the filesystem. TinyFS reads the superblock on initialization to know things like 52 | how much disk space is remaing. 53 | 54 | ``` 55 | Superblock 56 | block_size int 57 | block_count int 58 | ``` 59 | 60 | #### Bitmaps 61 | 62 | A bitmap is an array of bytes used to indicate whether the corresponding block is free or allocated. Each bit in the array 63 | maps to a corresponding block. Bit 0 maps to block 0, bit 1 maps to block 1 so on and so forth. 64 | 65 | ``` 66 | Bitmap 67 | []byte 68 | ``` 69 | 70 | #### Inodes 71 | 72 | Inodes hold file metadata and represent both files and directories 73 | 74 | ``` 75 | Inode 76 | mode int 77 | size int 78 | created_at Time 79 | modified_at Time 80 | deleted_at Time 81 | flags byte 82 | hard_links int 83 | direct_blocks []int 84 | indirect_block []int 85 | double_indirect_block []int 86 | ``` 87 | 88 | 89 | #### Data Blocks 90 | 91 | Datablocks have no particular structure and hold the actual file data. 92 | 93 | ## Implementation 94 | 95 | [mkfs](./mkfs.md) 96 | [creating a file](./creating_a_file.md) 97 | 98 | ## Notes 99 | Before mounting the FS, make sure that the directory exists ie `mkdir /tmp/tiny` 100 | 101 | To unmount the fs, `fusermount -u /tmp/tiny` 102 | 103 | When permissions are denied use `sudo addgroup fuse` 104 | 105 | ## References 106 | 107 | [Filesystem Implementation](https://pages.cs.wisc.edu/~remzi/OSTEP/file-implementation.pdf) 108 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod mkfs; 2 | mod tiny; 3 | 4 | use std::{fs::OpenOptions, path::Path}; 5 | 6 | use tiny::{constants::Disk, TinyFS}; 7 | 8 | fn main() { 9 | let path = Path::new("./tiny.img"); 10 | if !path.exists() { 11 | mkfs::make("./tiny.img"); 12 | } 13 | 14 | let mount_path = "/tmp/tiny"; 15 | 16 | fuser::mount2(TinyFS { disk: load_disk(path) }, &mount_path, &[]).expect("expected filesytem to mount"); 17 | } 18 | 19 | fn load_disk(path: &Path) -> Disk { 20 | OpenOptions::new() 21 | .write(true) 22 | .read(true) 23 | .open(path) 24 | .expect("file to have been opened") 25 | } 26 | -------------------------------------------------------------------------------- /src/mkfs.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::OpenOptions, 3 | io::{BufWriter, Write}, 4 | }; 5 | 6 | use crate::tiny::{bitmap::Bitmap, constants::BLOCK_SIZE, superblock::Superblock}; 7 | 8 | pub fn make(path: &str) { 9 | let disk = OpenOptions::new() 10 | .write(true) 11 | .create(true) 12 | .open(path) 13 | .expect("file to have been opened"); 14 | 15 | let mut buf = BufWriter::new(&disk); 16 | 17 | let mut super_block = Superblock::new(BLOCK_SIZE, 64); 18 | super_block 19 | .serialize_into(&mut buf) 20 | .expect("superblock to have been serialized"); 21 | 22 | let mut bitmap = Bitmap::new(); 23 | bitmap.save_to(&disk).expect("bitmap to have been serialized"); 24 | 25 | buf.flush().expect("buffer to have been flushed"); 26 | disk.set_len(64 * BLOCK_SIZE as u64).expect("to have set file size"); 27 | } 28 | -------------------------------------------------------------------------------- /src/tiny/bitmap.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, BufReader, BufWriter, Read, Seek, SeekFrom, Write}; 2 | 3 | use bitvec::prelude::*; 4 | 5 | use super::constants::{Disk, BLOCK_SIZE, DATA_BLOCK_BASE}; 6 | 7 | impl Bitmap { 8 | pub fn new() -> Self { 9 | Bitmap { 10 | inode: bitvec![u8, Lsb0; 0; BLOCK_SIZE * 8], 11 | data: bitvec![u8, Lsb0; 0; BLOCK_SIZE * 8 ], 12 | } 13 | } 14 | 15 | pub fn from(disk: &Disk) -> Bitmap { 16 | let buf = BufReader::new(disk); 17 | Self::deserialize_from(buf).expect("failed to load bitmap") 18 | } 19 | 20 | // TODO: check against the maximum number of inodes 21 | pub fn find_free_inode(&mut self) -> usize { 22 | // we start searching from 1 instead of 0 because 23 | // root inode has index 1. 24 | for i in 1..self.inode.len() { 25 | if self.inode[i] == false { 26 | return i; 27 | } 28 | } 29 | 30 | return 0; 31 | } 32 | 33 | pub fn allocate_inode(&mut self, index: usize, disk: &Disk) { 34 | self.inode.set(index, true); 35 | self.save_to(disk).expect("error saving inode to disk") 36 | } 37 | 38 | //pub fn free_inode(&mut self, index: usize) { 39 | // self.inode.set(index, false); 40 | //} 41 | 42 | pub fn allocate_data_block(&mut self, index: usize, disk: &Disk) { 43 | self.data.set(index, true); 44 | self.save_to(disk).expect("error saving inode to disk"); 45 | } 46 | 47 | // TODO: check against the maximum number of blocks 48 | // TODO: use correct error handling 49 | // https://doc.rust-lang.org/rust-by-example/error/multiple_error_types/define_error_type.html 50 | pub fn find_free_data_block(&mut self) -> usize { 51 | for i in 0..self.data.len() { 52 | if self.data[i] == false { 53 | return i; 54 | } 55 | } 56 | 57 | return 0; 58 | } 59 | 60 | pub fn free_data_block(&mut self, block_ptr: u64, disk: &Disk) { 61 | let index = (block_ptr.wrapping_sub(DATA_BLOCK_BASE)) / BLOCK_SIZE as u64; 62 | println!("block index: {} ptr: {}", index, block_ptr); 63 | 64 | self.data.set(index as usize, false); 65 | self.save_to(disk).expect("error saving inode to disk"); 66 | } 67 | 68 | pub fn is_inode_allocated(&mut self, index: usize) -> bool { 69 | self.inode[index] 70 | } 71 | 72 | pub fn save_to(&mut self, disk: &Disk) -> Result<(), io::Error> { 73 | let buf = BufWriter::new(disk); 74 | self.serialize_into(buf) 75 | } 76 | 77 | fn serialize_into(&mut self, mut buf: W) -> Result<(), io::Error> { 78 | let offset = BLOCK_SIZE as u64; 79 | 80 | buf.seek(SeekFrom::Start(offset))?; 81 | buf.write_all(self.inode.as_raw_slice())?; 82 | buf.write_all(self.data.as_raw_slice())?; 83 | buf.flush()?; 84 | Ok(()) 85 | } 86 | 87 | fn deserialize_from(mut r: R) -> Result { 88 | let offset = BLOCK_SIZE as u64; 89 | let mut bitmap = Bitmap::new(); 90 | 91 | let mut buf = [0u8; BLOCK_SIZE]; 92 | r.seek(SeekFrom::Start(offset))?; 93 | r.read_exact(&mut buf)?; 94 | bitmap.inode = BitVec::from_slice(&buf); 95 | 96 | r.read_exact(&mut buf)?; 97 | bitmap.data = BitVec::from_slice(&buf); 98 | 99 | Ok(bitmap) 100 | } 101 | } 102 | 103 | pub struct Bitmap { 104 | inode: BitVec, 105 | data: BitVec, 106 | } 107 | -------------------------------------------------------------------------------- /src/tiny/constants.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fs::File; 3 | 4 | pub const BLOCK_SIZE: usize = 4096; 5 | pub const INODE_BLOCK_COUNT: u64 = 5u64; 6 | pub const INODE_BLOCK_BASE: u64 = 3u64 * BLOCK_SIZE as u64; 7 | pub const DATA_BLOCK_BASE: u64 = INODE_BLOCK_BASE + (INODE_BLOCK_COUNT * BLOCK_SIZE as u64); 8 | 9 | #[derive(Deserialize, Serialize, Clone, Copy, Debug, PartialEq, Default)] 10 | pub enum InodeKind { 11 | #[default] 12 | File, 13 | Dir, 14 | } 15 | 16 | pub type Disk = File; 17 | pub type Block = [u8; BLOCK_SIZE]; 18 | -------------------------------------------------------------------------------- /src/tiny/directory.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io::{Read, Write}, 4 | }; 5 | 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use super::constants::InodeKind; 9 | 10 | impl DirData { 11 | pub fn serialize_into(&mut self, buf: W) -> Result<(), bincode::Error> { 12 | bincode::serialize_into(buf, self) 13 | } 14 | 15 | pub fn deserialize_from(buf: R) -> Result { 16 | let directory: Self = bincode::deserialize_from(buf)?; 17 | Ok(directory) 18 | } 19 | 20 | pub fn insert(&mut self, path: &str, entry: DirEntry) { 21 | self.entries.insert(String::from(path), entry); 22 | } 23 | } 24 | 25 | #[derive(Serialize, Deserialize, Default, Debug)] 26 | pub struct DirData { 27 | pub entries: HashMap, 28 | } 29 | 30 | #[derive(Serialize, Deserialize, Default, Debug)] 31 | pub struct DirEntry { 32 | pub ino: u64, 33 | pub name: String, 34 | pub kind: InodeKind, 35 | } 36 | -------------------------------------------------------------------------------- /src/tiny/fs.rs: -------------------------------------------------------------------------------- 1 | use fuser::{FileType, Filesystem}; 2 | use std::ffi::c_int; 3 | 4 | use crate::tiny::directory::DirEntry; 5 | 6 | use super::{ 7 | bitmap::Bitmap, 8 | constants::{Disk, InodeKind}, 9 | directory::DirData, 10 | inode::Inode, 11 | }; 12 | 13 | impl Filesystem for TinyFS { 14 | fn init(&mut self, _req: &fuser::Request<'_>, _config: &mut fuser::KernelConfig) -> Result<(), c_int> { 15 | println!("calling init"); 16 | 17 | let mut bm = Bitmap::from(&self.disk); 18 | if bm.is_inode_allocated(1) { 19 | return Ok(()); 20 | } 21 | 22 | let mut root_inode = Inode::new(InodeKind::Dir); 23 | root_inode.ino = 1; 24 | 25 | let dir_data = DirData::default(); 26 | root_inode.save_dir_data(dir_data, &mut bm, &self.disk); 27 | 28 | root_inode.save(&self.disk).expect("error saving root directory inode"); 29 | bm.allocate_inode(1, &self.disk); 30 | Ok(()) 31 | } 32 | 33 | fn lookup(&mut self, _req: &fuser::Request, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEntry) { 34 | let mut inode = Inode::load_from(&self.disk, parent).expect("error loading inode"); 35 | let parent_dir_data = inode.get_dir_data(&self.disk); 36 | 37 | if let Some(child_entry) = parent_dir_data.entries.get(self.to_string(name)) { 38 | let mut child = Inode::load_from(&self.disk, child_entry.ino).expect("error loading child"); 39 | reply.entry(&self.ttl(), &child.to_file_attr(), 0); 40 | } else if parent == 1 && name == "." { 41 | reply.entry(&self.ttl(), &inode.to_file_attr(), 0); 42 | } else { 43 | reply.error(libc::ENOENT); 44 | } 45 | } 46 | 47 | fn getattr(&mut self, _req: &fuser::Request, ino: u64, fd: Option, reply: fuser::ReplyAttr) { 48 | println!("calling getattr with ino: {} fd: {:?}", ino, fd); 49 | 50 | let mut inode = Inode::load_from(&self.disk, ino).expect("error loading inode"); 51 | reply.attr(&self.ttl(), &inode.to_file_attr()); 52 | } 53 | 54 | fn readdir(&mut self, _req: &fuser::Request, ino: u64, _fh: u64, offset: i64, mut reply: fuser::ReplyDirectory) { 55 | println!("calling readdir with ino {:?}", ino); 56 | 57 | let mut inode = Inode::load_from(&self.disk, ino).expect("error loading inode"); 58 | let dir_data = inode.get_dir_data(&self.disk); 59 | 60 | for (i, val) in dir_data.entries.iter().enumerate().skip(offset as usize) { 61 | let (name, entry) = val; 62 | 63 | let mut kind = FileType::RegularFile; 64 | if entry.kind == InodeKind::Dir { 65 | kind = FileType::Directory; 66 | } 67 | 68 | let _ = reply.add(entry.ino, i as i64 + 1, kind, name); 69 | } 70 | 71 | reply.ok(); 72 | } 73 | 74 | // TODO: handle dir exists 75 | // TODO: handle parent is not dir 76 | fn mkdir( 77 | &mut self, 78 | _req: &fuser::Request, 79 | parent: u64, 80 | name: &std::ffi::OsStr, 81 | _mode: u32, 82 | _umask: u32, 83 | reply: fuser::ReplyEntry, 84 | ) { 85 | println!("calling mkdir with name {:?} for parent {}", name, parent); 86 | 87 | let mut parent = Inode::load_from(&self.disk, parent).expect("error loading parent"); 88 | 89 | let mut child = Inode::new(InodeKind::Dir); 90 | let child_ino = child.save(&self.disk).expect("error saving child"); 91 | 92 | let entry = DirEntry { 93 | ino: child_ino, 94 | name: String::from(self.to_string(name)), 95 | kind: InodeKind::Dir, 96 | }; 97 | 98 | parent.update_entries(entry, &self.disk); 99 | let _ = parent.save(&self.disk); 100 | 101 | reply.entry(&self.ttl(), &child.to_file_attr(), 0); 102 | } 103 | } 104 | 105 | pub struct TinyFS { 106 | pub disk: Disk, 107 | } 108 | -------------------------------------------------------------------------------- /src/tiny/fs_extensions.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::OsStr, time::Duration}; 2 | 3 | use super::TinyFS; 4 | 5 | impl TinyFS { 6 | pub fn ttl(&mut self) -> Duration { 7 | Duration::new(1, 0) 8 | } 9 | 10 | pub fn to_string<'a>(&mut self, name: &'a OsStr) -> &'a str { 11 | if let Some(st) = name.to_str() { 12 | return st; 13 | } else { 14 | return ""; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/tiny/inode.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{BufReader, BufWriter, Cursor, Read, Seek, SeekFrom, Write}, 3 | time::{Duration, SystemTime, UNIX_EPOCH}, 4 | }; 5 | 6 | use super::{ 7 | bitmap::Bitmap, 8 | constants::{Block, Disk, InodeKind, BLOCK_SIZE, DATA_BLOCK_BASE, INODE_BLOCK_BASE}, 9 | directory::{DirData, DirEntry}, 10 | }; 11 | use fuser::{FileAttr, FileType}; 12 | use serde::{Deserialize, Serialize}; 13 | 14 | impl Inode { 15 | pub fn new(kind: InodeKind) -> Inode { 16 | let now = SystemTime::now() 17 | .duration_since(UNIX_EPOCH) 18 | .expect("error creating time"); 19 | 20 | Inode { 21 | ino: 0, 22 | kind, 23 | block_count: 0, 24 | size: 0, 25 | accessed_at: now.as_millis() as u64, 26 | modified_at: now.as_millis() as u64, 27 | created_at: now.as_millis() as u64, 28 | hard_links: 2, 29 | block_pointers: [0; 12], 30 | } 31 | } 32 | 33 | pub fn save(&mut self, disk: &Disk) -> Result { 34 | let mut bm = Bitmap::from(disk); 35 | let now = SystemTime::now() 36 | .duration_since(UNIX_EPOCH) 37 | .expect("error creating time"); 38 | 39 | let location: u64; 40 | if bm.is_inode_allocated(self.ino as usize) { 41 | println!("Inode {} is already allocated", self.ino); 42 | self.modified_at = now.as_millis() as u64; 43 | self.accessed_at = now.as_millis() as u64; 44 | location = Self::get_location(self.ino) 45 | } else { 46 | println!("Inode {} was not allocated", self.ino); 47 | let ino = bm.find_free_inode() as u64; 48 | self.ino = ino; 49 | location = Self::get_location(self.ino); 50 | bm.allocate_inode(self.ino as usize, &disk); 51 | } 52 | 53 | let mut buf = BufWriter::new(disk); 54 | 55 | let _ = buf.seek(SeekFrom::Start(location)); 56 | self.serialize_into(&mut buf)?; 57 | let _ = buf.flush(); 58 | 59 | Ok(self.ino) 60 | } 61 | 62 | pub fn load_from(disk: &Disk, ino: u64) -> Result { 63 | let location = Self::get_location(ino); 64 | let mut buf = BufReader::new(disk); 65 | let _ = buf.seek(SeekFrom::Start(location)); 66 | 67 | let mut read_buf = [0; size_of::()]; 68 | let _ = buf.read_exact(&mut read_buf); 69 | bincode::deserialize_from(&mut read_buf.as_slice()) 70 | } 71 | 72 | pub fn to_file_attr(&mut self) -> FileAttr { 73 | let mut kind = FileType::RegularFile; 74 | if self.kind == InodeKind::Dir { 75 | kind = FileType::Directory; 76 | } 77 | 78 | FileAttr { 79 | ino: self.ino, 80 | blksize: BLOCK_SIZE as u32, 81 | size: self.size as u64, 82 | blocks: self.block_count, 83 | atime: self.to_time(self.accessed_at), 84 | mtime: self.to_time(self.modified_at), 85 | crtime: self.to_time(self.created_at), 86 | ctime: self.to_time(self.created_at), 87 | kind, 88 | perm: 0o755, 89 | nlink: self.hard_links, 90 | uid: 1000, 91 | gid: 1003, 92 | rdev: 0, 93 | flags: 0, 94 | } 95 | } 96 | 97 | pub fn update_entries(&mut self, entry: DirEntry, disk: &Disk) { 98 | let mut bm = Bitmap::from(&disk); 99 | let mut dir_data = self.get_dir_data(&disk); 100 | 101 | for ptr in self.block_pointers { 102 | if ptr == 0 { 103 | break; 104 | } 105 | 106 | bm.free_data_block(ptr, &disk); 107 | } 108 | 109 | let name = entry.name.clone(); 110 | dir_data.insert(&name, entry); 111 | 112 | self.save_dir_data(dir_data, &mut bm, &disk); 113 | } 114 | 115 | pub fn get_dir_data(&mut self, disk: &Disk) -> DirData { 116 | let mut buf = vec![]; 117 | 118 | for ptr in self.block_pointers { 119 | if ptr == 0 { 120 | break; 121 | } 122 | 123 | let block = self.load_block(ptr, disk); 124 | buf.append(&mut block.to_vec()); 125 | } 126 | 127 | if buf.len() > 0 { 128 | let s = &buf[..self.size + 1]; 129 | return DirData::deserialize_from(s).expect("error getting dir data"); 130 | } 131 | 132 | DirData::default() 133 | } 134 | 135 | pub fn save_dir_data(&mut self, mut data: DirData, bm: &mut Bitmap, disk: &Disk) { 136 | let data_buf = Vec::new(); 137 | let mut write_buf = BufWriter::new(data_buf); 138 | let _ = data.serialize_into(&mut write_buf); 139 | let _ = write_buf.flush(); 140 | let data_buf = write_buf.into_inner().expect("error getting inner buffer"); 141 | 142 | self.size = data_buf.len(); 143 | 144 | let (block_ptrs, block_count) = self.save_data_blocks(bm, data_buf, &disk); 145 | 146 | self.block_count = block_count as u64; 147 | self.block_pointers = block_ptrs; 148 | } 149 | 150 | fn load_block(&mut self, location: u64, disk: &Disk) -> Block { 151 | let mut block = [0; BLOCK_SIZE]; 152 | 153 | let mut disk_buf = BufReader::new(disk); 154 | let _ = disk_buf.seek(SeekFrom::Start(location)); 155 | disk_buf.read_exact(&mut block).expect("error reading block"); 156 | 157 | block 158 | } 159 | 160 | // TODO: throw error for files greater than 48KB in size 161 | fn save_data_blocks(&mut self, bitmap: &mut Bitmap, buf: Vec, disk: &Disk) -> ([u64; 12], usize) { 162 | let mut block_ptrs = [0u64; 12]; 163 | let mut cursor = Cursor::new(buf); 164 | let mut chunk = [0u8; BLOCK_SIZE]; 165 | let mut last_allocated = 0; 166 | 167 | while let Ok(n) = cursor.read(&mut chunk) { 168 | if n == 0 { 169 | println!("nothing more to read"); 170 | break; 171 | } 172 | 173 | let mut write_buf = BufWriter::new(disk); 174 | let index = bitmap.find_free_data_block(); 175 | let block_location = DATA_BLOCK_BASE + (index * BLOCK_SIZE) as u64; 176 | write_buf 177 | .seek(SeekFrom::Start(block_location)) 178 | .expect("error seeking to block location"); 179 | write_buf.write_all(&chunk).expect("error writing chunk"); 180 | write_buf.flush().expect("error flushing buff"); 181 | 182 | bitmap.allocate_data_block(index, &disk); 183 | block_ptrs[last_allocated] = block_location; 184 | last_allocated += 1; 185 | } 186 | 187 | (block_ptrs, last_allocated) 188 | } 189 | 190 | fn serialize_into(&mut self, buf: W) -> Result<(), bincode::Error> { 191 | bincode::serialize_into(buf, self) 192 | } 193 | 194 | fn to_time(&mut self, millis: u64) -> SystemTime { 195 | UNIX_EPOCH + Duration::from_millis(millis) 196 | } 197 | 198 | fn get_location(ino: u64) -> u64 { 199 | INODE_BLOCK_BASE + (ino * size_of::() as u64) 200 | } 201 | } 202 | 203 | #[derive(Serialize, Deserialize, Debug, Clone, Copy)] 204 | #[repr(C)] 205 | pub struct Inode { 206 | pub ino: u64, 207 | pub kind: InodeKind, 208 | pub size: usize, 209 | pub block_count: u64, 210 | pub accessed_at: u64, 211 | pub modified_at: u64, 212 | pub created_at: u64, 213 | pub hard_links: u32, 214 | pub block_pointers: [u64; 12], 215 | } 216 | -------------------------------------------------------------------------------- /src/tiny/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bitmap; 2 | pub mod constants; 3 | pub mod directory; 4 | pub mod fs; 5 | mod fs_extensions; 6 | pub mod inode; 7 | pub mod superblock; 8 | 9 | pub use fs::TinyFS; 10 | -------------------------------------------------------------------------------- /src/tiny/superblock.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::io::{Read, Write}; 3 | 4 | #[derive(Deserialize, Serialize, Debug)] 5 | pub struct Superblock { 6 | block_size: usize, 7 | block_count: u32, 8 | } 9 | 10 | impl Superblock { 11 | pub fn new(block_size: usize, block_count: u32) -> Superblock { 12 | Self { 13 | block_size, 14 | block_count, 15 | } 16 | } 17 | 18 | pub fn serialize_into(&mut self, buf: W) -> Result<(), bincode::Error> { 19 | bincode::serialize_into(buf, self) 20 | } 21 | 22 | pub fn deserialize_from(&mut self, buf: R) -> Result { 23 | let super_block: Self = bincode::deserialize_from(buf)?; 24 | Ok(super_block) 25 | } 26 | } 27 | 28 | #[cfg(test)] 29 | mod sb_tests { 30 | 31 | #[test] 32 | fn test_serialization() { 33 | println!("Hello, world!"); 34 | } 35 | } 36 | --------------------------------------------------------------------------------