├── License ├── Makefile ├── README ├── afl-tests ├── bigz ├── full ├── full2 ├── med └── simple ├── afl.c ├── anaimg.py ├── stfs.c ├── stfs.h ├── stfs.py ├── stfsfuzz.py ├── test.c └── testcases ├── closermdired ├── closeunlinked ├── closeunlinked2 ├── hang ├── missingend └── overwrite-on-full-fs /License: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016, stf <7o5rfu92t@ctrlc.hu> 2 | 3 | stfs is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Lesser General Public 5 | License as published by the Free Software Foundation; either 6 | version 2.1 of the License, or (at your option) any later version. 7 | 8 | stfs is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Lesser General Public License for more details. 12 | 13 | You should have received a copy of the GNU Lesser General Public 14 | License along with the GNU C Library; if not, write to the Free 15 | Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 16 | 02110-1301 USA 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS+=-Wall -O2 2 | 3 | all: stfs afl 4 | 5 | afl: afl.o stfs.o 6 | 7 | stfs: stfs.o test.o 8 | 9 | check: scan-build flawfinder cppcheck 10 | 11 | clean: 12 | rm -f stfs afl *.o 13 | 14 | scan-build: clean 15 | scan-build-3.9 make 16 | 17 | flawfinder: 18 | flawfinder --quiet stfs.c 19 | 20 | cppcheck: 21 | cppcheck --enable=all stfs.c 22 | 23 | .PHONY: clean check scan-build flawfinder cppcheck 24 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | stfs - simple tabular file system 2 | 3 | this file system is meant for the embedded flash of cortex-m3 4 | micro controller. I tried yaffs, but it used too much memory, and 5 | since the cortex-M3 controller already handles the low level 6 | stuff, is not really suited for this purpose. stfs uses no heap, 7 | and is also otherwise as the name hints: simple. 8 | 9 | stfs is a log-structured appending file system for embedded flash 10 | devices, that expose the flash mapped as memory. 11 | 12 | - assumes that you have only a few 4KB-128KB erasable blocks - all 13 | of the same size. 14 | 15 | - stfs provides only directories and files, but no other types like 16 | pipes, links, etc. also no file metadata like timestamps or access 17 | permissions. 18 | 19 | - filenames are max 32 bytes long, files max 64KB - both settings 20 | are configurable, but otherwise untested. 21 | 22 | - always reserves one empty block for vacuuming. 23 | 24 | - default chunksize is 128B with fs metadata included. unlike other 25 | flash file systems where the block size is usually limited by 512B. 26 | 27 | - totally single threaded 28 | 29 | License 30 | 31 | stfs is licensed LGPLV2.1+. 32 | 33 | api 34 | 35 | currently the api provides the following posix-like interfaces: 36 | 37 | directory handling functions: mkdir, rmdir, opendir, readdir, 38 | 39 | file handling functions: open, lseek, write, read, close, unlink, truncate 40 | 41 | generic functions: init 42 | 43 | how to play with it 44 | 45 | 1st of all this is a simulation, a toy. if you want to use it in 46 | your micro controller you have to adapt a few things. like how you 47 | write chunks and erase blocks. But you can play with it on your 48 | computer. Compile everything with the makefile: 49 | 50 | `CFLAGS="-DDEBUG_LEVEL=3" make all` 51 | 52 | where the debug level can be: 53 | - 0 is quiet, 54 | - 1 - errors, 55 | - 2 - info, 56 | - 3 - debug. 57 | 58 | if you want to fuzz, you probably want to go with level 0, when 59 | you debug you can play with other levels. 60 | 61 | after compiling, you get `stfs` and `afl`. 62 | 63 | `stfs` test binary 64 | `stfs` demos how to use stfs, executes a few test cases and then 65 | dumps the whole fs into ./test.img. 66 | 67 | afl 68 | `afl` is a simple script interpreter: 69 | 70 | afl.c - reads stdin to execute script on in-RAM stfs and dump the result into test.img 71 | commands are 72 | m - mkdir 73 | x - rmdir 74 | o 0|64 - open 64==O_CREAT 75 | w - write 76 | r - read 77 | s - seek 78 | c - close 79 | t - truncate 80 | d - unlink 81 | 82 | is always length prefixed, space-separated, e.g.: 83 | m 5 /root 84 | ^ path 85 | ^ length of path 86 | 87 | ./afl is also used by stfsfuzz.py, if you want to use it with afl, 88 | you should compile without logging and dumping of the fs image. 89 | 90 | you can find a few sample test cases in `testcases/` 91 | 92 | python tools 93 | 94 | there's two python tools: stfsfuzz.py and anaimg.py 95 | 96 | stfsfuzz.py runs `afl` repeatedly each time testing a different 97 | random command, if the command returns something else than -1, it 98 | is recorded in ./fuzz.script and keep this command in the test 99 | case for subsequent commands to be appended. in the background 100 | `afl` always dumps the latest fs image into `test.img` 101 | 102 | `anaimg.py` loads `test.img` and prints a condensed sequence of 103 | the chunks, the directory layout with file sizes, and the block 104 | statistics. 105 | 106 | python deps 107 | 108 | `pip install construct sh` 109 | 110 | data structure 111 | 112 | chunk_size = 128 113 | chunks_per_block = 1024 114 | 115 | 4 different chunk types are used: 116 | 117 | empty = 0xff (1B) irrelevant(all 0xff) (127B) 118 | inode (128B)- contain file meta information 119 | - chunktype (0xAA) (1B) 120 | - directory | file (1b) 121 | - size (2B) 122 | - parent_directory_obj_id (4B) 123 | - obj_id (4B) 124 | - name_len (6b) 125 | - name (32B) 126 | - data (84B) 127 | data (7B) - contain data 128 | - chunktype (0xCC) (1B) 129 | - seq_id (2B) 130 | - obj_id (4B) 131 | - data blob (chunksize-metasize) 132 | deleted = 0x00 (1B) irrelevant(all 0x00) (127B) 133 | 134 | inode with oid 1 is the root directory and virtual 135 | 136 | important implementation todo 137 | 138 | In case you adapat this code, please replace the call to 139 | `random(3)` in `stfs_init()` and in vacuum() to the cryptographic 140 | random function of your system such as /dev/random or /dev/urandom 141 | on Unix-like systems, and CryptGenRandom on Windows. -------------------------------------------------------------------------------- /afl-tests/bigz: -------------------------------------------------------------------------------- 1 | m 5 /qwer 2 | o 64 10 /qwer/qwer 3 | w 0 256 4 | w 0 256 5 | w 0 256 6 | w 0 256 7 | w 0 256 8 | w 0 256 9 | w 0 256 10 | w 0 256 11 | w 0 256 12 | w 0 256 13 | w 0 256 14 | w 0 256 15 | w 0 256 16 | w 0 256 17 | w 0 256 18 | w 0 256 19 | w 0 256 20 | w 0 256 21 | w 0 256 22 | w 0 256 23 | w 0 256 24 | w 0 256 25 | w 0 256 26 | w 0 256 27 | w 0 256 28 | w 0 256 29 | w 0 256 30 | w 0 256 31 | w 0 256 32 | w 0 256 33 | w 0 256 34 | w 0 256 35 | w 0 256 36 | w 0 256 37 | w 0 256 38 | w 0 256 39 | w 0 256 40 | w 0 256 41 | w 0 256 42 | w 0 256 43 | w 0 256 44 | w 0 256 45 | w 0 256 46 | w 0 256 47 | w 0 256 48 | w 0 256 49 | w 0 256 50 | w 0 256 51 | w 0 256 52 | w 0 256 53 | w 0 256 54 | w 0 256 55 | w 0 256 56 | w 0 256 57 | w 0 256 58 | w 0 256 59 | w 0 256 60 | w 0 256 61 | w 0 256 62 | w 0 256 63 | w 0 256 64 | w 0 256 65 | w 0 256 66 | w 0 256 67 | w 0 256 68 | w 0 256 69 | w 0 256 70 | w 0 256 71 | w 0 256 72 | w 0 256 73 | w 0 256 74 | w 0 256 75 | w 0 256 76 | w 0 256 77 | w 0 256 78 | w 0 256 79 | w 0 256 80 | w 0 256 81 | w 0 256 82 | w 0 256 83 | w 0 256 84 | w 0 256 85 | w 0 256 86 | w 0 256 87 | w 0 256 88 | w 0 256 89 | w 0 256 90 | w 0 256 91 | w 0 256 92 | w 0 256 93 | w 0 256 94 | w 0 256 95 | w 0 256 96 | w 0 256 97 | w 0 256 98 | w 0 256 99 | w 0 256 100 | w 0 256 101 | w 0 256 102 | w 0 256 103 | w 0 256 104 | w 0 256 105 | w 0 256 106 | w 0 256 107 | w 0 256 108 | w 0 256 109 | w 0 256 110 | w 0 256 111 | w 0 256 112 | w 0 256 113 | w 0 256 114 | w 0 256 115 | w 0 256 116 | w 0 256 117 | w 0 256 118 | w 0 256 119 | w 0 256 120 | w 0 256 121 | w 0 256 122 | w 0 256 123 | w 0 256 124 | w 0 256 125 | w 0 256 126 | w 0 256 127 | w 0 256 128 | w 0 256 129 | w 0 256 130 | w 0 256 131 | w 0 256 132 | w 0 256 133 | w 0 256 134 | w 0 256 135 | w 0 256 136 | w 0 256 137 | w 0 256 138 | w 0 256 139 | w 0 256 140 | w 0 256 141 | w 0 256 142 | w 0 256 143 | w 0 256 144 | w 0 256 145 | w 0 256 146 | w 0 256 147 | w 0 256 148 | w 0 256 149 | w 0 256 150 | w 0 256 151 | w 0 256 152 | w 0 256 153 | w 0 256 154 | w 0 256 155 | w 0 256 156 | w 0 256 157 | w 0 256 158 | w 0 256 159 | w 0 256 160 | w 0 256 161 | w 0 256 162 | w 0 256 163 | w 0 256 164 | w 0 256 165 | w 0 256 166 | w 0 256 167 | w 0 256 168 | w 0 256 169 | w 0 256 170 | w 0 256 171 | w 0 256 172 | w 0 256 173 | w 0 256 174 | w 0 256 175 | w 0 256 176 | w 0 256 177 | w 0 256 178 | w 0 256 179 | w 0 256 180 | w 0 256 181 | w 0 256 182 | w 0 256 183 | w 0 256 184 | w 0 256 185 | w 0 256 186 | w 0 256 187 | w 0 256 188 | w 0 256 189 | w 0 256 190 | w 0 256 191 | w 0 256 192 | w 0 256 193 | w 0 256 194 | w 0 256 195 | w 0 256 196 | w 0 256 197 | w 0 256 198 | w 0 256 199 | w 0 256 200 | w 0 256 201 | w 0 256 202 | w 0 256 203 | w 0 256 204 | w 0 256 205 | w 0 256 206 | w 0 256 207 | w 0 256 208 | w 0 256 209 | w 0 256 210 | w 0 256 211 | w 0 256 212 | w 0 256 213 | w 0 256 214 | w 0 256 215 | w 0 256 216 | w 0 256 217 | w 0 256 218 | w 0 256 219 | w 0 256 220 | w 0 256 221 | w 0 256 222 | w 0 256 223 | w 0 256 224 | w 0 256 225 | w 0 256 226 | w 0 256 227 | w 0 256 228 | w 0 256 229 | w 0 256 230 | w 0 256 231 | w 0 256 232 | w 0 256 233 | w 0 256 234 | w 0 256 235 | w 0 256 236 | w 0 256 237 | w 0 256 238 | w 0 256 239 | w 0 256 240 | w 0 256 241 | w 0 256 242 | w 0 256 243 | w 0 256 244 | w 0 256 245 | w 0 256 246 | w 0 256 247 | w 0 256 248 | w 0 256 249 | w 0 256 250 | w 0 256 251 | w 0 256 252 | w 0 256 253 | w 0 256 254 | w 0 256 255 | w 0 256 256 | w 0 256 257 | w 0 256 258 | w 0 256 259 | c 0 260 | t 16384 10 /qwer/qwer 261 | o 0 10 /qwer/qwer 262 | s 0 2 0 263 | w 0 1 f 264 | s 0 0 0 265 | r 0 10 266 | c 0 267 | d 10 /qwer/qwer -------------------------------------------------------------------------------- /afl-tests/full: -------------------------------------------------------------------------------- 1 | m 5 /qwer 2 | o 64 10 /qwer/lkjh 3 | w 0 65535 4 | c 0 5 | o 64 10 /qwer/vcxz 6 | w 0 65535 7 | c 0 8 | o 64 10 /qwer/fdsa 9 | w 0 65535 10 | c 0 11 | o 64 10 /qwer/rewq 12 | w 0 65535 13 | c 0 14 | o 64 10 /qwer/hjkl 15 | w 0 65535 16 | c 0 17 | o 64 10 /qwer/zxcv 18 | w 0 65535 19 | c 0 20 | o 64 10 /qwer/asdf 21 | w 0 65535 22 | c 0 23 | o 64 10 /qwer/qwer 24 | w 0 65535 25 | c 0 26 | s 0 65530 0 27 | w 0 3 28 | s 0 65530 0 29 | r 0 10 30 | c 0 31 | d 10 /qwer/qwer 32 | d 10 /qwer/asdf 33 | d 10 /qwer/qwer 34 | d 10 /qwer/zxcv 35 | o 64 10 /qwer/zxcv 36 | w 0 256 37 | w 0 256 38 | w 0 256 39 | w 0 256 40 | w 0 256 41 | w 0 256 42 | w 0 256 43 | w 0 256 44 | w 0 256 45 | w 0 256 46 | w 0 256 47 | w 0 256 48 | w 0 256 49 | w 0 256 50 | w 0 256 51 | w 0 256 52 | w 0 256 53 | w 0 256 54 | w 0 256 55 | w 0 256 56 | w 0 256 57 | w 0 256 58 | w 0 256 59 | w 0 256 60 | w 0 256 61 | w 0 256 62 | w 0 256 63 | w 0 256 64 | w 0 256 65 | w 0 256 66 | w 0 256 67 | w 0 256 68 | w 0 256 69 | w 0 256 70 | w 0 256 71 | w 0 256 72 | w 0 256 73 | w 0 256 74 | w 0 256 75 | w 0 256 76 | w 0 256 77 | w 0 256 78 | w 0 256 79 | w 0 256 80 | w 0 256 81 | w 0 256 82 | w 0 256 83 | w 0 256 84 | w 0 256 85 | w 0 256 86 | w 0 256 87 | w 0 256 88 | w 0 256 89 | w 0 256 90 | w 0 256 91 | w 0 256 92 | w 0 256 93 | w 0 256 94 | w 0 256 95 | w 0 256 96 | w 0 256 97 | w 0 256 98 | w 0 256 99 | w 0 256 100 | w 0 256 101 | w 0 256 102 | w 0 256 103 | w 0 256 104 | w 0 256 105 | w 0 256 106 | w 0 256 107 | w 0 256 108 | w 0 256 109 | w 0 256 110 | w 0 256 111 | w 0 256 112 | w 0 256 113 | w 0 256 114 | w 0 256 115 | w 0 256 116 | w 0 256 117 | w 0 256 118 | w 0 256 119 | w 0 256 120 | w 0 256 121 | w 0 256 122 | w 0 256 123 | w 0 256 124 | w 0 256 125 | w 0 256 126 | w 0 256 127 | w 0 256 128 | w 0 256 129 | w 0 256 130 | w 0 256 131 | w 0 256 132 | w 0 256 133 | w 0 256 134 | w 0 256 135 | w 0 256 136 | w 0 256 137 | w 0 256 138 | w 0 256 139 | w 0 256 140 | w 0 256 141 | w 0 256 142 | w 0 256 143 | w 0 256 144 | w 0 256 145 | w 0 256 146 | w 0 256 147 | w 0 256 148 | w 0 256 149 | w 0 256 150 | w 0 256 151 | w 0 256 152 | w 0 256 153 | w 0 256 154 | w 0 256 155 | w 0 256 156 | w 0 256 157 | w 0 256 158 | w 0 256 159 | w 0 256 160 | w 0 256 161 | w 0 256 162 | w 0 256 163 | w 0 256 164 | w 0 256 165 | w 0 256 166 | w 0 256 167 | w 0 256 168 | w 0 256 169 | w 0 256 170 | w 0 256 171 | w 0 256 172 | w 0 256 173 | w 0 256 174 | w 0 256 175 | w 0 256 176 | w 0 256 177 | w 0 256 178 | w 0 256 179 | w 0 256 180 | w 0 256 181 | w 0 256 182 | w 0 256 183 | w 0 256 184 | w 0 256 185 | w 0 256 186 | w 0 256 187 | w 0 256 188 | w 0 256 189 | w 0 256 190 | w 0 256 191 | w 0 256 192 | w 0 256 193 | w 0 256 194 | w 0 256 195 | w 0 256 196 | w 0 256 197 | w 0 256 198 | w 0 256 199 | w 0 256 200 | w 0 256 201 | w 0 256 202 | w 0 256 203 | w 0 256 204 | w 0 256 205 | w 0 256 206 | w 0 256 207 | w 0 256 208 | w 0 256 209 | w 0 256 210 | w 0 256 211 | w 0 256 212 | w 0 256 213 | w 0 256 214 | w 0 256 215 | w 0 256 216 | w 0 256 217 | w 0 256 218 | w 0 256 219 | w 0 256 220 | w 0 256 221 | w 0 256 222 | w 0 256 223 | w 0 256 224 | w 0 256 225 | w 0 256 226 | w 0 256 227 | w 0 256 228 | w 0 256 229 | w 0 256 230 | w 0 256 231 | w 0 256 232 | w 0 256 233 | w 0 256 234 | w 0 256 235 | w 0 256 236 | w 0 256 237 | w 0 256 238 | w 0 256 239 | w 0 256 240 | w 0 256 241 | w 0 256 242 | w 0 256 243 | w 0 256 244 | w 0 256 245 | w 0 256 246 | w 0 256 247 | w 0 256 248 | w 0 256 249 | w 0 256 250 | w 0 256 251 | w 0 256 252 | w 0 256 253 | w 0 256 254 | w 0 256 255 | w 0 256 256 | w 0 256 257 | w 0 256 258 | w 0 256 259 | w 0 256 260 | w 0 256 261 | w 0 256 262 | w 0 256 263 | w 0 256 264 | w 0 256 265 | w 0 256 266 | w 0 256 267 | w 0 256 268 | w 0 256 269 | w 0 256 270 | w 0 256 271 | w 0 256 272 | w 0 256 273 | w 0 256 274 | w 0 256 275 | w 0 256 276 | w 0 256 277 | w 0 256 278 | w 0 256 279 | w 0 256 280 | w 0 256 281 | w 0 256 282 | w 0 256 283 | w 0 256 284 | w 0 256 285 | w 0 256 286 | w 0 256 287 | w 0 256 288 | w 0 256 289 | w 0 256 290 | w 0 256 291 | w 0 256 292 | c 0 293 | o 64 10 /qwer/asdf 294 | w 0 65535 295 | c 0 296 | o 64 10 /qwer/qwer 297 | w 0 65535 298 | c 0 299 | d 10 /qwer/qwer 300 | d 10 /qwer/asdf 301 | d 10 /qwer/qwer 302 | d 10 /qwer/zxcv 303 | o 64 10 /qwer/asdf 304 | w 0 65535 305 | c 0 306 | o 64 10 /qwer/qwer 307 | w 0 65535 308 | c 0 309 | d 10 /qwer/qwer 310 | d 10 /qwer/asdf 311 | o 64 10 /qwer/asdf 312 | w 0 65535 313 | c 0 314 | o 64 10 /qwer/qwer 315 | w 0 65535 316 | c 0 317 | d 10 /qwer/qwer 318 | d 10 /qwer/asdf 319 | o 64 10 /qwer/asdf 320 | w 0 65535 321 | c 0 322 | o 64 10 /qwer/qwer 323 | w 0 65535 324 | c 0 325 | d 10 /qwer/qwer 326 | d 10 /qwer/asdf 327 | o 64 10 /qwer/asdf 328 | w 0 65535 329 | c 0 330 | o 64 10 /qwer/qwer 331 | w 0 65535 332 | c 0 333 | d 10 /qwer/qwer 334 | d 10 /qwer/asdf 335 | -------------------------------------------------------------------------------- /afl-tests/full2: -------------------------------------------------------------------------------- 1 | m 5 /qwer 2 | o 64 10 /qwer/lkjh 3 | w 0 65535 4 | c 0 5 | o 64 10 /qwer/vcxz 6 | w 0 65535 7 | c 0 8 | o 64 10 /qwer/fdsa 9 | w 0 65535 10 | c 0 11 | o 64 10 /qwer/rewq 12 | d 10 /qwer/lkjh 13 | w 0 65535 14 | d 10 /qwer/vcxz 15 | c 0 16 | o 64 10 /qwer/hjkl 17 | w 0 65535 18 | c 0 19 | o 64 10 /qwer/zxcv 20 | d 10 /qwer/hjkl 21 | w 0 65535 22 | c 0 23 | o 64 10 /qwer/asdf 24 | w 0 65535 25 | c 0 26 | o 64 10 /qwer/qwer 27 | w 0 65535 28 | c 0 29 | d 10 /qwer/asdf 30 | d 10 /qwer/qwer 31 | d 10 /qwer/zxcv 32 | o 64 10 /qwer/asdf 33 | w 0 65535 34 | c 0 35 | o 64 10 /qwer/qwer 36 | w 0 65535 37 | c 0 38 | d 10 /qwer/qwer 39 | d 10 /qwer/asdf 40 | d 10 /qwer/qwer 41 | d 10 /qwer/zxcv 42 | o 64 10 /qwer/asdf 43 | w 0 65535 44 | c 0 45 | o 64 10 /qwer/qwer 46 | w 0 65535 47 | c 0 48 | d 10 /qwer/qwer 49 | d 10 /qwer/asdf 50 | o 64 10 /qwer/asdf 51 | w 0 65535 52 | c 0 53 | o 64 10 /qwer/qwer 54 | w 0 65535 55 | c 0 56 | d 10 /qwer/qwer 57 | d 10 /qwer/asdf 58 | o 64 10 /qwer/asdf 59 | w 0 65535 60 | c 0 61 | o 64 10 /qwer/qwer 62 | w 0 65535 63 | c 0 64 | d 10 /qwer/qwer 65 | d 10 /qwer/asdf 66 | o 64 10 /qwer/asdf 67 | w 0 65535 68 | c 0 69 | o 64 10 /qwer/qwer 70 | w 0 65535 71 | c 0 72 | d 10 /qwer/qwer 73 | d 10 /qwer/asdf 74 | -------------------------------------------------------------------------------- /afl-tests/med: -------------------------------------------------------------------------------- 1 | m 5 /qwer 2 | o 64 10 /qwer/qwer 3 | w 0 256 4 | c 0 5 | t 128 10 /qwer/qwer 6 | o 0 10 /qwer/qwer 7 | s 0 2 0 8 | w 0 1 9 | s 0 0 0 10 | r 0 10 11 | c 0 12 | d 10 /qwer/qwer 13 | -------------------------------------------------------------------------------- /afl-tests/simple: -------------------------------------------------------------------------------- 1 | m 5 /asdf 2 | m 5 /qwer 3 | m 5 /zxcv 4 | x 5 /asdf 5 | o 64 10 /qwer/qwer 6 | w 0 8 7 | c 0 8 | t 3 10 /qwer/qwer 9 | o 0 10 /qwer/qwer 10 | s 0 2 0 11 | w 0 1 12 | s 0 0 0 13 | r 0 10 14 | c 0 15 | d 10 /qwer/qwer -------------------------------------------------------------------------------- /afl.c: -------------------------------------------------------------------------------- 1 | /* 2 | afl.c - reads stdin to execute script on in-RAM stfs and dump the result into test.img 3 | commands are 4 | m - mkdir 5 | x - rmdir 6 | o 0|64 - open 64=O_CREAT 7 | w - write 8 | r - read 9 | s - seek 10 | c - close 11 | t - truncate 12 | d - unlink 13 | p - reset 14 | # - comment 15 | 16 | is always length prefixed, space-separated, e.g.: 17 | m 5 /root 18 | ^ path 19 | ^ length of path 20 | 21 | ./afl is also used by stfsfuzz.py, if you want to use it with afl, 22 | you should compile without logging and dumping of the fs image. 23 | 24 | */ 25 | #include "stfs.h" 26 | #include 27 | #include 28 | 29 | void dump(uint8_t *src, uint32_t len); 30 | 31 | /* 32 | directory ops 33 | m path 34 | int stfs_mkdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path); 35 | ctx=l path 36 | int opendir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path, ReaddirCTX *ctx); 37 | inode=n ctx 38 | const Inode_t* readdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], ReaddirCTX *ctx); 39 | x path 40 | int stfs_rmdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path); 41 | file ops 42 | fd=o path flags 43 | int stfs_open(uint8_t *path, int oflag, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 44 | w fd buf size 45 | ssize_t stfs_write(int fildes, const void *buf, size_t nbyte, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 46 | r fd buf size 47 | ssize_t stfs_read(int fildes, void *buf, size_t nbyte, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 48 | s off whence 49 | off_t stfs_lseek(int fildes, off_t offset, int whence); 50 | c fd 51 | int stfs_close(int fildes, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 52 | d path 53 | int stfs_unlink(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path); 54 | t path size 55 | int stfs_truncate(uint8_t *path, int length, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 56 | 57 | 58 | m path 59 | ctx=l path 60 | inode=n ctx 61 | x path 62 | fd=o path flags 63 | w fd buf 64 | r fd size 65 | s fd off whence 66 | c fd 67 | d path 68 | t path size 69 | */ 70 | 71 | #include 72 | #include 73 | #include 74 | 75 | void handler(int signo) { 76 | exit(1); 77 | } 78 | 79 | Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]; 80 | int _main(void) { 81 | printf("AFL test harness\n"); 82 | memset(&blocks,0xff,sizeof(blocks)); 83 | 84 | struct sigaction sa; 85 | 86 | sa.sa_handler = handler; 87 | sigemptyset(&sa.sa_mask); 88 | sa.sa_flags = 0; 89 | sigaction(SIGALRM, &sa, NULL); 90 | 91 | if(stfs_init(blocks)==-1) { 92 | return 0; 93 | }; 94 | 95 | uint8_t cmd; 96 | int cmd_size; 97 | while (1) { 98 | //alarm(1); 99 | if(fscanf(stdin,"%c ", &cmd)!=1) return 0; 100 | //alarm(0); 101 | switch(cmd) { 102 | case('m'): { /*mkdir path*/ 103 | //alarm(1); 104 | if(fscanf(stdin,"%d ", &cmd_size)!=1) return 0; 105 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 106 | uint8_t path[cmd_size+1]; 107 | int n; 108 | if((n=fread(path, 1, cmd_size, stdin))!=cmd_size) return 0; 109 | //alarm(0); 110 | path[n]=0; 111 | fprintf(stderr,"mkdir %s returns: %d\n", path, stfs_mkdir(blocks, path)); 112 | break; 113 | }; 114 | case('l'): { /*ctx=opendir path*/ }; 115 | case('n'): { /*readdir ctx*/ }; 116 | case('x'): { /*rmdir path*/ 117 | //alarm(1); 118 | if(fscanf(stdin,"%d ", &cmd_size)!=1) return 0; 119 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 120 | uint8_t path[cmd_size+1]; 121 | int n; 122 | if((n=fread(path, 1, cmd_size, stdin))!=cmd_size) return 0; 123 | //alarm(0); 124 | path[n]=0; 125 | fprintf(stderr,"rmdir '%s' returns: %d\n", path, stfs_rmdir(blocks, path)); 126 | break; 127 | }; 128 | case('o'): { /*open flags path */ 129 | int oflags; 130 | //alarm(1); 131 | if(fscanf(stdin, "%d ", &oflags)!=1) return 0; 132 | if(fscanf(stdin,"%d ", &cmd_size)!=1) return 0; 133 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 134 | uint8_t path[cmd_size+1]; 135 | int n; 136 | if((n=fread(path, 1, cmd_size, stdin))!=cmd_size) return 0; 137 | //alarm(0); 138 | path[n]=0; 139 | fprintf(stderr,"open '%s' %d returns: %d\n", path, oflags, stfs_open(path, oflags, blocks)); 140 | break; 141 | }; 142 | case('w'): { /*write fd buf*/ 143 | int fd; 144 | //alarm(1); 145 | if(fscanf(stdin, "%d ", &fd)!=1) return 0; 146 | if(fscanf(stdin,"%d ", &cmd_size)!=1) return 0; 147 | //alarm(0); 148 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 149 | uint8_t buf[cmd_size+1]; 150 | int n; 151 | for(n=0;n %d returns: %d\n", cmd_size, fd, stfs_write(fd, buf, cmd_size, blocks)); 153 | break; 154 | }; 155 | case('r'): { /*read fd size*/ 156 | int fd; 157 | //alarm(1); 158 | if(fscanf(stdin, "%d ", &fd)!=1) return 0; 159 | if(fscanf(stdin, "%d", &cmd_size)!=1) return 0; 160 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 161 | //alarm(0); 162 | uint8_t buf[cmd_size+1]; 163 | int ret; 164 | fprintf(stderr,"read %dB from %d returns: %d\n", cmd_size, fd, (ret=stfs_read(fd, buf, cmd_size, blocks))); 165 | //if(ret>0) dump(buf,ret); 166 | break; 167 | } 168 | case('s'): { /*seek fd off whence*/ 169 | int fd, off, whence; 170 | //alarm(1); 171 | if(fscanf(stdin, "%d %d %d", &fd, &off, &whence)!=3) return 0; 172 | //alarm(0); 173 | fprintf(stderr,"seek %d %d %d returns: %d\n", fd, off, whence, stfs_lseek(fd, off, whence)); 174 | break; 175 | }; 176 | case('c'): { /*close fd */ 177 | int fd; 178 | //alarm(1); 179 | if(fscanf(stdin, "%d", &fd)!=1) return 0; 180 | //alarm(0); 181 | fprintf(stderr,"close %d returns: %d\n", fd, stfs_close(fd, blocks)); 182 | break; 183 | }; 184 | case('d'): { /*unlink path */ 185 | //alarm(1); 186 | if(fscanf(stdin,"%d ", &cmd_size)!=1) return 0; 187 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 188 | uint8_t path[cmd_size+1]; 189 | int n; 190 | if((n=fread(path, 1, cmd_size, stdin))!=cmd_size) return 0; 191 | //alarm(0); 192 | path[n]=0; 193 | fprintf(stderr,"unlink '%s' returns: %d\n", path, stfs_unlink(blocks, path)); 194 | break; 195 | }; 196 | case('t'): { /*truncate size path */ 197 | int size; 198 | //alarm(1); 199 | if(fscanf(stdin, "%d ", &size)!=1) return 0; 200 | if(fscanf(stdin,"%d ", &cmd_size)!=1) return 0; 201 | if(cmd_size>1024*1024 || cmd_size<0) return 0; 202 | uint8_t path[cmd_size+1]; 203 | int n; 204 | if((n=fread(path, 1, cmd_size, stdin))!=cmd_size) return 0; 205 | //alarm(0); 206 | path[n]=0; 207 | fprintf(stderr,"truncate %d '%s' returns: %d\n", size, path, stfs_truncate(path, size, blocks)); 208 | break; 209 | }; 210 | case('p'): { 211 | if(stfs_init(blocks)==-1) { 212 | return 0; 213 | }; 214 | fprintf(stderr,"reset device\n"); 215 | break; 216 | } 217 | case('#'): { 218 | while(getchar()!='\n'); 219 | break; 220 | } 221 | case('\n'): break; 222 | default: return 0; 223 | } 224 | } 225 | 226 | return 0; 227 | } 228 | 229 | void dump_info(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 230 | 231 | int main(void) { 232 | _main(); 233 | dump_info(blocks); 234 | int fd; 235 | fd=open("test.img", O_RDWR | O_CREAT | O_TRUNC, 0666 ); 236 | fprintf(stderr, "[i] dumping fs to fd %d\n", fd); 237 | write(fd,blocks, sizeof(blocks)); 238 | close(fd); 239 | return 0; 240 | } 241 | -------------------------------------------------------------------------------- /anaimg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from binascii import hexlify 5 | from stfs import Chunk 6 | 7 | dirs=set(['']) 8 | files=set() 9 | objects = {1: {'oid': 1, 'path': '/'}} 10 | empty=[0,0,0,0,0] 11 | used=[0,0,0,0,0] 12 | deleted=[0,0,0,0,0] 13 | 14 | def getimg(): 15 | with open(sys.argv[1], 'r') as fd: 16 | return fd.read() 17 | 18 | img = getimg() 19 | 20 | def split_by_n( seq, n ): 21 | """A generator to divide a sequence into chunks of n units. 22 | src: http://stackoverflow.com/questions/9475241/split-python-string-every-nth-character""" 23 | while seq: 24 | yield seq[:n] 25 | seq = seq[n:] 26 | 27 | def dump_chunks(prev): 28 | if prev[0]=='d': 29 | print "data\t%3d blocks of %d [%s..%s]" % (len(prev[2]), prev[1], str(prev[2][:3])[1:-1], str(prev[2][-3:])[1:-1]) 30 | elif prev[0]=='e': 31 | print "empty\t%3d blocks" % (prev[1]) 32 | elif prev[0]=='x': 33 | print "deleted\t%3d blocks" % (prev[1]) 34 | 35 | for b, block in enumerate(split_by_n(img,128*1024)): 36 | print "[i] block", b 37 | prev = None 38 | for c, raw in enumerate(split_by_n(block,128)): 39 | chunk=Chunk.parse(raw) 40 | if chunk.type == "Data": 41 | used[b]+=1 42 | if chunk.node.dnode.oid not in objects: 43 | objects[chunk.node.dnode.oid]={'seq': [chunk.node.dnode.seq,], 44 | 'oid': chunk.node.dnode.oid} 45 | else: 46 | if chunk.node.dnode.seq in objects[chunk.node.dnode.oid]['seq']: 47 | print "[x] already seen seq", chunk.node.dnode.seq 48 | else: 49 | objects[chunk.node.dnode.oid]['seq'].append(chunk.node.dnode.seq) 50 | if prev and prev[0]=='d' and prev[1]==chunk.node.dnode.oid: 51 | prev[2].append(chunk.node.dnode.seq) 52 | else: 53 | if prev: dump_chunks(prev) 54 | prev = ('d', chunk.node.dnode.oid, [chunk.node.dnode.seq]) 55 | elif chunk.type == "Inode": 56 | used[b]+=1 57 | name = "" 58 | if chunk.node.inode.oid not in objects: 59 | nsize = chunk.node.inode.bits.name_len 60 | name = chunk.node.inode.name[:nsize] 61 | objects[chunk.node.inode.oid]={'seq': [], 62 | 'oid': chunk.node.inode.oid, 63 | 'type': chunk.node.inode.bits.type, 64 | 'parent': chunk.node.inode.parent, 65 | 'size': chunk.node.inode.size, 66 | 'name': name} 67 | else: 68 | if 'parent' in objects[chunk.node.inode.oid]: 69 | print "[x] double inode %s", chunk.node.inode 70 | continue 71 | else: 72 | nsize = chunk.node.inode.bits.name_len 73 | name = chunk.node.inode.name[:nsize] 74 | objects[chunk.node.inode.oid]['type']=chunk.node.inode.bits.type 75 | objects[chunk.node.inode.oid]['parent']=chunk.node.inode.parent 76 | objects[chunk.node.inode.oid]['size']=chunk.node.inode.size 77 | objects[chunk.node.inode.oid]['name']=name 78 | 79 | if prev: 80 | dump_chunks(prev) 81 | prev = None 82 | print "inode:\t%s '%s' inode(%d) %dB parent: %d" % ( 83 | "File" if (chunk.node.inode.bits.type==1) else "Directory", 84 | name, 85 | chunk.node.inode.oid, 86 | chunk.node.inode.size, 87 | chunk.node.inode.parent) 88 | prev = None 89 | elif chunk.type == "Empty": 90 | empty[b]+=1 91 | if prev and prev[0]=='e': 92 | prev[1]+=1 93 | else: 94 | if prev: dump_chunks(prev) 95 | prev = ['e', 1] 96 | elif chunk.type == "Deleted": 97 | deleted[b]+=1 98 | if prev and prev[0]=='x': 99 | prev[1]+=1 100 | else: 101 | if prev: dump_chunks(prev) 102 | prev = ['x', 1] 103 | if prev: 104 | dump_chunks(prev) 105 | 106 | ls = [] 107 | for oid, obj in objects.items(): 108 | if not 'name' in obj: 109 | if obj['oid']!=1: 110 | print "orphan\t%3d blocks (%dB) of %x [%s..%s]" % ( 111 | len(obj['seq']), 112 | len(obj['seq'])*121, 113 | obj['oid'], 114 | str(obj['seq'][:3])[1:-1], 115 | str(obj['seq'][-3:])[1:-1]) 116 | continue 117 | path=['/'+obj['name']] 118 | o=objects.get(obj['parent']) 119 | while o and o['oid']!=1: 120 | path.append('/'+o['name']) 121 | o=objects.get(o['parent']) 122 | obj['path']=''.join(reversed(path)) 123 | if not o or o['oid']!=1: 124 | print "dangling object", obj['path'], hex(obj.get('parent')) 125 | # check if seq list could be valid 126 | #for i, seq in enumerate(sorted(obj['seq'])): 127 | # if i!=seq: 128 | # print "missing chunk %d, from" % i, obj 129 | if obj['type']==1: # files 130 | if (obj['size']/121)+1>len(obj['seq']) and obj['size']%121!=0: 131 | print "[x] only %d chunks (%d B) for %d bytes - %s" % (len(obj['seq']), len(obj['seq'])*121, obj['size'], obj['path']) 132 | print obj 133 | elif (obj['size']&1 && ./stfs | less` */ 3 | /* for how to use see main() */ 4 | 5 | /* 6 | stfs is a log-structured appending file system for embedded flash 7 | devices, that expose the flash mapped as memory. 8 | 9 | - for embedded flash in cortex-m3, where you have only a few 10 | 4KB-128KB ereasable blocks. (this implementation only supports 11 | blocks all with the same size) 12 | 13 | - STFS provides only directories and files, but no other types like 14 | pipes, links, etc. 15 | 16 | - no file metadata like timestamps or access permissions. 17 | 18 | - filenames are max 32 bytes long, files max 64KB. 19 | 20 | - always reserves one empty block for vacuuming. 21 | 22 | - default blocksize is 128B with fs metadata included. unlike other 23 | flash filesystems where the blocksize is usually limited by 512B. 24 | 25 | - single threaded 26 | 27 | data structure 28 | 29 | chunk_size = 128 30 | chunks_per_block = 1024 31 | 32 | 4 different chunk types are used: 33 | 34 | empty = 0xff (1B) irrelevant(all 0xff) (127B) 35 | inode (128B)- contain file meta information 36 | - chunktype (0xAA) (1B) 37 | - directory | file (1b) 38 | - size (2B) 39 | - parent_directory_obj_id (4B) 40 | - obj_id (4B) 41 | - name_len (6b) 42 | - name (32B) 43 | - data (84B) 44 | data (7B) - contain data 45 | - chunktype (0xCC) (1B) 46 | - seq_id (2B) 47 | - obj_id (4B) 48 | - data blob (chunksize-metasize) 49 | deleted = 0x00 (1B) irrelevant(all 0x00) (127B) 50 | 51 | inode with oid 1 is the root directory and virtual 52 | 53 | */ 54 | 55 | #include 56 | #include // mem* 57 | #include // printf 58 | #include // random 59 | // todo delme #include 60 | #include 61 | 62 | #include "stfs.h" 63 | 64 | #define OID_BLOCK_SIZE (CHUNKS_PER_BLOCK * (NBLOCKS - 1) + MAX_OPEN_FILES + 3) 65 | #define OID_START_OFFSET 1 66 | 67 | #define VALIDFD(fd) if(validfd(fd)!=0) return -1; 68 | 69 | #ifdef DEBUG_LEVEL 70 | #define LOG(level, ...) if(DEBUG_LEVEL>=level) fprintf(stderr, ##__VA_ARGS__) 71 | #else 72 | #define LOG(level, ...) 73 | #endif // DEBUG_LEVEL 74 | 75 | static STFS_File fdesc[MAX_OPEN_FILES]; 76 | static uint32_t errno; 77 | static uint32_t reserved_block; 78 | static uint32_t current_oid_offset = OID_START_OFFSET; 79 | 80 | void dump(uint8_t *src, uint32_t len) { 81 | uint32_t i,j; 82 | for(i=0;iname_len>32 || inode->name_len<1) { 93 | printf("[x] inode has invalid name size: %d\n", inode->name_len); 94 | printf("[i] chunk: %s inode(%d) %dB parent: %x\n", 95 | (inode->type==File)?"File":"Directory", 96 | inode->oid, 97 | inode->size, 98 | inode->parent 99 | ); 100 | return; 101 | } 102 | memcpy(tmpname,inode->name, inode->name_len); 103 | tmpname[inode->name_len]=0; 104 | printf("[i] chunk: %s %s inode(%d) %dB parent: %x\n", 105 | (inode->type==File)?"File":"Directory", 106 | tmpname, 107 | inode->oid, 108 | inode->size, 109 | inode->parent 110 | ); 111 | } 112 | 113 | void dump_chunk(Chunk *chunk) { 114 | switch(chunk->type) { 115 | case(Empty): { printf("[i] chunk: empty\n"); break; } 116 | case(Deleted): { printf("[i] chunk: deleted\n"); break; } 117 | case(Data): { 118 | printf("[i] chunk: data %d %d\n", chunk->data.oid, chunk->data.seq); 119 | dump(chunk->data.data,DATA_PER_CHUNK); 120 | break; 121 | } 122 | case(Inode): { 123 | dump_inode(&chunk->inode); 124 | break; 125 | } 126 | } 127 | } 128 | 129 | static int validfd(uint32_t fildes) { 130 | if(fildes>=MAX_OPEN_FILES) { 131 | // fail invalid fildes 132 | LOG(1, "[x] invalid fd, %d\n", fildes); 133 | errno = E_INVFD; 134 | return -1; 135 | } 136 | if(fdesc[fildes].free!=0) { 137 | // fail not open 138 | LOG(1, "[x] unused fd, %d\n", fildes); 139 | errno = E_NOTOPEN; 140 | return -1; 141 | } 142 | return 0; 143 | } 144 | 145 | static const Chunk* find_inode_by_parent_fname(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], 146 | const uint32_t parent, 147 | const uint8_t* fname, 148 | uint32_t *block, uint32_t *chunk) { 149 | LOG(3, "[i] find_inode_by_parent_fname %x %s %d %d\n", parent, fname, *block, *chunk); 150 | uint32_t b; 151 | const uint32_t fsize=strlen((const char*) fname); 152 | for(b=0;b32) { 226 | // directory name size is <0 or >32 227 | LOG(1, "[x] directory name size is <0 or >32\n"); 228 | path[i]='/'; // restore path 229 | errno = E_NAMESIZE; 230 | return 0; 231 | } 232 | LOG(3, "[i] looking for child named: %s\n", ptr); 233 | if(find_inode_by_parent_fname(blocks, parent, ptr,b,c)==NULL) { 234 | // fail no such directory 235 | //printf("[x] find inode by parent/fname"); 236 | path[i]='/'; // restore path 237 | errno = E_NOTFOUND; 238 | return 0; 239 | } 240 | parent=blocks[*b][*c].inode.oid; 241 | path[i]='/'; // restore path 242 | ptr=&path[i+1]; // advance start ptr 243 | } 244 | } 245 | 246 | const uint32_t psize = strlen((char*) ptr); 247 | if(psize==0 || psize>32) { 248 | // directory name size is <0 or >32 249 | errno = E_NAMESIZE; 250 | return 0; 251 | } 252 | if(find_inode_by_parent_fname(blocks, parent, ptr,b,c)==NULL) { 253 | // fail no such directory 254 | errno = E_NOTFOUND; 255 | return 0; 256 | } 257 | return blocks[*b][*c].inode.oid; 258 | } 259 | 260 | static int write_chunk(void *dst, void *src, uint32_t size) { 261 | if(size!=sizeof(Chunk)) { 262 | LOG(1, "[x] Bad chunk size: %d\n", size); 263 | errno = E_BADCHUNK; 264 | return -1; 265 | } 266 | memcpy(dst, src, size); 267 | return 0; 268 | } 269 | 270 | int vacuum(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 271 | uint32_t i, b,c, candidate_reclaim=0, used[NBLOCKS], unused[NBLOCKS], deleted[NBLOCKS]; 272 | int candidate=-1; 273 | for(i=0;icandidate_reclaim) { 285 | LOG(1, "[i] old, new can: %d %d (%d>%d)\n", candidate, b, (unused[b]+deleted[b]),candidate_reclaim); 286 | candidate=b; 287 | candidate_reclaim=(unused[b]+deleted[b]); 288 | } else if((unused[b]+deleted[b])>(candidate_reclaim*9)/10 && random()%4==0) { 289 | LOG(1, "[i] lucky old, new can: %d %d (%d>%d)\n", candidate, b, (unused[b]+deleted[b]),candidate_reclaim); 290 | candidate=b; 291 | candidate_reclaim=(unused[b]+deleted[b]); 292 | } 293 | } 294 | for(b=0;b=NBLOCKS) { 304 | // fail 305 | LOG(1, "[x] vacuum invalid block: %d candidate: %d\n", reserved_block, candidate); 306 | errno = E_VAC; 307 | return -1; 308 | } 309 | LOG(2, "[i] vacuuming from %d to %d\n", candidate, reserved_block); 310 | i=0; 311 | for(c=0;coid=oid; 406 | ctx->block=0; 407 | ctx->chunk=0; 408 | return 0; 409 | } 410 | 411 | const Inode_t* readdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], ReaddirCTX *ctx) { 412 | const Chunk *chunk=find_chunk(blocks, Inode, 0, ctx->oid, 0, &(ctx->block), &(ctx->chunk)); 413 | if(chunk==NULL) return NULL; 414 | if(ctx->chunk+1>=CHUNKS_PER_BLOCK) { 415 | ctx->block++; 416 | ctx->chunk=0; 417 | } else { 418 | ctx->chunk++; 419 | } 420 | return &chunk->inode; 421 | } 422 | 423 | static uint8_t* split_path(uint8_t *path) { 424 | uint32_t i; 425 | uint8_t* ptr=NULL; 426 | for(i=0;path[i]!=0 && i<0xffffffff;i++) { 427 | if(path[i]=='/') ptr=&(path[i]); 428 | } 429 | if(ptr==NULL) { 430 | // fail, not an absolute path 431 | errno = E_RELPATH; 432 | return NULL; 433 | } 434 | *ptr=0; // terminate path 435 | return ptr+1; 436 | } 437 | 438 | static int create_obj(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path, Chunk *chunk) { 439 | int ret=0; 440 | uint8_t *fname=split_path(path); 441 | if(fname==NULL) { 442 | errno=E_INVNAME; 443 | return -1; 444 | } 445 | if(chunk==NULL) { 446 | errno=E_NOCHUNK; 447 | ret=-1; 448 | goto exit; 449 | } 450 | if(memcmp(fname, "..", 3)==0 || 451 | memcmp(fname, ".", 2)==0 || 452 | fname[0]==0) { 453 | errno = E_INVNAME; 454 | ret=-1; 455 | goto exit; 456 | } 457 | 458 | //printf("[i] split path: '%s' fname: '%s'\n", path, fname); 459 | uint32_t b=0,c=0; 460 | const uint32_t parent=oid_by_path(blocks, path,&b,&c); 461 | if(parent==0) { 462 | LOG(1, "[x] '%s' not found by oid\n", path); 463 | // fail no such directory 464 | errno = E_NOTFOUND; 465 | ret=-1; 466 | goto exit; 467 | } 468 | // check if parent is a directory 469 | if(parent!=1 && blocks[b][c].inode.type!=Directory) { 470 | // parent is a file 471 | errno = E_WRONGOBJ; 472 | ret=-1; 473 | goto exit; 474 | } 475 | 476 | // check if object already exists 477 | ReaddirCTX ctx={.oid=parent,.block=0,.chunk=0}; 478 | const Inode_t *inode; 479 | const uint32_t fsize=strlen((char*) fname); 480 | while((inode=readdir(blocks, &ctx))!=0) { 481 | if(fsize==inode->name_len && 482 | memcmp(inode->name, fname, inode->name_len)==0) { 483 | // fail parent has already a child named fname 484 | LOG(1, "[x] '%s' has already a child %s\n", path, fname); 485 | errno = E_EXISTS; 486 | ret=-1; 487 | goto exit; 488 | } 489 | } 490 | 491 | LOG(3, "[i] parent inode: %x\n", parent); 492 | const uint32_t nsize = strlen((char*) fname); 493 | if(nsize>32 || nsize <1) { 494 | // fail name not valid size 495 | LOG(1, "invalid fname size\n"); 496 | errno = E_NAMESIZE; 497 | ret=-1; 498 | goto exit; 499 | } 500 | 501 | chunk->inode.parent=parent; 502 | chunk->inode.name_len=nsize; 503 | memcpy(chunk->inode.name, fname, nsize); 504 | exit: 505 | fname[-1]='/'; // recover from split_path 506 | return ret; 507 | } 508 | 509 | int stfs_mkdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path) { 510 | LOG(2, "[x] mkdir %s\n", path); 511 | 512 | Chunk chunk; 513 | memset(&chunk,0,sizeof(chunk)); 514 | chunk.type=Inode; 515 | chunk.inode.type=Directory; 516 | chunk.inode.size=0; 517 | chunk.inode.oid=new_oid(blocks); 518 | //printf("[i] new oid: 0x%x\n", chunk.inode.oid); 519 | 520 | if(create_obj(blocks, path, &chunk)==-1) { 521 | // fail 522 | LOG(1, "[x] create obj failed\n"); 523 | return -1; 524 | } 525 | 526 | // store chunk 527 | if(store_chunk(blocks, &chunk)==-1) { 528 | // fail to store chunk 529 | LOG(1, "failed to store chunk\n"); 530 | return -1; 531 | } 532 | return 0; 533 | } 534 | 535 | int stfs_rmdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path) { 536 | uint32_t b=0, c=0; 537 | const uint32_t self=oid_by_path(blocks, path, &b, &c); 538 | if(self==0) { 539 | LOG(1, "[x] path doesn't exist '%s'\n", path); 540 | // fail no such directory 541 | return -1; 542 | } 543 | if(self==1) { 544 | LOG(1, "[x] can't delete /\n"); 545 | // fail no such directory 546 | errno = E_DELROOT; 547 | return -1; 548 | } 549 | // check if self is indeed a directory 550 | if(blocks[b][c].type==Inode && blocks[b][c].inode.type!=Directory) { 551 | // fail 552 | LOG(1, "[x] path '%s' is not a directory\n", path); 553 | return -1; 554 | } 555 | 556 | // check if directory is empty 557 | ReaddirCTX ctx={.oid=self, .block=0, .chunk=0}; 558 | if(readdir(blocks, &ctx)!=0) { 559 | // fail directory is not empty 560 | LOG(1, "[x] directory '%s' is not empty\n", path); 561 | return -1; 562 | } 563 | 564 | // del chunk 565 | del_chunk(blocks, b, c); 566 | return 0; 567 | } 568 | 569 | int stfs_geterrno(void) { 570 | return errno; 571 | } 572 | 573 | int stfs_open(uint8_t *path, uint32_t oflag, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 574 | // oflag maybe: O_RDONLY O_RDWR O_WRONLY O_SYNC(caching?) O_EXCL 575 | // oflags: O_APPEND O_CREAT O_TRUNC(seek) 576 | 577 | // find free fdesc 578 | uint32_t fd; 579 | for(fd=0;fd=MAX_OPEN_FILES) { 583 | // fail no free file descriptors available 584 | errno = E_NOFDS; 585 | return -1; 586 | } 587 | 588 | memset(&fdesc[fd], 0xff, sizeof(STFS_File)); 589 | 590 | if(oflag == O_CREAT) { 591 | // create file 592 | 593 | // check if file doesn't exist 594 | uint32_t b=0, c=0; 595 | const uint32_t self=oid_by_path(blocks, path, &b, &c); 596 | if(self!=0) { 597 | LOG(1, "[x] path already exists '%s'\n", path); 598 | // fail no such directory 599 | errno = E_EXISTS; 600 | return -1; 601 | } 602 | 603 | if(create_obj(blocks, path, &fdesc[fd].ichunk)==-1) { 604 | // fail 605 | LOG(1, "[x] create obj failed\n"); 606 | return -1; 607 | } 608 | uint32_t i; 609 | for(i=0;ifdesc[fildes].ichunk.inode.size) { 664 | // fail seek beyond eof 665 | LOG(1, "[x] cannot seek beyond eof set\n"); 666 | errno = E_NOSEEKEOF; 667 | return -1; 668 | } 669 | fdesc[fildes].fptr=newfptr; 670 | return newfptr; 671 | } 672 | 673 | uint32_t stfs_size(uint32_t fildes) { 674 | VALIDFD(fildes); 675 | return fdesc[fildes].ichunk.inode.size; 676 | } 677 | 678 | ssize_t stfs_write(uint32_t fildes, const void *buf, size_t nbyte, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 679 | // check if fildes is valid 680 | // before writing a chunk check if it changed 681 | // update inode if neccessary 682 | if(nbyte<1) return 0; 683 | if(buf==NULL) return 0; 684 | VALIDFD(fildes) 685 | if(fdesc[fildes].fptr+nbyte>MAX_FILE_SIZE) { 686 | // fail too big 687 | LOG(1, "[x] too big, %d\n", fdesc[fildes].fptr+nbyte); 688 | errno = E_TOOBIG; 689 | nbyte=MAX_FILE_SIZE-fdesc[fildes].fptr; 690 | } 691 | 692 | if(fdesc[fildes].fptr>fdesc[fildes].ichunk.inode.size) { 693 | // due to lseek pointing behind eof there would be holes if we 694 | // write to this position. 695 | LOG(1, "[i] todo 0xff extend then append existing data\n"); 696 | LOG(1, "[x] only if seek allows it, but it won't\n"); 697 | errno = E_INVFP; 698 | return -1; 699 | } 700 | 701 | uint32_t written=0; 702 | if(fdesc[fildes].fptr<=fdesc[fildes].ichunk.inode.size) { 703 | // append to end of file 704 | uint32_t b,c; 705 | Chunk chunk; 706 | if(fdesc[fildes].fptrfdesc[fildes].ichunk.inode.size/DATA_PER_CHUNK) 713 | endseq = fdesc[fildes].ichunk.inode.size/DATA_PER_CHUNK; 714 | LOG(1,"[.] %d %d\n",startseq, endseq); 715 | uint32_t i; 716 | for(i=startseq;iDATA_PER_CHUNK-(fdesc[fildes].fptr+written)%DATA_PER_CHUNK)? 741 | DATA_PER_CHUNK-((fdesc[fildes].fptr+written)%DATA_PER_CHUNK): 742 | (nbyte-written)); 743 | if(find_chunk(blocks, Data, chunk.data.oid, 0, chunk.data.seq, &b, &c)!=NULL) { 744 | // found chunk, check if write is necessary, if so partial, or full? 745 | memcpy(chunk.data.data, &blocks[b][c].data.data, DATA_PER_CHUNK); 746 | memcpy(chunk.data.data+((fdesc[fildes].fptr+written)%DATA_PER_CHUNK), ((uint8_t*) buf)+written,towrite); 747 | uint32_t i; 748 | // can we update the chunk, or have to del,create a new one? 749 | for(i=0;iDATA_PER_CHUNK)?DATA_PER_CHUNK:(nbyte-written)); 770 | //dump_chunk(&chunk); 771 | if(store_chunk(blocks, &chunk)==-1) { 772 | // fail to store chunk 773 | LOG(1, "failed to store chunk\n"); 774 | goto exit; 775 | } 776 | written+=(nbyte-written>DATA_PER_CHUNK)?DATA_PER_CHUNK:(nbyte-written); 777 | } 778 | } 779 | } 780 | exit: 781 | // update inode 782 | if(written+fdesc[fildes].fptr>fdesc[fildes].ichunk.inode.size) { 783 | // file grows update inode 784 | fdesc[fildes].ichunk.inode.size=written+fdesc[fildes].fptr; 785 | } 786 | if(written>0) { 787 | fdesc[fildes].idirty=1; 788 | } 789 | 790 | fdesc[fildes].fptr+=written; 791 | 792 | return written; 793 | } 794 | 795 | ssize_t stfs_read(uint32_t fildes, void *buf, size_t nbyte, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 796 | if(nbyte<1) return 0; 797 | if(buf==NULL) return 0; 798 | VALIDFD(fildes) 799 | uint32_t read=0; 800 | uint32_t b,c; 801 | if(nbyte+fdesc[fildes].fptr>fdesc[fildes].ichunk.inode.size) { 802 | // read only as much there is available, not beyond eof 803 | nbyte=fdesc[fildes].ichunk.inode.size-fdesc[fildes].fptr; 804 | LOG(3, "[i] changed nbyte to %d, size is %d\n",nbyte, fdesc[fildes].ichunk.inode.size); 805 | } 806 | for(read=0;readdata.data+coff, ((nbyte-read>DATA_PER_CHUNK)?(DATA_PER_CHUNK-coff):(nbyte-read))); 817 | read+=((nbyte-read>(DATA_PER_CHUNK-coff))?(DATA_PER_CHUNK-coff):(nbyte-read)); 818 | } else { 819 | errno = E_NOCHUNK; 820 | return -1; 821 | } 822 | } 823 | fdesc[fildes].fptr+=read; 824 | return read; 825 | } 826 | 827 | static void del_chunks(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint32_t oid) { 828 | uint32_t b=0,c=0, n=0; 829 | const Chunk *chunk=find_chunk(blocks, Data, oid, 0, 0xffff, &b, &c); 830 | while(chunk) { 831 | del_chunk(blocks, b, c); 832 | b=c=0; 833 | chunk=find_chunk(blocks, Data, oid, 0,0xffff, &b, &c); 834 | n++; 835 | } 836 | LOG(3,"[i] deleted %d chunks from oid %x\n",n, oid); 837 | } 838 | 839 | int stfs_close(uint32_t fildes, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 840 | VALIDFD(fildes) 841 | 842 | if(fdesc[fildes].idirty!=0) { 843 | // check if path is valid 844 | uint32_t b=0,c=0; 845 | const Chunk *chunk; 846 | if(fdesc[fildes].ichunk.inode.parent!=1) { 847 | chunk=find_chunk(blocks, Inode, fdesc[fildes].ichunk.inode.parent, 0,0, &b, &c); 848 | while(chunk && chunk->inode.parent!=1) { 849 | b=c=0; 850 | chunk=find_chunk(blocks, Inode, chunk->inode.parent, 0,0, &b, &c); 851 | } 852 | if(!chunk) { 853 | LOG(1, "[x] null chunk while resolving path\n"); 854 | del_chunks(blocks, fdesc[fildes].ichunk.inode.oid); 855 | errno = E_DANGLE; 856 | return -1; 857 | } 858 | if(chunk->inode.type!=0) { 859 | LOG(1, "[x] invalid path\n"); 860 | del_chunks(blocks, fdesc[fildes].ichunk.inode.oid); 861 | errno = E_DANGLE; 862 | return -1; 863 | } 864 | if(chunk->inode.parent!=1) { 865 | LOG(1, "[x] while resolving path\n"); 866 | del_chunks(blocks, fdesc[fildes].ichunk.inode.oid); 867 | errno = E_DANGLE; 868 | return -1; 869 | } 870 | } 871 | // need to update inode chunk 872 | //LOG(3, "[i] tentatively updating inode\n"); 873 | b=c=0; 874 | chunk=find_chunk(blocks, Inode, fdesc[fildes].ichunk.inode.oid, 0,0, &b, &c); 875 | if(chunk==NULL || chunk->inode.type!=File) { // if inode is dir, then file 876 | // has been unlinked and a dir instead created 877 | // between open and close 878 | // inode has been deleted, also delete all chunks 879 | del_chunks(blocks, fdesc[fildes].ichunk.inode.oid); 880 | } else if(memcmp(chunk,&fdesc[fildes].ichunk, sizeof(*chunk))!=0) { 881 | // invalidate old chunk 882 | LOG(3, "[i] deleting old inode at %d %d\n", b, c); 883 | del_chunk(blocks, b, c); 884 | // write new chunk 885 | store_chunk(blocks, &fdesc[fildes].ichunk); 886 | } 887 | } 888 | 889 | fdesc[fildes].free=1; 890 | fdesc[fildes].idirty=0; 891 | fdesc[fildes].fptr=0; 892 | 893 | return 0; 894 | } 895 | 896 | int stfs_unlink(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path) { 897 | uint32_t b=0, c=0; 898 | const uint32_t self=oid_by_path(blocks, path, &b, &c); 899 | if(self==0) { 900 | LOG(1, "[x] path doesn't exist '%s'\n", path); 901 | // fail no such file 902 | errno = E_NOTFOUND; 903 | return -1; 904 | } 905 | if(self==1 || blocks[b][c].inode.type!=File) { 906 | LOG(1, "[x] cannot unlink directory '%s'\n", path); 907 | errno = E_OPEN; 908 | // fail no such file 909 | return -1; 910 | } 911 | // check if self is indeed a file 912 | if(blocks[b][c].type==Inode && blocks[b][c].inode.type!=File) { 913 | // fail 914 | LOG(1, "[x] path '%s' is not a File\n", path); 915 | errno = E_WRONGOBJ; 916 | return -1; 917 | } 918 | 919 | uint32_t oid=blocks[b][c].inode.oid; 920 | 921 | // del inode chunk 922 | LOG(3, "[i] deleting inode chunk %d %d\n", b,c); 923 | del_chunk(blocks, b, c); 924 | 925 | // del data chunks 926 | const Chunk*chunk; 927 | b=c=0; 928 | while((chunk=find_chunk(blocks, Data, oid, 0, 0xffff, &b, &c))!=NULL) { 929 | LOG(3, "[i] deleting data chunk %d %d\n", b,c); 930 | del_chunk(blocks, b, c); 931 | } 932 | return 0; 933 | } 934 | 935 | int stfs_truncate(uint8_t *path, uint32_t length, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 936 | LOG(2, "[i] truncating '%s' to %d\n", path, length); 937 | uint32_t b=0, c=0; 938 | const uint32_t self=oid_by_path(blocks, path, &b, &c); 939 | if(self==0) { 940 | LOG(1, "[x] path doesn't exist '%s'\n", path); 941 | // fail no such file 942 | errno = E_NOTFOUND; 943 | return -1; 944 | } 945 | if(self==1 || blocks[b][c].inode.type!=File) { 946 | LOG(1, "[x] cannot truncate directory '%s'\n", path); 947 | errno = E_OPEN; 948 | // fail no such file 949 | return -1; 950 | } 951 | // check if self is indeed a file 952 | if(blocks[b][c].type==Inode && blocks[b][c].inode.type!=File) { 953 | // fail 954 | LOG(1, "[x] path '%s' is not a File\n", path); 955 | errno = E_WRONGOBJ; 956 | return -1; 957 | } 958 | if(blocks[b][c].inode.size<=length) { 959 | // fail 960 | LOG(1, "[x] path '%s' is too short\n", path); 961 | errno = E_NOEXT; 962 | return -1; 963 | } 964 | 965 | // store new inode 966 | Chunk nchunk; 967 | memcpy(&nchunk, &blocks[b][c], sizeof(Chunk)); 968 | nchunk.inode.size=length; 969 | store_chunk(blocks, &nchunk); 970 | 971 | uint32_t oid=blocks[b][c].inode.oid; 972 | 973 | // del inode chunk 974 | LOG(3, "[i] deleting inode chunk %d %d\n", b,c); 975 | del_chunk(blocks, b, c); 976 | 977 | // del data chunks 978 | const Chunk*chunk; 979 | uint32_t seq=length/DATA_PER_CHUNK; 980 | if(length%DATA_PER_CHUNK>0) { 981 | Chunk dchunk; 982 | b=c=0; 983 | if((chunk=find_chunk(blocks, Data, oid, 0, seq++, &b, &c))==NULL) { 984 | LOG(1, "[x] no chunk to truncate from found\n"); 985 | errno = E_NOCHUNK; 986 | return -1; 987 | } 988 | memcpy(&dchunk, &blocks[b][c], sizeof(Chunk)); 989 | memset(&dchunk.data.data[length%DATA_PER_CHUNK], 0xff, (DATA_PER_CHUNK-length%DATA_PER_CHUNK)); 990 | del_chunk(blocks, b, c); 991 | store_chunk(blocks, &dchunk); 992 | } 993 | b=c=0; 994 | while((chunk=find_chunk(blocks, Data, oid, 0, seq++, &b, &c))!=NULL) { 995 | LOG(3, "[i] deleting data chunk %d %d\n", b,c); 996 | del_chunk(blocks, b, c); 997 | b=c=0; 998 | } 999 | return 0; 1000 | } 1001 | 1002 | int stfs_init(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 1003 | // check if at least one block is empty for migration 1004 | uint32_t b, free, rcan, i; 1005 | for(b=0,free=0;b=NBLOCKS) { 1022 | // fail no empty blocks 1023 | return -1; 1024 | } 1025 | 1026 | memset(fdesc,0xff,sizeof(fdesc)); 1027 | 1028 | return 0; 1029 | } 1030 | 1031 | void dump_info(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]) { 1032 | uint32_t i, b,c, candidate_reclaim=0, used[NBLOCKS], unused[NBLOCKS], deleted[NBLOCKS]; 1033 | int candidate=-1, reserved=-1; 1034 | for(i=0;icandidate_reclaim) { 1047 | candidate=b; 1048 | candidate_reclaim=(unused[b]+deleted[b]); 1049 | } 1050 | } 1051 | for(b=0;b 5 | #include 6 | 7 | #define CHUNK_SIZE 128 8 | #define CHUNKS_PER_BLOCK 1024 9 | #define NBLOCKS 6 10 | #define DATA_PER_CHUNK (CHUNK_SIZE-7) 11 | #define MAX_FILE_SIZE 65535 12 | #define MAX_OPEN_FILES 4 13 | #define MAX_DIR_SIZE 32 14 | 15 | #define O_CREAT 64 16 | 17 | #define E_NOFDS 0 18 | #define E_EXISTS 1 19 | #define E_NOTOPEN 2 20 | #define E_INVFD 3 21 | #define E_INVFP 4 22 | #define E_TOOBIG 5 23 | #define E_SHORTWRT 6 24 | #define E_NOSEEKEOF 7 25 | #define E_NOTFOUND 8 26 | #define E_WRONGOBJ 9 27 | #define E_NOCHUNK 10 28 | #define E_NOEXT 11 29 | #define E_RELPATH 12 30 | #define E_NAMESIZE 13 31 | #define E_FULL 14 32 | #define E_BADCHUNK 15 33 | #define E_VAC 16 34 | #define E_INVNAME 17 35 | #define E_OPEN 18 36 | #define E_DELROOT 19 37 | #define E_FDREOPEN 20 38 | #define E_DANGLE 21 39 | 40 | #define SEEK_SET 0 41 | #define SEEK_CUR 1 42 | #define SEEK_END 2 43 | 44 | typedef enum { 45 | Deleted = 0x00, 46 | Inode = 0xAA, 47 | Data = 0xCC, 48 | Empty = 0xff 49 | } ChunkType; 50 | 51 | typedef enum { 52 | Directory = 0x00, 53 | File = 0x01, 54 | } InodeType; 55 | 56 | typedef struct Inode_Struct { 57 | InodeType type :1; 58 | unsigned int name_len :6; 59 | int padding: 1; 60 | uint16_t size; 61 | uint32_t parent; 62 | uint32_t oid; 63 | uint8_t name[32]; 64 | uint8_t data[CHUNK_SIZE - 44]; 65 | } __attribute((packed)) Inode_t; 66 | 67 | typedef struct Data_Struct { 68 | uint16_t seq; 69 | uint32_t oid; 70 | uint8_t data[CHUNK_SIZE-7]; 71 | } __attribute((packed)) Data_t; 72 | 73 | typedef struct Chunk_Struct { 74 | ChunkType type :8; 75 | union { 76 | Inode_t inode; 77 | Data_t data; 78 | }; 79 | } __attribute((packed)) Chunk; 80 | 81 | typedef struct { 82 | uint32_t oid; 83 | uint32_t block; 84 | uint32_t chunk; 85 | } ReaddirCTX; 86 | 87 | typedef struct { 88 | char free :1; 89 | char idirty :1; 90 | char padding :6; 91 | Chunk ichunk; 92 | uint32_t fptr; 93 | } STFS_File; 94 | 95 | int opendir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path, ReaddirCTX *ctx); 96 | const Inode_t* readdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], ReaddirCTX *ctx); 97 | int stfs_mkdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path); 98 | int stfs_rmdir(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path); 99 | int stfs_open(uint8_t *path, uint32_t oflag, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 100 | off_t stfs_lseek(uint32_t fildes, off_t offset, int whence); 101 | ssize_t stfs_write(uint32_t fildes, const void *buf, size_t nbyte, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 102 | ssize_t stfs_read(uint32_t fildes, void *buf, size_t nbyte, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 103 | int stfs_close(uint32_t fildes, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 104 | int stfs_unlink(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK], uint8_t *path); 105 | int stfs_truncate(uint8_t *path, uint32_t length, Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 106 | int stfs_init(Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]); 107 | int stfs_geterrno(void); 108 | 109 | uint32_t stfs_size(uint32_t fildes); 110 | 111 | #endif //STFS_H 112 | -------------------------------------------------------------------------------- /stfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import construct 4 | 5 | # typedef struct Inode_Struct { 6 | # InodeType type :1; 7 | # int name_len :6; 8 | # int padding: 1; 9 | # uint16_t size; 10 | # uint32_t parent; 11 | # uint32_t oid; 12 | # uint8_t name[32]; 13 | # uint8_t data[CHUNK_SIZE - 44]; 14 | # } __attribute((packed)) Inode_t; 15 | 16 | Inode = construct.Struct( 17 | 'bits'/construct.BitStruct( 18 | 'type'/construct.Flag, 19 | "name_len"/construct.BitsInteger(6), 20 | construct.Padding(1)), 21 | "size"/construct.Int16ul, 22 | 'parent'/construct.Int32ul, 23 | 'oid'/construct.Int32ul, 24 | 'name'/construct.String(length=32), 25 | 'data'/construct.String(length=84), 26 | ) 27 | 28 | # typedef struct Data_Struct { 29 | # uint16_t seq; 30 | # uint32_t oid; 31 | # uint8_t data[CHUNK_SIZE-8]; 32 | # } __attribute((packed)) Data_t; 33 | 34 | Dnode = construct.Struct( 35 | 'seq'/construct.Int16ul, 36 | 'oid'/construct.Int32ul, 37 | 'data'/construct.String(length=121), 38 | ) 39 | 40 | # typedef enum { 41 | # Deleted = 0x00, 42 | # Inode = 0xAA, 43 | # Data = 0xCC, 44 | # Empty = 0xff 45 | # } ChunkType; 46 | ChunkType = construct.Enum( 47 | construct.Int8ul, 48 | Deleted = 0x00, 49 | Inode = 0xAA, 50 | Data = 0xCC, 51 | Empty = 0xff) 52 | 53 | # typedef struct Chunk_Struct { 54 | # ChunkType type :8; 55 | # union { 56 | # Inode_t inode; 57 | # Data_t data; 58 | # }; 59 | # } __attribute((packed)) Chunk; 60 | 61 | Chunk = construct.Struct( 62 | "type"/ChunkType, 63 | "node"/construct.Union("dnode"/Dnode, 64 | "inode"/Inode), 65 | ) 66 | -------------------------------------------------------------------------------- /stfsfuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import random 4 | import sys 5 | 6 | import sh 7 | afl = sh.Command("./afl"); 8 | from stfs import Chunk 9 | from binascii import hexlify 10 | 11 | 12 | 13 | 14 | ops = ('m', 'l', 'n', 'x', 'o', 'w', 'r', 's', 'c', 'd', 't', 'c', 'c', 'c' ) 15 | dirs=set(['']) 16 | files=set() 17 | fds=[None, None, None, None, None] 18 | 19 | def random_path(): 20 | path = random.choice(tuple(dirs)) 21 | name = ''.join([chr(random.randint(65,66)) for _ in xrange(random.randint(1,2))]) 22 | path = path + "/" + name 23 | return path 24 | 25 | def getimg(): 26 | with open("test.img", 'r') as fd: 27 | return fd.read() 28 | 29 | def split_by_n( seq, n ): 30 | """A generator to divide a sequence into chunks of n units. 31 | src: http://stackoverflow.com/questions/9475241/split-python-string-every-nth-character""" 32 | while seq: 33 | yield seq[:n] 34 | seq = seq[n:] 35 | 36 | def check(): 37 | img = getimg() 38 | objects = {1: {'oid': 1, 'path': '/'}} 39 | empty=[0,0,0,0,0,0] 40 | used=[0,0,0,0,0,0] 41 | deleted=[0,0,0,0,0,0] 42 | 43 | for b, block in enumerate(split_by_n(img,128*1024)): 44 | for c, raw in enumerate(split_by_n(block,128)): 45 | chunk=Chunk.parse(raw) 46 | if chunk.type == "Data": 47 | used[b]+=1 48 | if chunk.node.dnode.oid not in objects: 49 | objects[chunk.node.dnode.oid]={'seq': [chunk.node.dnode.seq,], 50 | 'oid': chunk.node.dnode.oid} 51 | else: 52 | if chunk.node.dnode.seq in objects[chunk.node.dnode.oid]['seq']: 53 | print "[x] already seen seq", chunk.node.dnode.seq 54 | else: 55 | objects[chunk.node.dnode.oid]['seq'].append(chunk.node.dnode.seq) 56 | elif chunk.type == "Inode": 57 | used[b]+=1 58 | if chunk.node.inode.oid not in objects: 59 | nsize = chunk.node.inode.bits.name_len 60 | name = chunk.node.inode.name[:nsize] 61 | objects[chunk.node.inode.oid]={'seq': [], 62 | 'oid': chunk.node.inode.oid, 63 | 'type': chunk.node.inode.bits.type, 64 | 'parent': chunk.node.inode.parent, 65 | 'size': chunk.node.inode.size, 66 | 'name': name} 67 | else: 68 | if 'parent' in objects[chunk.node.inode.oid]: 69 | print "[x] double inode %s", chunk.node.inode 70 | else: 71 | nsize = chunk.node.inode.bits.name_len 72 | name = chunk.node.inode.name[:nsize] 73 | objects[chunk.node.inode.oid]['type']=chunk.node.inode.bits.type 74 | objects[chunk.node.inode.oid]['parent']=chunk.node.inode.parent 75 | objects[chunk.node.inode.oid]['size']=chunk.node.inode.size 76 | objects[chunk.node.inode.oid]['name']=name 77 | elif chunk.type == "Empty": 78 | empty[b]+=1 79 | elif chunk.type == "Deleted": 80 | deleted[b]+=1 81 | 82 | for oid, obj in objects.items(): 83 | if not 'name' in obj: 84 | if obj['oid']!=1: 85 | print "orphan\t%3d blocks (%dB) of %x [%s..%s]" % ( 86 | len(obj['seq']), 87 | len(obj['seq'])*121, 88 | obj['oid'], 89 | str(obj['seq'][:3])[1:-1], 90 | str(obj['seq'][-3:])[1:-1]) 91 | continue 92 | path=['/'+obj['name']] 93 | o=objects.get(obj['parent']) 94 | while o and o['oid']!=1: 95 | path.append('/'+o['name']) 96 | o=objects.get(o['parent']) 97 | obj['path']=''.join(reversed(path)) 98 | if not o or o['oid']!=1: 99 | print "dangling object", obj['path'] 100 | sys.exit(0) 101 | # check if seq list could be valid 102 | #for i, seq in enumerate(sorted(obj['seq'])): 103 | # if i!=seq: 104 | # print "missing chunk %d, from" % i, obj 105 | if obj['type']==1: # files 106 | if (obj['size']/121)+1>len(obj['seq']) and obj['size']%121!=0: 107 | print "[x] only %d chunks (%d B) for %d bytes - %s" % (len(obj['seq']), len(obj['seq'])*121, obj['size'], obj['path']) 108 | elif (obj['size']=0 and fd<5 and fds[fd]: 163 | files.add(fds[fd]) 164 | fds[fd]=None 165 | elif op == 'd' and ret.stderr.split('\n')[retline].endswith("returns: 0"): 166 | files.remove(cmd.split()[-1]) 167 | #print "files", files 168 | #print "dirs", dirs 169 | print "fds", fds 170 | objs=check() 171 | #print objs 172 | print "--------" 173 | 174 | #for line in sys.stdin: 175 | # xqt(line.strip()) 176 | #sys.exit(0) 177 | 178 | #for _ in xrange(50000): #random.randint(4,1000)): 179 | while 1: 180 | op = random.choice(ops) 181 | if op == 'm': 182 | if random.randint(0,5)>0: continue 183 | #if len(dirs)>2: continue 184 | path=random_path() 185 | xqt("m %d %s" % (len(path), path)) 186 | elif op == 'x': 187 | if len(dirs)<1: continue 188 | if random.randint(0,2)>0: continue 189 | path=random.choice(tuple(dirs)) 190 | xqt("x %d %s" % (len(path), path)) 191 | elif op == 'o': 192 | if len([x for x in fds if x])==len(fds): continue 193 | path=random_path() 194 | mode=random.choice((0, 64)) 195 | xqt("o %d %d %s" % (mode, len(path), path)) 196 | elif op == 'w': 197 | if len([x for x in fds if x])==0: continue 198 | fd=random.choice([i for i, x in enumerate(fds) if x]) 199 | size=random.randint(0, 65535) # randomize this better also bigger ranges, rarely 200 | xqt("w %d %d" % (fd, size)) 201 | elif op == 'r': 202 | if len([x for x in fds if x])==0: continue 203 | fd=random.choice([i for i, x in enumerate(fds) if x]) 204 | size=random.randint(0, 65535) # randomize this better also bigger ranges, rarely 205 | xqt("r %d %d" % (fd, size)) 206 | elif op == 's': 207 | if len([x for x in fds if x])==0: continue 208 | fd=random.choice([i for i, x in enumerate(fds) if x]) 209 | off=random.randint(-65535, 65535) # randomize this better also bigger ranges, rarely 210 | whence=random.choice((0, 1, 2)) # randomize this better also bigger ranges, rarely 211 | xqt("s %d %d %d" % (fd, off, whence)) 212 | elif op == 'c': 213 | if random.randint(0,3)>0: continue 214 | if len([x for x in fds if x])==0: continue 215 | fd=random.choice([i for i, x in enumerate(fds) if x]) 216 | xqt("c %d" % fd) 217 | elif op == 'd': 218 | if len(files)<1: continue 219 | #if random.randint(0,2)>0: continue 220 | path=random.choice(tuple(files)) 221 | xqt("d %d %s" % (len(path), path)) 222 | elif op == 't': 223 | if len([x for x in files if x])==0: continue 224 | #if random.randint(0,2)>0: continue 225 | path=random.choice(tuple(files)) 226 | off=random.randint(0, 65535) # randomize this better also bigger ranges, rarely 227 | xqt("t %d %d %s" % (off, len(path), path)) 228 | 229 | 230 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | #include "stfs.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void dump(uint8_t *src, uint32_t len); 7 | void dump_inode(const Inode_t *inode); 8 | void dump_chunk(Chunk *chunk); 9 | 10 | int main(void) { 11 | Chunk blocks[NBLOCKS][CHUNKS_PER_BLOCK]; 12 | memset(blocks,0xff,sizeof(blocks)); 13 | 14 | uint8_t testdir[]="/test"; 15 | uint8_t testdir2[]="/test/test"; 16 | uint8_t testdir3[]="/test/asdf"; 17 | uint8_t testdir4[]="/test/zxcv"; 18 | uint8_t testdir5[]="/test/qwer"; 19 | uint8_t testdir6[]="/test/hjkl"; 20 | uint8_t testfile[]="/test.txt"; 21 | uint8_t testfile2[]="/test2.txt"; 22 | uint8_t testfilebig[]="/huge.bin"; 23 | uint8_t rootpath[]="/test"; 24 | 25 | // geometry 26 | printf("[i] storage is: %.2fKB\n", sizeof(blocks)/1024.0); 27 | printf("[i] chunk is: %dB\n", sizeof(Chunk)); 28 | printf("[i] inode is: %dB\n", sizeof(Inode_t)); 29 | printf("[i] data is: %dB\n", sizeof(Data_t)); 30 | printf("[i] block is: %.2fKB\n", sizeof(blocks[0])/1024.0); 31 | printf("[x] initializing\n"); 32 | if(stfs_init(blocks)==-1) { 33 | return 1; 34 | }; 35 | // testing mkdir 36 | printf("[?] mkdir %s, returns %d\n", testdir, stfs_mkdir(blocks, testdir)); 37 | dump_chunk(&blocks[0][0]); 38 | dump((uint8_t*) blocks,128); 39 | 40 | dump_chunk(&blocks[0][1]); 41 | //dump((uint8_t*) &blocks[0][1],128); 42 | printf("[?] mkdir %s returns %d\n", testdir2, stfs_mkdir(blocks, testdir2)); 43 | dump_chunk(&blocks[0][1]); 44 | //dump((uint8_t*) &blocks[0][1],128); 45 | 46 | dump_chunk(&blocks[0][2]); 47 | //dump((uint8_t*) &blocks[0][2],128); 48 | printf("[?] mkdir %s returns %d\n", testdir3, stfs_mkdir(blocks, testdir3)); 49 | dump_chunk(&blocks[0][2]); 50 | //dump((uint8_t*) &blocks[0][2],128); 51 | 52 | printf("[?] mkdir %s returns %d\n", testdir4, stfs_mkdir(blocks, testdir4)); 53 | printf("[?] mkdir %s returns %d\n", testdir5, stfs_mkdir(blocks, testdir5)); 54 | printf("[?] mkdir %s returns %d\n", testdir6, stfs_mkdir(blocks, testdir6)); 55 | printf("[?] mkdir %s returns %d\n", testdir3, stfs_mkdir(blocks, testdir3)); 56 | 57 | // basic getdents aka ls /test 58 | ReaddirCTX ctx; 59 | opendir(blocks, rootpath, &ctx); 60 | const Inode_t *inode; 61 | while((inode=readdir(blocks, &ctx))!=0) { 62 | dump_inode(inode); 63 | } 64 | 65 | // testing rmdir 66 | dump_chunk(&blocks[0][0]); 67 | printf("[?] rmdir %s returns %d\n", testdir, stfs_rmdir(blocks, testdir)); 68 | dump_chunk(&blocks[0][0]); 69 | dump_chunk(&blocks[0][1]); 70 | printf("[?] rmdir %s returns %d\n", testdir2, stfs_rmdir(blocks, testdir2)); 71 | dump_chunk(&blocks[0][1]); 72 | 73 | // ls /test 74 | opendir(blocks, rootpath, &ctx); 75 | while((inode=readdir(blocks, &ctx))!=0) { 76 | dump_inode(inode); 77 | } 78 | 79 | // file op tests 80 | // open create? 81 | int fd=stfs_open(testfile, O_CREAT, blocks); 82 | printf("[?] open %s o_creat returns %d\n", testfile, fd); 83 | 84 | // write data 85 | uint8_t data0[256]; 86 | int i, ret; 87 | for(i=0;i0) cnt+=ret; 245 | if(memcmp(data0, data0r, i<255?256:255)!=0) { 246 | printf("[x] fail to compare saved file with original\n"); 247 | } 248 | } 249 | printf("[i] total read: %d\n", cnt); 250 | printf("[?] close returns %d\n",stfs_close(fd, blocks)); 251 | 252 | fd=open("test.img", O_RDWR | O_CREAT | O_TRUNC, 0666 ); 253 | printf("[i] dumping fs to fd %d\n", fd); 254 | write(fd,blocks, sizeof(blocks)); 255 | close(fd); 256 | 257 | return 0; 258 | } 259 | -------------------------------------------------------------------------------- /testcases/closermdired: -------------------------------------------------------------------------------- 1 | m 2 /a 2 | o 64 4 /a/a 3 | w 0 2000 4 | c 0 5 | o 0 4 /a/a 6 | s 0 100 0 7 | w 0 100 8 | x 2 /a 9 | s 0 1000 0 10 | w 0 100 11 | c 0 12 | -------------------------------------------------------------------------------- /testcases/closeunlinked: -------------------------------------------------------------------------------- 1 | o 64 2 /a 2 | w 0 2000 3 | c 0 4 | o 0 2 /a 5 | s 0 100 0 6 | w 0 100 7 | d 2 /a 8 | s 0 1000 0 9 | w 0 100 10 | c 0 11 | -------------------------------------------------------------------------------- /testcases/closeunlinked2: -------------------------------------------------------------------------------- 1 | o 64 2 /a 2 | c 0 3 | o 0 2 /a 4 | w 0 2000 5 | s 0 -1000 2 6 | d 2 /a 7 | c 0 8 | -------------------------------------------------------------------------------- /testcases/hang: -------------------------------------------------------------------------------- 1 | w 0 66080 2 | o 64 2 /A 3 | m 3 /BA 4 | m 3 /BA 5 | d 2 /A 6 | c 4 7 | o 0 3 /AB 8 | w 4 61067 9 | d 0 10 | m 5 /BA/A 11 | m 6 /BA/AB 12 | t 29107 0 13 | x 5 /BA/A 14 | c 3 15 | s 0 19068 2 16 | x 6 /BA/AB 17 | m 2 /B 18 | c 2 19 | s 1 37064 2 20 | d 0 21 | r 1 28346 22 | s 0 56870 0 23 | s 0 43804 0 24 | x 3 /BA 25 | c 4 26 | x 2 /B 27 | d 0 28 | s 1 26136 1 29 | w 4 58882 30 | s 2 38151 2 31 | o 0 2 /A 32 | d 0 33 | o 0 2 /A 34 | t 41126 0 35 | o 64 2 /A 36 | c 0 37 | c 4 38 | c 4 39 | o 64 3 /AB 40 | m 3 /BB 41 | m 2 /A 42 | s 0 42532 0 43 | s 0 53264 1 44 | w 0 31061 45 | o 0 5 /BB/B 46 | w 3 64415 47 | r 2 52016 48 | m 5 /BB/A 49 | d 3 /AB 50 | r 2 49412 51 | r 4 66967 52 | r 4 10276 53 | x 3 /BB 54 | x 5 /BB/A 55 | o 64 3 /AB 56 | t 40548 3 /AB 57 | s 0 30715 1 58 | m 5 /A/BB 59 | t 19880 0 60 | x 2 /A 61 | d 2 /A 62 | o 0 8 /A/BB/BA 63 | x 5 /A/BB 64 | w 2 40445 65 | w 2 29044 66 | w 4 28898 67 | x 0 68 | w 2 12562 69 | o 64 2 /A 70 | w 2 11921 71 | r 4 46657 72 | c 2 73 | c 3 74 | m 2 /B 75 | o 0 4 /B/B 76 | m 2 /B 77 | w 2 62800 78 | w 4 68914 79 | t 17364 2 /A 80 | s 3 64778 2 81 | w 4 55381 82 | w 2 33843 83 | r 0 12108 84 | d 0 85 | x 0 86 | w 3 58301 87 | w 3 22929 88 | s 2 18989 2 89 | s 4 53781 1 90 | o 0 5 /B/BA 91 | s 2 15935 0 92 | d 2 /A 93 | r 2 23678 94 | m 2 /A 95 | d 0 96 | d 3 /AB 97 | t 27676 0 98 | o 64 3 /AA 99 | t 26553 0 100 | d 0 101 | m 5 /B/BB 102 | d 3 /AA 103 | o 64 5 /B/BA 104 | r 0 26110 105 | s 2 29362 0 106 | m 8 /B/BB/AB 107 | d 5 /B/BA 108 | d 0 109 | r 0 23013 110 | d 0 111 | m 5 /B/BA 112 | d 0 113 | o 0 2 /B 114 | d 0 115 | s 2 19215 0 116 | m 4 /A/A 117 | c 1 118 | c 3 119 | t 29641 0 120 | m 3 /AA 121 | c 1 122 | r 1 2427 123 | s 4 10340 1 124 | c 0 125 | m 4 /A/A 126 | t 21760 0 127 | r 0 6048 128 | o 0 2 /A 129 | m 10 /B/BB/AB/A 130 | c 1 131 | t 24987 0 132 | x 5 /B/BB 133 | m 12 /B/BB/AB/A/A 134 | m 10 /B/BB/AB/A 135 | w 4 35825 136 | t 51983 0 137 | r 3 66664 138 | c 0 139 | w 4 65010 140 | c 4 141 | t 39641 0 142 | s 4 4506 2 143 | o 64 13 /B/BB/AB/A/AA 144 | r 4 44485 145 | c 2 146 | x 0 147 | w 2 43852 148 | m 3 /AB 149 | t 58528 13 /B/BB/AB/A/AA 150 | c 4 151 | o 64 15 /B/BB/AB/A/A/BB 152 | w 3 17756 153 | m 8 /B/BA/AA 154 | d 0 155 | t 18105 15 /B/BB/AB/A/A/BB 156 | m 5 /B/AB 157 | r 2 59740 158 | x 12 /B/BB/AB/A/A 159 | r 3 52018 160 | r 0 67343 161 | m 7 /B/BA/B 162 | w 0 16549 163 | o 0 12 /B/BB/AB/A/B 164 | m 11 /B/BB/AB/AA 165 | r 1 39984 166 | c 0 167 | w 3 58941 168 | o 64 5 /AB/B 169 | w 4 33814 170 | m 11 /B/BA/AA/BB 171 | t 65048 15 /B/BB/AB/A/A/BB 172 | d 0 173 | r 2 25970 174 | c 3 175 | r 3 553 176 | t 17384 15 /B/BB/AB/A/A/BB 177 | s 1 15157 0 178 | w 4 3432 179 | s 1 43012 2 180 | o 0 6 /AB/AB 181 | s 3 35907 1 182 | m 6 /AA/BA 183 | c 0 184 | c 2 185 | t 67295 15 /B/BB/AB/A/A/BB 186 | x 11 /B/BA/AA/BB 187 | d 13 /B/BB/AB/A/AA 188 | s 0 18898 1 189 | t 61653 0 190 | w 3 11174 191 | d 15 /B/BB/AB/A/A/BB 192 | s 3 59938 2 193 | c 1 194 | d 0 195 | d 0 196 | c 1 197 | w 0 47313 198 | s 1 34879 1 199 | w 1 65378 200 | o 0 5 /A/AA 201 | m 13 /B/BB/AB/A/AB 202 | x 7 /B/BA/B 203 | d 5 /AB/B 204 | s 0 47508 1 205 | w 3 33930 206 | t 46805 0 207 | x 2 /A 208 | c 1 209 | m 13 /B/BB/AB/A/BB 210 | m 5 /AA/A 211 | r 2 41478 212 | w 4 41720 213 | m 10 /B/BA/AA/A 214 | m 5 /AA/A 215 | t 40564 0 216 | r 3 56281 217 | t 7381 0 218 | o 64 12 /B/BA/AA/A/B 219 | w 4 48427 220 | x 8 /B/BA/AA 221 | m 13 /B/BA/AA/A/AA 222 | r 3 36135 223 | c 0 224 | r 1 1717 225 | x 2 /B 226 | c 4 227 | r 3 14818 228 | d 12 /B/BA/AA/A/B 229 | m 6 /A/A/A 230 | r 4 29967 231 | s 4 3926 0 232 | r 2 17184 233 | o 64 8 /AA/A/BA 234 | x 0 235 | o 0 2 /B 236 | m 13 /B/BB/AB/A/BB 237 | x 5 /AA/A 238 | c 2 239 | r 0 69134 240 | d 0 241 | c 3 242 | d 8 /AA/A/BA 243 | t 43940 0 244 | w 3 6958 245 | w 0 66889 246 | s 2 31983 2 247 | w 0 54198 248 | w 1 26099 249 | o 0 16 /B/BB/AB/A/AB/BA 250 | x 5 /B/BA 251 | c 1 252 | m 5 /AA/B 253 | t 41867 0 254 | o 64 8 /AA/B/BB 255 | t 55789 8 /AA/B/BB 256 | c 0 257 | s 2 23414 1 258 | d 8 /AA/B/BB 259 | o 64 10 /B/BB/AB/B 260 | r 3 57823 261 | m 6 /AA/AA 262 | c 0 263 | r 0 40397 264 | s 3 51859 1 265 | w 3 69715 266 | m 13 /B/BB/AB/AA/A 267 | o 0 8 /AA/B/AA 268 | x 13 /B/BB/AB/AA/A 269 | r 4 36192 270 | s 1 51280 0 271 | o 64 15 /B/BB/AB/A/BB/A 272 | t 68997 0 273 | r 1 68033 274 | c 1 275 | x 6 /A/A/A 276 | t 19499 15 /B/BB/AB/A/BB/A 277 | s 4 19026 2 278 | d 10 /B/BB/AB/B 279 | t 62095 0 280 | w 0 22842 281 | w 4 10731 282 | t 63476 15 /B/BB/AB/A/BB/A 283 | w 1 10482 284 | x 6 /AA/BA 285 | m 13 /B/BA/AA/A/AB 286 | x 10 /B/BA/AA/A 287 | t 18077 15 /B/BB/AB/A/BB/A 288 | x 6 /AA/AA 289 | d 15 /B/BB/AB/A/BB/A 290 | m 15 /B/BA/AA/A/AB/A 291 | r 0 8424 292 | m 8 /B/AB/BB 293 | m 6 /A/A/A 294 | r 4 1433 295 | s 3 28735 0 296 | t 28999 0 297 | s 2 8301 0 298 | t 44845 0 299 | d 0 300 | d 0 301 | w 0 59036 302 | o 64 5 /AA/A 303 | r 0 33641 304 | s 0 62856 0 305 | d 5 /AA/A 306 | d 0 307 | o 0 15 /B/BB/AB/A/BB/A 308 | r 3 48510 309 | r 2 58819 310 | s 0 20242 1 311 | d 0 312 | x 11 /B/BB/AB/AA 313 | m 8 /A/A/A/A 314 | c 2 315 | x 8 /A/A/A/A 316 | s 4 26454 0 317 | w 4 51925 318 | s 4 13907 1 319 | c 2 320 | m 7 /A/A/BA 321 | o 0 3 /AB 322 | t 25931 0 323 | s 3 26047 2 324 | t 42364 0 325 | c 2 326 | r 0 49265 327 | t 50217 0 328 | o 64 11 /B/BB/AB/AA 329 | x 8 /B/AB/BB 330 | o 64 17 /B/BA/AA/A/AB/A/A 331 | r 1 63792 332 | s 4 5289 2 333 | r 4 34972 334 | w 4 59808 335 | m 9 /A/A/BA/A 336 | r 0 7221 337 | t 48924 0 338 | x 8 /B/BB/AB 339 | t 26825 11 /B/BB/AB/AA 340 | o 64 16 /B/BB/AB/A/AB/BB 341 | s 0 66965 2 342 | w 4 16171 343 | s 1 7338 1 344 | w 3 17879 345 | t 21563 16 /B/BB/AB/A/AB/BB 346 | d 0 347 | t 61531 17 /B/BA/AA/A/AB/A/A 348 | s 2 45386 2 349 | x 4 /A/A 350 | m 6 /AB/BA 351 | s 4 1384 2 352 | w 3 53417 353 | x 3 /AB 354 | c 3 355 | s 3 53293 2 356 | o 0 3 /AB 357 | s 1 33045 2 358 | s 0 20021 2 359 | t 32588 16 /B/BB/AB/A/AB/BB 360 | s 4 63222 1 361 | x 7 /A/A/BA 362 | d 16 /B/BB/AB/A/AB/BB 363 | o 64 16 /B/BB/AB/A/BB/BB 364 | x 3 /AA 365 | t 40106 17 /B/BA/AA/A/AB/A/A 366 | o 0 7 /B/AB/A 367 | s 2 32273 2 368 | o 64 13 /B/BB/AB/A/AB 369 | r 1 34840 370 | d 11 /B/BB/AB/AA 371 | t 22173 0 372 | s 4 22532 0 373 | r 0 69028 374 | r 4 586 375 | x 6 /AB/BA 376 | o 0 16 /B/BA/AA/A/AA/BB 377 | s 1 43178 2 378 | s 2 19720 1 379 | d 16 /B/BB/AB/A/BB/BB 380 | w 1 12902 381 | r 3 1323 382 | o 0 12 /A/A/BA/A/BA 383 | m 8 /B/AB/BA 384 | x 5 /AA/B 385 | m 8 /B/AB/AB 386 | x 13 /B/BB/AB/A/BB 387 | x 5 /B/AB 388 | o 0 10 /B/AB/BA/A 389 | o 0 15 /B/BB/AB/A/AB/A 390 | t 18862 0 391 | t 27104 13 /B/BB/AB/A/AB 392 | o 64 18 /B/BA/AA/A/AB/A/BA 393 | w 4 60092 394 | d 13 /B/BB/AB/A/AB 395 | r 4 6839 396 | x 6 /A/A/A 397 | r 4 44508 398 | m 10 /B/AB/AB/B 399 | s 3 299 1 400 | o 64 11 /A/A/BA/A/B 401 | x 10 /B/BB/AB/A 402 | m 11 /B/AB/AB/BA 403 | d 17 /B/BA/AA/A/AB/A/A 404 | r 4 24608 405 | x 9 /A/A/BA/A 406 | w 1 14281 407 | c 1 408 | w 2 13498 409 | w 1 44632 410 | x 13 /B/BA/AA/A/AA 411 | x 11 /B/AB/AB/BA 412 | s 2 65118 2 413 | w 3 39726 414 | s 1 52144 1 415 | w 0 8575 416 | o 64 11 /B/AB/BA/AA 417 | x 0 418 | d 11 /B/AB/BA/AA 419 | t 30504 18 /B/BA/AA/A/AB/A/BA 420 | t 7248 18 /B/BA/AA/A/AB/A/BA 421 | d 0 422 | o 0 11 /B/AB/BA/BB 423 | o 0 16 /B/BA/AA/A/AB/AA 424 | w 1 68048 425 | o 64 10 /B/AB/BA/B 426 | s 2 6459 2 427 | d 18 /B/BA/AA/A/AB/A/BA 428 | o 64 12 /B/AB/AB/B/B 429 | m 13 /B/AB/AB/B/AA 430 | c 4 431 | t 67987 10 /B/AB/BA/B 432 | w 2 55034 433 | s 0 62195 0 434 | c 2 435 | c 2 436 | x 0 437 | x 0 438 | o 0 10 /B/AB/AB/A 439 | r 4 53925 440 | o 64 15 /B/BA/AA/A/AB/B 441 | o 64 18 /B/BA/AA/A/AB/A/BB 442 | c 4 443 | t 22447 0 444 | m 3 /BB 445 | d 11 /A/A/BA/A/B 446 | s 1 56353 1 447 | c 1 448 | c 4 449 | x 13 /B/BA/AA/A/AB 450 | w 2 49959 451 | s 0 55539 0 452 | m 13 /B/AB/AB/B/BA 453 | d 18 /B/BA/AA/A/AB/A/BB 454 | c 3 -------------------------------------------------------------------------------- /testcases/missingend: -------------------------------------------------------------------------------- 1 | o 64 3 /AB 2 | w 0 65138 3 | s 0 17428 0 4 | w 0 2287 5 | w 0 28051 6 | c 0 -------------------------------------------------------------------------------- /testcases/overwrite-on-full-fs: -------------------------------------------------------------------------------- 1 | o 64 2 /a 2 | w 0 65535 3 | c 0 4 | o 64 2 /b 5 | w 0 65535 6 | c 0 7 | o 64 2 /c 8 | w 0 65535 9 | c 0 10 | o 64 2 /d 11 | w 0 65535 12 | c 0 13 | o 64 2 /e 14 | w 0 65535 15 | c 0 16 | o 64 2 /f 17 | w 0 65535 18 | c 0 19 | o 64 2 /g 20 | w 0 65535 21 | c 0 22 | m 2 /A 23 | x 2 /A 24 | o 64 2 /h 25 | w 0 35574 26 | c 0 27 | o 0 2 /h 28 | s 0 1 0 29 | w 0 1210 30 | --------------------------------------------------------------------------------