├── LICENSE ├── Makefile ├── README.md ├── example.c ├── imfs.c ├── imfs.h └── tests.c /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Raffaele del Gaudio 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS = -Wall -Wextra 3 | 4 | 5 | .PHONY: example tests cov clean 6 | 7 | example: imfs.c example.c 8 | $(CC) $(CFLAGS) $^ -o $@ 9 | 10 | tests: CFLAGS += -g -O0 11 | tests: tests.c imfs.c 12 | $(CC) $(CFLAGS) $^ -o $@ 13 | ./$@ 14 | 15 | cov: CFLAGS += -fprofile-arcs -ftest-coverage 16 | cov: tests 17 | @gcov tests-imfs.gcno 18 | @lcov --capture --directory . --output-file coverage.lcov 19 | @genhtml coverage.lcov --output-directory cov 20 | @rm -f *.gcda *.gcno *.gcov *.lcov 21 | 22 | clean: 23 | rm -rf cov example tests *.o *.gcda *.gcno *.gcov *.lcov -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # In Memory File System 2 | A simple in-memory file system written in C. 3 | 4 | This project marks my first venture into the world of file systems and was inspired by the structure of the [Linux VFS](https://docs.kernel.org/filesystems/vfs.html). 5 | 6 | The goal was to provide a way to store data permanently on addressable non-volatile memories (such as flash), while remaining light enough to be used in resource-constrained embedded environments. 7 | 8 | ## Specs 9 | - Supports hard links, 10 | - Lacks complex file operations like renaming or moving the file r/w pointer, 11 | - No support for multi-threaded usage. User code must handle serialization when accessing the file system, 12 | - Not atomic with respect to failures during operations. 13 | 14 | ## Usage 15 | Simply add `imfs.c` to your project and include `imfs.h`. That's all you need! :) 16 | 17 | ## Example 18 | Using IMFS is simple as: 19 | ```c 20 | struct imfs *fs = imfs_init(base_mem, mem_size, &c, true); 21 | int fd = imfs_open(fs, "/file1", IMFS_CREAT | IMFS_RDWR); 22 | imfs_write(fs, fd, wbuf, len); 23 | imfs_read(fs, fd, rbuf, BSIZE); 24 | imfs_close(fs, fd); 25 | ``` 26 | Use `make example && ./example` to compile and run an example program. 27 | 28 | ## Tests & Coverage 29 | A combination of random tests and common usage patterns was used for testing.\ 30 | Run `make tests` to compile and execute the tests; run `make cov` to execute the tests with code coverage. -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "imfs.h" 4 | 5 | #define MSIZE 128*1024 6 | #define BSIZE 1024 7 | 8 | char base_mem[MSIZE]; 9 | char buf[BSIZE]; 10 | 11 | int main(void) 12 | { 13 | struct imfs_conf c = { 14 | .max_num_fnodes = 50, 15 | .max_opened_files = 50 16 | }; 17 | 18 | struct imfs *fs = imfs_init(base_mem, MSIZE, &c, true); 19 | 20 | if (!fs) 21 | { 22 | fputs("IMFS: Error in IMFS init!\n", stdout); 23 | return -1; 24 | } 25 | 26 | fputs("IMFS: Filesystem started\n", stdout); 27 | 28 | bool cont = true; 29 | int fd; 30 | size_t size; 31 | long result; 32 | do 33 | { 34 | fputs("\nSelect an option:\n" 35 | "1: Create a directory\n" 36 | "2: Remove a directory\n" 37 | "3: Open/Create a file\n" 38 | "4: Close a file\n" 39 | "5: Read from a file\n" 40 | "6: Write to a file\n" 41 | "7: Create an hard link\n" 42 | "8: Delete an hard link\n" 43 | "Other: Exit\n" 44 | "# ", stdout); 45 | 46 | fgets(buf, sizeof(buf), stdin); 47 | 48 | switch (buf[0]) 49 | { 50 | case '1': 51 | fputs("Path of the directory:\n" 52 | "# ", stdout); 53 | fgets(buf, sizeof(buf), stdin); 54 | buf[strcspn(buf, "\n")] = '\0'; 55 | 56 | printf("STATUS: %s\n", imfs_mkdir(fs, buf) ? "ERROR" : "OK"); 57 | break; 58 | 59 | case '2': 60 | fputs("Path of the directory:\n" 61 | "# ", stdout); 62 | fgets(buf, sizeof(buf), stdin); 63 | buf[strcspn(buf, "\n")] = '\0'; 64 | 65 | printf("STATUS: %s\n", imfs_rmdir(fs, buf) ? "ERROR" : "OK"); 66 | break; 67 | 68 | case '3': 69 | fputs("Path of the file:\n" 70 | "# ", stdout); 71 | fgets(buf, sizeof(buf), stdin); 72 | buf[strcspn(buf, "\n")] = '\0'; 73 | 74 | printf("FD (< 0 IS ERROR): %d\n", 75 | imfs_open(fs, buf, IMFS_CREAT | IMFS_RDWR)); 76 | break; 77 | 78 | case '4': 79 | fputs("File descriptor:\n" 80 | "# ", stdout); 81 | fgets(buf, sizeof(buf), stdin); 82 | sscanf(buf, "%d", &fd); 83 | 84 | printf("STATUS: %s\n", imfs_close(fs, fd) ? "ERROR" : "OK"); 85 | break; 86 | 87 | case '5': 88 | fputs("File descriptor:\n" 89 | "# ", stdout); 90 | fgets(buf, sizeof(buf), stdin); 91 | sscanf(buf, "%d", &fd); 92 | 93 | fputs("Num. bytes to read:\n" 94 | "# ", stdout); 95 | fgets(buf, sizeof(buf), stdin); 96 | sscanf(buf, "%zu", &size); 97 | size = size < sizeof(buf) ? size : sizeof(buf); 98 | 99 | result = imfs_read(fs, fd, buf, size); 100 | printf("NUM. BYTES READ (< 0 IS ERROR): %zi\n", result); 101 | if (result > 0) 102 | { 103 | buf[result] = '\0'; 104 | printf("BYTES: %s\n", buf); 105 | } 106 | break; 107 | 108 | case '6': 109 | fputs("File descriptor:\n" 110 | "# ", stdout); 111 | fgets(buf, sizeof(buf), stdin); 112 | sscanf(buf, "%d", &fd); 113 | 114 | fputs("Bytes to write:\n" 115 | "# ", stdout); 116 | fgets(buf, sizeof(buf), stdin); 117 | buf[strcspn(buf, "\n")] = '\0'; 118 | 119 | printf("BYTES WRITTEN (< 0 IS ERROR): %zi\n", 120 | imfs_write(fs, fd, buf, strlen(buf))); 121 | break; 122 | 123 | case '7': 124 | fputs("Path of the old file:\n" 125 | "# ", stdout); 126 | fgets(buf, sizeof(buf), stdin); 127 | buf[strcspn(buf, "\n")] = '\0'; 128 | size = strlen(buf) + 1; 129 | 130 | fputs("Path of the new file:\n" 131 | "# ", stdout); 132 | fgets(&buf[size], sizeof(buf) - size, stdin); 133 | buf[size + strcspn(&buf[size], "\n")] = '\0'; 134 | 135 | printf("STATUS: %s\n", 136 | imfs_link(fs, buf, &buf[size]) ? "ERROR" : "OK"); 137 | break; 138 | 139 | case '8': 140 | fputs("Path of the file:\n" 141 | "# ", stdout); 142 | fgets(buf, sizeof(buf), stdin); 143 | buf[strcspn(buf, "\n")] = '\0'; 144 | 145 | printf("STATUS: %s\n", 146 | imfs_unlink(fs, buf) ? "ERROR" : "OK"); 147 | break; 148 | 149 | default: 150 | cont = false; 151 | break; 152 | } 153 | 154 | } while (cont); 155 | 156 | fputs("IMFS: Filesystem shutdowned\n", stdout); 157 | 158 | return 0; 159 | } -------------------------------------------------------------------------------- /imfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Raffaele del Gaudio 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 5 | * and associated documentation files (the “Software”), to deal in the Software without restriction, 6 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 8 | * subject to the following conditions: 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial 10 | * portions of the Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 13 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #include "imfs.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | _Static_assert(IMFS_MIN_DATA_BLOCK_SIZE_POW2 > 5, 26 | "Minimum file data block size is 64 bytes."); 27 | 28 | #define ALIGN_ADDR_POW2(addr, pow2) \ 29 | ({ \ 30 | uintptr_t addr_ = (uintptr_t)(addr); \ 31 | uintptr_t pow2_ = (uintptr_t)(pow2); \ 32 | addr_ += - addr_ & (pow2_ - 1UL); \ 33 | }) 34 | 35 | #define IMFS_PRE_ADJ_DATA_BLOCK_SIZE \ 36 | (1UL << IMFS_MIN_DATA_BLOCK_SIZE_POW2) 37 | 38 | struct direlem 39 | { 40 | size_t fnodeID; 41 | unsigned char name_len; 42 | char name[IMFS_MAX_NAME_LEN]; 43 | }; 44 | /* Statically defined fnodeid for the '/' directory */ 45 | #define ROOT_DIR_FNODEID 0 46 | 47 | /* 48 | * Number of struct direlem elements per file data 49 | * block approximated by the excess 50 | */ 51 | #define DIRELEM_PER_FDB \ 52 | ((IMFS_PRE_ADJ_DATA_BLOCK_SIZE + \ 53 | sizeof(struct direlem) - 1)/ \ 54 | sizeof(struct direlem)) 55 | 56 | /* 57 | * The file data block size is calculated as the smallest 58 | * integer bigger that IMFS_PRE_ADJ_DATA_BLOCK_SIZE such that 59 | * it's a multiple of the size of struct direlem. 60 | */ 61 | #define IMFS_DATA_BLOCK_SIZE \ 62 | (sizeof(struct direlem) * DIRELEM_PER_FDB) 63 | 64 | struct fdatablock 65 | { 66 | union fdbhead 67 | { 68 | /* next is used while in freelist for easy management */ 69 | struct fdatablock *next; 70 | /* xor is used while allocated to a fnode as next ^ prev */ 71 | uintptr_t xor; 72 | } h; 73 | /* 74 | * data is aligned as struct direlem so that it can be casted to 75 | * an array of such structs 76 | */ 77 | char _Alignas(struct direlem) data[IMFS_DATA_BLOCK_SIZE]; 78 | }; 79 | // We want to use the LSB of [h.next] to store allocation 80 | // status so we need to impose a minimum alignment of 2 81 | _Static_assert(_Alignof(struct fdatablock) > 1); 82 | 83 | struct fnode 84 | { 85 | union 86 | { 87 | /* 88 | * Points to the head of the circular xor list 89 | * of filedatablock allocated to this fnode 90 | */ 91 | struct fdatablock *data_blocks_head; 92 | /* next ptr is used while fnode is in the circular freelist */ 93 | struct fnode *next; 94 | }; 95 | /* 96 | * Points to the tail of the circular xor list 97 | * of filedatablock allocated to this fnode. 98 | * Needed to start the traversal of the list 99 | */ 100 | struct fdatablock *data_blocks_tail; 101 | /* The number of used bytes of the last fdatablock */ 102 | size_t last_block_used; 103 | /* the number of direlem referencing this fnode */ 104 | unsigned short link_count; 105 | /* the number of opened files referencing this fnode */ 106 | unsigned short open_count; 107 | enum 108 | { 109 | IMFS_FILE, 110 | IMFS_DIR 111 | } type; 112 | }; 113 | 114 | struct file 115 | { 116 | size_t fnodeID; 117 | union 118 | { 119 | struct fileptr 120 | { 121 | struct fdatablock *curr; 122 | struct fdatablock *prev; 123 | size_t b_index; 124 | } read_ptr; 125 | /* Used while in the freelist */ 126 | struct file *next; 127 | }; 128 | bool readonly; 129 | }; 130 | 131 | /* IMFS */ 132 | #define IMFS_MAGIC 0x494D4653 133 | 134 | struct imfs 135 | { 136 | uint32_t magic; 137 | /* size of the whole available memory */ 138 | size_t mem_size; 139 | 140 | /* file memory area */ 141 | struct file *files; 142 | /* Number of files in the table */ 143 | size_t files_len; 144 | /* file circular freelist tail */ 145 | struct file *files_cfl_tail; 146 | 147 | /* fnode memory area */ 148 | struct fnode *fn; 149 | /* Number of fnodes in the table */ 150 | size_t fn_len; 151 | /* fnode circular freelist tail */ 152 | struct fnode *fn_cfl_tail; 153 | 154 | /* fdatablock memory area */ 155 | struct fdatablock *fb; 156 | /* Number of fdatablocks in the table */ 157 | size_t fb_len; 158 | /* fdatablock freelist head */ 159 | struct fdatablock *fb_fl_head; 160 | }; 161 | 162 | /* ------------------------------------------------------------------------- */ 163 | 164 | #define FDATABLOCK_IS_VALID(fs, fdb) \ 165 | ({ \ 166 | struct imfs *fs_ = fs; \ 167 | uintptr_t fdb_ = (uintptr_t)fdb; \ 168 | uintptr_t start_ = \ 169 | (uintptr_t)&fs_->fb[0]; \ 170 | uintptr_t end_ = \ 171 | (uintptr_t)&fs_->fb[fs_->fb_len-1]; \ 172 | fdb_ >= start_ && fdb_ <= end_ && \ 173 | (fdb_ - start_) % \ 174 | sizeof(struct fdatablock) == 0; \ 175 | }) 176 | #define FDATABLOCK_IS_FREE(fdb) \ 177 | ({ \ 178 | struct fdatablock *fdb_ = fdb; \ 179 | (uintptr_t)fdb_->h.next & 1; \ 180 | }) 181 | 182 | static struct fdatablock *alloc_fdatablock(struct imfs *fs) 183 | { 184 | // At this level we ASSUME a valid imfs 185 | assert(fs); 186 | 187 | struct fdatablock *new = fs->fb_fl_head; 188 | 189 | if (fs->fb_fl_head) 190 | { 191 | fs->fb_fl_head = (struct fdatablock *) 192 | ((uintptr_t)(fs->fb_fl_head->h.next) 193 | & ~(uintptr_t)1); 194 | new->h.next = NULL; 195 | } 196 | 197 | return new; 198 | } 199 | 200 | static void free_fdatablock(struct imfs *fs, struct fdatablock *fdb) 201 | { 202 | assert(fs && FDATABLOCK_IS_VALID(fs, fdb)); 203 | // Catch double frees 204 | if (FDATABLOCK_IS_FREE(fdb)) return; 205 | 206 | fdb->h.next = (struct fdatablock *) 207 | ((uintptr_t)fs->fb_fl_head | (uintptr_t)1); 208 | fs->fb_fl_head = fdb; 209 | } 210 | 211 | /* ------------------------------------------------------------------------- */ 212 | 213 | #define FNODE_IS_VALID(fs, f) \ 214 | ({ \ 215 | struct imfs *fs_ = fs; \ 216 | uintptr_t f_ = (uintptr_t)f; \ 217 | uintptr_t start_ = \ 218 | (uintptr_t)&fs_->fn[0]; \ 219 | uintptr_t end_ = \ 220 | (uintptr_t)&fs_->fn[fs_->fn_len-1]; \ 221 | f_ >= start_ && f_ <= end_ && \ 222 | (f_ - start_) % sizeof(struct fnode) == 0; \ 223 | }) 224 | #define FNODE_IS_FREE(fs, f) \ 225 | FNODE_IS_VALID(fs, ((struct fnode *)f)->next) 226 | 227 | static struct fnode *alloc_fnode(struct imfs *fs) 228 | { 229 | // At this level we ASSUME a valid imfs 230 | assert(fs); 231 | struct fnode *new = fs->fn_cfl_tail; 232 | 233 | if (fs->fn_cfl_tail) 234 | { 235 | if(fs->fn_cfl_tail == 236 | fs->fn_cfl_tail->next) 237 | // We're removing the last element of the list 238 | fs->fn_cfl_tail = NULL; 239 | else 240 | { 241 | // List has more than one element so we have to 242 | // remove the one following the tail 243 | new = fs->fn_cfl_tail->next; 244 | fs->fn_cfl_tail->next = new->next; 245 | } 246 | // We init next to NULL because this is the 247 | // way we can tell apart allocated nodes from free ones 248 | new->next = NULL; 249 | 250 | // We also init the fnode to be sure it respects the right 251 | // semantic. 252 | new->data_blocks_head = new->data_blocks_tail = NULL; 253 | new->last_block_used = 0; 254 | new->link_count = 0; 255 | new->open_count = 0; 256 | } 257 | 258 | return new; 259 | } 260 | 261 | static void free_fnode(struct imfs *fs, struct fnode *f) 262 | { 263 | // Valid [fs] and [f] is assumed here 264 | assert(fs && FNODE_IS_VALID(fs, f)); 265 | // Double freeing the node? I don't think so! 266 | // Considering we have an array of fnode - [fnodes] - 267 | // and a circular linked list of free fnode starting 268 | // from [cfl_tail], we can check that [f] is in the 269 | // freelist without searching the whole list. 270 | // If [f] is in the freelist than [f]->next MUST 271 | // point to a fnode. Since any fnode resides in the 272 | // [fnodes] array we can just check that [f]->next 273 | // points to an fnode contained in the [fnodes] array. 274 | if (FNODE_IS_FREE(fs, f)) return; 275 | 276 | // Ok, so we need to actually free the node [f] 277 | if(fs->fn_cfl_tail) 278 | { 279 | // Inserting in the circular queue after the tail 280 | f->next = fs->fn_cfl_tail->next; 281 | fs->fn_cfl_tail->next = f; 282 | } 283 | else 284 | { 285 | // Freelist was empty and this is the only element 286 | fs->fn_cfl_tail = f; 287 | f->next = f; 288 | } 289 | } 290 | 291 | /* ------------------------------------------------------------------------- */ 292 | 293 | static void append_fdatablock_to_fnode(struct imfs *fs, struct fnode *dst, 294 | struct fdatablock *src) 295 | { 296 | assert(fs && FNODE_IS_VALID(fs, dst) && !FNODE_IS_FREE(fs, dst) && 297 | FDATABLOCK_IS_VALID(fs, src) && !FDATABLOCK_IS_FREE(src)); 298 | 299 | assert((dst->data_blocks_head && dst->data_blocks_tail) || 300 | (!dst->data_blocks_head && !dst->data_blocks_tail)); 301 | 302 | uintptr_t head_next, tail_prev; 303 | 304 | src->h.xor = 305 | (uintptr_t)dst->data_blocks_tail ^ 306 | (uintptr_t)dst->data_blocks_head; 307 | dst->last_block_used = 0; 308 | 309 | if(dst->data_blocks_tail) 310 | { 311 | if (dst->data_blocks_head != dst->data_blocks_tail) 312 | { 313 | // At least two blocks are present before appending 314 | head_next = dst->data_blocks_head->h.xor ^ 315 | (uintptr_t)dst->data_blocks_tail; 316 | tail_prev = dst->data_blocks_tail->h.xor ^ 317 | (uintptr_t)dst->data_blocks_head; 318 | 319 | dst->data_blocks_tail->h.xor = tail_prev ^ (uintptr_t)src; 320 | 321 | dst->data_blocks_head->h.xor = (uintptr_t)src ^ head_next; 322 | } 323 | dst->data_blocks_tail = src; 324 | } 325 | else 326 | { 327 | dst->data_blocks_head = src; 328 | dst->data_blocks_tail = src; 329 | } 330 | } 331 | 332 | static struct fdatablock *pop_fdatablock_from_fnode(struct imfs *fs, 333 | struct fnode *src) 334 | { 335 | assert(fs && 336 | FNODE_IS_VALID(fs, src) && !FNODE_IS_FREE(fs, src)); 337 | 338 | struct fdatablock *block = src->data_blocks_tail; 339 | 340 | assert((src->data_blocks_head && src->data_blocks_tail) || 341 | (!src->data_blocks_head && !src->data_blocks_tail)); 342 | 343 | struct fdatablock *tail_prev; 344 | uintptr_t tail_prev_prev; 345 | 346 | if (src->data_blocks_tail) 347 | { 348 | uintptr_t head_next = src->data_blocks_head->h.xor ^ 349 | (uintptr_t)src->data_blocks_tail; 350 | 351 | if (src->data_blocks_tail == src->data_blocks_head) 352 | src->data_blocks_tail = src->data_blocks_head = NULL; 353 | else if (head_next == (uintptr_t)src->data_blocks_tail) 354 | src->data_blocks_tail = src->data_blocks_head; 355 | else 356 | { 357 | tail_prev = (struct fdatablock *) 358 | (src->data_blocks_tail->h.xor ^ 359 | (uintptr_t)src->data_blocks_head); 360 | 361 | tail_prev_prev = tail_prev->h.xor ^ 362 | (uintptr_t)src->data_blocks_tail; 363 | 364 | tail_prev->h.xor = tail_prev_prev ^ 365 | (uintptr_t)src->data_blocks_head; 366 | 367 | src->data_blocks_head->h.xor = 368 | (uintptr_t)tail_prev ^ head_next; 369 | 370 | src->data_blocks_tail = tail_prev; 371 | } 372 | src->last_block_used = IMFS_DATA_BLOCK_SIZE; 373 | } 374 | 375 | return block; 376 | } 377 | 378 | static long append_bytes_to_fnode(struct imfs *fs, struct fnode *f, 379 | const void *buf, size_t len, size_t alignment) 380 | { 381 | assert(fs && buf && len <= LONG_MAX && 382 | alignment < IMFS_DATA_BLOCK_SIZE && 383 | (alignment & (alignment - 1UL)) == 0 && 384 | FNODE_IS_VALID(fs, f) && !FNODE_IS_FREE(fs, f)); 385 | 386 | if (0 == len) return 0; 387 | if (!f->data_blocks_tail) 388 | { 389 | struct fdatablock *first = alloc_fdatablock(fs); 390 | if (!first) return -1; 391 | append_fdatablock_to_fnode(fs, f, first); 392 | } 393 | assert(f->data_blocks_tail && f->data_blocks_head); 394 | 395 | // The first write is preceded by the alignment 396 | size_t pad = -(uintptr_t) 397 | (&f->data_blocks_tail->data[f->last_block_used]) & 398 | (alignment - 1UL); 399 | if (f->last_block_used + pad < IMFS_DATA_BLOCK_SIZE) 400 | f->last_block_used += pad; 401 | else 402 | { 403 | struct fdatablock *new = alloc_fdatablock(fs); 404 | if (!new) return -1; 405 | append_fdatablock_to_fnode(fs, f, new); 406 | pad = -(uintptr_t) 407 | (&f->data_blocks_tail->data[f->last_block_used]) & 408 | (alignment - 1UL); 409 | assert(pad < IMFS_DATA_BLOCK_SIZE); 410 | f->last_block_used += pad; 411 | } 412 | 413 | char *cbuf = (char *)buf; 414 | size_t tot_written = 0; 415 | 416 | size_t avlbl = IMFS_DATA_BLOCK_SIZE - f->last_block_used; 417 | size_t to_write = len - tot_written; 418 | size_t w_len = avlbl < to_write ? avlbl : to_write; 419 | memcpy(&f->data_blocks_tail->data[f->last_block_used], 420 | &cbuf[tot_written], w_len); 421 | tot_written += w_len; 422 | f->last_block_used += w_len; 423 | 424 | while (tot_written < len) 425 | { 426 | struct fdatablock *new = alloc_fdatablock(fs); 427 | if (!new) return -1; 428 | append_fdatablock_to_fnode(fs, f, new); 429 | avlbl = IMFS_DATA_BLOCK_SIZE - f->last_block_used; 430 | to_write = len - tot_written; 431 | w_len = avlbl < to_write ? avlbl : to_write; 432 | memcpy(&f->data_blocks_tail->data[f->last_block_used], 433 | &cbuf[tot_written], w_len); 434 | tot_written += w_len; 435 | f->last_block_used += w_len; 436 | } 437 | 438 | return (long)tot_written; 439 | } 440 | 441 | /* ------------------------------------------------------------------------- */ 442 | 443 | #define FILE_IS_VALID(fs, s) \ 444 | ({ \ 445 | struct imfs *fs_ = fs; \ 446 | uintptr_t s_ = (uintptr_t)s; \ 447 | uintptr_t start_ = \ 448 | (uintptr_t)&fs_->files[0]; \ 449 | uintptr_t end_ = \ 450 | (uintptr_t)&fs_->files[fs_->files_len-1]; \ 451 | s_ >= start_ && s_ <= end_ && \ 452 | (s_ - start_) % sizeof(struct file) == 0; \ 453 | }) 454 | #define FILE_IS_FREE(fs, s) \ 455 | FILE_IS_VALID(fs, \ 456 | ((struct file *)s)->next) 457 | 458 | static struct file *alloc_file(struct imfs *fs) 459 | { 460 | assert(fs); 461 | 462 | struct file *new = fs->files_cfl_tail; 463 | 464 | if (fs->files_cfl_tail) 465 | { 466 | if (fs->files_cfl_tail == 467 | fs->files_cfl_tail->next) 468 | { 469 | fs->files_cfl_tail = NULL; 470 | } 471 | else 472 | { 473 | new = fs->files_cfl_tail->next; 474 | fs->files_cfl_tail->next = new->next; 475 | } 476 | new->next = NULL; 477 | } 478 | 479 | return new; 480 | } 481 | 482 | static void free_file(struct imfs *fs, struct file *files) 483 | { 484 | assert(fs && FILE_IS_VALID(fs, files)); 485 | 486 | if (FILE_IS_FREE(fs, files)) return; 487 | 488 | if (fs->files_cfl_tail) 489 | { 490 | files->next = fs->files_cfl_tail->next; 491 | fs->files_cfl_tail->next = files; 492 | } 493 | else 494 | { 495 | fs->files_cfl_tail = files; 496 | files->next = files; 497 | } 498 | } 499 | 500 | static unsigned int get_fileID(struct imfs *fs, struct file *file) 501 | { 502 | assert(fs && FILE_IS_VALID(fs, file)); 503 | return (unsigned int)(file - fs->files); 504 | } 505 | 506 | /* ------------------------------------------------------------------------- */ 507 | 508 | static size_t get_fnodeID(struct imfs *fs, struct fnode *f) 509 | { 510 | assert(fs && FNODE_IS_VALID(fs, f)); 511 | return (size_t)(f - fs->fn); 512 | } 513 | 514 | static int init_fnode_as_dir(struct imfs *fs, struct fnode *f, 515 | size_t parentID) 516 | { 517 | assert(fs && FNODE_IS_VALID(fs, f) && 518 | !FNODE_IS_FREE(fs, f) && parentID < fs->fn_len && 519 | !FNODE_IS_FREE(fs, &fs->fn[parentID])); 520 | 521 | f->type = IMFS_DIR; 522 | 523 | struct direlem init_dirs[] = 524 | { 525 | { 526 | .fnodeID = get_fnodeID(fs, f), 527 | .name_len = 1, 528 | .name = {'.'} 529 | }, 530 | { 531 | .fnodeID = parentID, 532 | .name_len = 2, 533 | .name = {'.', '.'} 534 | } 535 | }; 536 | 537 | if (append_bytes_to_fnode(fs, f, init_dirs, 538 | sizeof(init_dirs), _Alignof(init_dirs)) 539 | != (long)sizeof(init_dirs)) return -1; 540 | 541 | return 0; 542 | } 543 | 544 | static struct direlem * 545 | search_son_in_dir(struct imfs *fs, struct fnode *dir, 546 | const char *son_name, size_t name_len) 547 | { 548 | assert(fs && FNODE_IS_VALID(fs, dir) && 549 | !FNODE_IS_FREE(fs, dir) 550 | && dir->type == IMFS_DIR && 551 | son_name && name_len <= IMFS_MAX_NAME_LEN); 552 | 553 | const size_t lbu = dir->last_block_used; 554 | struct fdatablock *prev = dir->data_blocks_tail; 555 | struct fdatablock *curr = dir->data_blocks_head; 556 | struct fdatablock *next = NULL; 557 | 558 | assert((!curr && !prev) || 559 | (FDATABLOCK_IS_VALID(fs, curr) && 560 | FDATABLOCK_IS_VALID(fs, prev) && 561 | !FDATABLOCK_IS_FREE(curr) && 562 | !FDATABLOCK_IS_FREE(prev))); 563 | 564 | bool l; 565 | struct direlem *dirs; 566 | size_t numdirelem; 567 | size_t i; 568 | 569 | while (next != dir->data_blocks_head) 570 | { 571 | next = (struct fdatablock *) 572 | (curr->h.xor ^ (uintptr_t)prev); 573 | l = next == dir->data_blocks_head; 574 | numdirelem = 575 | l * (lbu / sizeof(struct direlem)) + 576 | (1 - l) * DIRELEM_PER_FDB; 577 | dirs = (struct direlem *) 578 | ALIGN_ADDR_POW2(&curr->data, 579 | _Alignof(struct direlem)); 580 | 581 | i = 0; 582 | 583 | while (i < numdirelem && 584 | (dirs[i].name_len != name_len || 585 | memcmp(dirs[i].name, son_name, 586 | dirs[i].name_len))) 587 | i++; 588 | 589 | if (i < numdirelem) return &dirs[i]; 590 | else 591 | { 592 | prev = curr; 593 | curr = next; 594 | } 595 | } 596 | 597 | return NULL; 598 | } 599 | 600 | 601 | /* 602 | * Given a [pathname] returns a pointer to a remaining slice of 603 | * the path - that is a string corresponding to a file or a 604 | * directory name - or NULL if some error occurred. 605 | * If [parent] is true the path traversal is stopped to the penultimate 606 | * element and the returned pointer points to the first character of the 607 | * last element of the path. Correctness of the last element is still verified. 608 | * If [parent] is false the path traversal is executed completely. 609 | * Additionally, if completed with no errors: 610 | * - *fnodeID will contain the ID of the fnode corresponding to the last 611 | * analyzed element of the path 612 | * - *last_len will contain the length of the remaining not analyzed 613 | * string slice ('\0' NOT counted) 614 | * 615 | * COMMENTS: 616 | * Given that a VALID pathname is used for: 617 | * - creating a file, in the form "/folder1/folder2/new-file-name" 618 | * - opening a file, in the form "/folder1/folder2/file-name" 619 | * - deleting a file, in the form "/folder1/folder2/file-name" 620 | * - creating a directory in a parent directory, in the form 621 | * "/folder1/folder2/new-folder-name" 622 | * - deleting a directory in a parent directory, in the form 623 | * "/folder1/folder2/folder-name" 624 | * 625 | * we have different needs in use cases. 626 | * While creating a new file or a new directory we want this function 627 | * to return the fnodeID of the parent directory, when opening a file, 628 | * deleting a file or deleting a directory we want the fnodeID of 629 | * the file/directory itself (it already exists). 630 | * Let's use a flag "parent" that will stop the traversal to the parent 631 | */ 632 | static char *pathname_lookup(struct imfs *fs, const char *pathname, 633 | bool parent, size_t *fnodeID, size_t *last_len) 634 | { 635 | assert(fs && pathname && fnodeID && last_len && 636 | !FNODE_IS_FREE(fs, &fs->fn[ROOT_DIR_FNODEID]) && 637 | fs->fn[ROOT_DIR_FNODEID].type == IMFS_DIR); 638 | 639 | if (*pathname != '/') return NULL; 640 | 641 | char *last_head = (char *)pathname + 1; 642 | char *p = last_head; 643 | size_t fnID = ROOT_DIR_FNODEID; 644 | 645 | do 646 | { 647 | while ('\0' != *p && '/' != *p && 648 | p - last_head <= IMFS_MAX_NAME_LEN) 649 | p++; 650 | 651 | if (p - last_head > IMFS_MAX_NAME_LEN) 652 | { 653 | last_head = NULL; 654 | break; 655 | } 656 | else if ('\0' == *p) 657 | { 658 | // End of pathname reached. 659 | // If [parent] is true, we have to just check 660 | // that the last element name is correct, 661 | // otherwise we need to search for the element 662 | // name in the fnID fnode and retreive it's fnodeID. 663 | if (parent && p - last_head > 0) break; 664 | else if (parent && p - last_head <= 0) 665 | { 666 | last_head = NULL; 667 | break; 668 | } 669 | else 670 | { 671 | assert(fnID < fs->fn_len && 672 | !FNODE_IS_FREE(fs, &fs->fn[fnID]) && 673 | fs->fn[fnID].type == IMFS_DIR); 674 | 675 | struct direlem *d = 676 | search_son_in_dir(fs, &fs->fn[fnID], 677 | last_head, p - last_head); 678 | if (!d) 679 | { 680 | last_head = NULL; 681 | break; 682 | } 683 | else 684 | { 685 | fnID = d->fnodeID; 686 | assert(fnID < fs->fn_len && 687 | !FNODE_IS_FREE(fs, &fs->fn[fnID])); 688 | last_head = p = p + 1; 689 | break; 690 | } 691 | } 692 | } 693 | else // '/' == *p 694 | { 695 | // End of element reached correctly. 696 | // We need to search for the element name in 697 | // the fnID fnode and retreive it's fnodeID. 698 | assert(fnID < fs->fn_len && 699 | !FNODE_IS_FREE(fs, &fs->fn[fnID]) && 700 | fs->fn[fnID].type == IMFS_DIR); 701 | // Let's search 702 | struct direlem *d = 703 | search_son_in_dir(fs, &fs->fn[fnID], 704 | last_head, p - last_head); 705 | // This search can fail both because there is 706 | // no element with such name or because it's a 707 | // file and not a dir 708 | if (!d) 709 | { 710 | last_head = NULL; 711 | break; 712 | } 713 | else 714 | { 715 | fnID = d->fnodeID; 716 | assert(fnID < fs->fn_len && 717 | !FNODE_IS_FREE(fs, &fs->fn[fnID])); 718 | 719 | if (fs->fn[d->fnodeID].type != IMFS_DIR) 720 | { 721 | last_head = NULL; 722 | break; 723 | } 724 | last_head = p = p + 1; 725 | } 726 | } 727 | } while (true); 728 | 729 | *fnodeID = fnID; 730 | *last_len = p - last_head; 731 | return last_head; 732 | } 733 | 734 | /* 735 | * Check if fnode has to be freed due to link_count 736 | * and open_count reached zero. This is a macro 737 | * because it is used in places where the assertion 738 | * of the validity of its parameters is already done. 739 | */ 740 | #define MAYBE_FREE_FNODE(fs, fID) do \ 741 | { \ 742 | if (fs->fn[fID].link_count == 0 && \ 743 | fs->fn[fID].open_count == 0) \ 744 | { \ 745 | struct fdatablock *p; \ 746 | while ((p = pop_fdatablock_from_fnode(fs, \ 747 | &fs->fn[fID]))) \ 748 | free_fdatablock(fs, p); \ 749 | free_fnode(fs, &fs->fn[fID]); \ 750 | } \ 751 | } while (0) 752 | 753 | int imfs_mkdir(struct imfs *fs, const char *pathname) 754 | { 755 | if (!fs || !pathname) return -1; 756 | 757 | size_t parentID; 758 | size_t last_len; 759 | char *last = pathname_lookup(fs, pathname, 760 | true, &parentID, &last_len); 761 | if (!last) return -1; 762 | 763 | assert(last_len <= IMFS_MAX_NAME_LEN 764 | && parentID < fs->fn_len && 765 | !FNODE_IS_FREE(fs, &fs->fn[parentID]) && 766 | fs->fn[parentID].type == IMFS_DIR); 767 | 768 | // Before actually create newdir we need to check 769 | // if it is already present a file or another dir 770 | // with the same name 771 | size_t newdirID; 772 | if (search_son_in_dir(fs, &fs->fn[parentID], 773 | last, last_len)) return -1; 774 | 775 | struct fnode *newdir = alloc_fnode(fs); 776 | if (!newdir) return -1; 777 | newdirID = get_fnodeID(fs, newdir); 778 | init_fnode_as_dir(fs, newdir, parentID); 779 | 780 | struct direlem de = 781 | { 782 | .fnodeID = newdirID, 783 | .name_len = last_len 784 | }; 785 | strncpy(de.name, last, IMFS_MAX_NAME_LEN); 786 | 787 | if (append_bytes_to_fnode(fs, &fs->fn[parentID], 788 | &de, sizeof(de), _Alignof(de)) 789 | != (long)sizeof(de)) return -1; 790 | 791 | return 0; 792 | } 793 | 794 | int imfs_rmdir(struct imfs *fs, const char *pathname) 795 | { 796 | if (!fs || !pathname) return -1; 797 | 798 | size_t pID; 799 | size_t last_len; 800 | char *last = pathname_lookup(fs, pathname, 801 | true, &pID, &last_len); 802 | if (!last) return -1; 803 | 804 | assert(last_len <= IMFS_MAX_NAME_LEN 805 | && pID < fs->fn_len && 806 | !FNODE_IS_FREE(fs, &fs->fn[pID]) && 807 | fs->fn[pID].type == IMFS_DIR); 808 | 809 | struct direlem *d = 810 | search_son_in_dir(fs, &fs->fn[pID], 811 | last, last_len); 812 | if (!d) return -1; 813 | 814 | size_t fID = d->fnodeID; 815 | 816 | assert(fID < fs->fn_len && 817 | !FNODE_IS_FREE(fs, &fs->fn[fID])); 818 | 819 | // Special direlems "." and ".." (curr and prev 820 | // directories) can't be removed by this 821 | // function. Since both names starts with '.' 822 | // it's simple as checking the first char value. 823 | if (fs->fn[fID].type != IMFS_DIR || 824 | '.' == d->name[0]) 825 | return -1; 826 | 827 | assert(fs->fn[fID].data_blocks_head && 828 | fs->fn[fID].data_blocks_tail && 829 | fs->fn[fID].last_block_used >= 830 | 2 * sizeof(struct direlem)); 831 | 832 | #define dirs ((struct direlem *) \ 833 | fs->fn[fID].data_blocks_head->data) 834 | // First two direlem are always expected 835 | // to be "." and ".." 836 | assert(dirs[0].name_len == 1 && 837 | dirs[0].name[0] == '.' && 838 | dirs[1].name_len == 2 && 839 | dirs[1].name[0] == '.' && 840 | dirs[1].name[1] == '.'); 841 | #undef dirs 842 | 843 | // The dir can be removed only if it's empty. 844 | // This means having no more that one allocated 845 | // fdatablock and that only "." and ".." 846 | // direlems are present. 847 | if (fs->fn[fID].data_blocks_head != 848 | fs->fn[fID].data_blocks_tail || 849 | fs->fn[fID].last_block_used > 850 | 2 * sizeof(struct direlem)) 851 | return -1; 852 | 853 | // Free the fID fnode and its fdatablocks 854 | struct fdatablock *p; 855 | while ((p = pop_fdatablock_from_fnode(fs, 856 | &fs->fn[fID]))) 857 | free_fdatablock(fs, p); 858 | free_fnode(fs, &fs->fn[fID]); 859 | 860 | // Delete the direlem d from fs->fn[pID] 861 | // by cutting the last direlem from fs->fn[pID] 862 | // and pasting in the place of d. 863 | const size_t sd = sizeof(struct direlem); 864 | size_t lbu = fs->fn[pID].last_block_used; 865 | assert(lbu >= sd); 866 | size_t idx = lbu - sd; 867 | struct direlem *s = (struct direlem *) 868 | &fs->fn[pID].data_blocks_tail->data[idx]; 869 | 870 | if (d != s) memcpy(d, s, sd); 871 | fs->fn[pID].last_block_used -= sd; 872 | 873 | if (fs->fn[pID].last_block_used == 0) 874 | { 875 | struct fdatablock *p = 876 | pop_fdatablock_from_fnode(fs, 877 | &fs->fn[pID]); 878 | assert(p); 879 | free_fdatablock(fs, p); 880 | } 881 | 882 | return 0; 883 | } 884 | 885 | int imfs_open(struct imfs *fs, const char *pathname, int flags) 886 | { 887 | if (!fs || !pathname) return -1; 888 | 889 | size_t fID; 890 | size_t len; 891 | bool create = flags & IMFS_CREAT; 892 | bool rdwr = flags & IMFS_RDWR; 893 | char *last = pathname_lookup(fs ,pathname, create, 894 | &fID, &len); 895 | if (!last) return -1; 896 | 897 | assert(len <= IMFS_MAX_NAME_LEN && fID < fs->fn_len && 898 | !FNODE_IS_FREE(fs, &fs->fn[fID])); 899 | 900 | if (create) 901 | { 902 | // If create is true we have the fnodeID of 903 | // the parent directory 904 | assert(fs->fn[fID].type == IMFS_DIR); 905 | 906 | // Check if file already exists 907 | struct direlem *d = search_son_in_dir(fs, &fs->fn[fID], 908 | last, len); 909 | if (d) fID = d->fnodeID; 910 | else 911 | { 912 | struct fnode *newfilenode = alloc_fnode(fs); 913 | if (!newfilenode) return -1; 914 | newfilenode->type = IMFS_FILE; 915 | 916 | struct direlem newfile = { 917 | .fnodeID = get_fnodeID(fs, newfilenode), 918 | .name_len = len 919 | }; 920 | strncpy(newfile.name, last, IMFS_MAX_NAME_LEN); 921 | 922 | if(append_bytes_to_fnode(fs, &fs->fn[fID], &newfile, 923 | sizeof(newfile), _Alignof(newfile)) 924 | != (long)sizeof(newfile)) return -1; 925 | 926 | fID = newfile.fnodeID; 927 | } 928 | // Increment the link count 929 | fs->fn[fID].link_count++; 930 | } 931 | 932 | // fID contains the ID of the file 933 | assert(fID < fs->fn_len && 934 | !FNODE_IS_FREE(fs, &fs->fn[fID]) && 935 | fs->fn[fID].type == IMFS_FILE); 936 | 937 | if (fs->fn[fID].open_count == USHRT_MAX) 938 | return -1; 939 | 940 | struct file *file = alloc_file(fs); 941 | if (!file) return -1; 942 | 943 | if (flags & IMFS_TRUNC && rdwr) 944 | { 945 | // File truncation 946 | // NOTE: Consider optimizing with one function that 947 | // frees all the block of the file apart from the first 948 | struct fdatablock *p; 949 | while ((p = pop_fdatablock_from_fnode(fs, &fs->fn[fID]))) 950 | free_fdatablock(fs, p); 951 | } 952 | 953 | file->fnodeID = fID; 954 | file->read_ptr.curr = NULL; 955 | file->read_ptr.prev = NULL; 956 | file->read_ptr.b_index = 0; 957 | file->readonly = !rdwr; 958 | 959 | // Increment the open files count 960 | fs->fn[fID].open_count++; 961 | 962 | return get_fileID(fs, file) + 1; 963 | } 964 | 965 | int imfs_close(struct imfs *fs, int fd) 966 | { 967 | fd -= 1U; 968 | if(!fs || fd < 0 || 969 | (size_t)fd >= fs->files_len || 970 | FILE_IS_FREE(fs, &fs->files[fd])) 971 | return -1; 972 | 973 | size_t fID = fs->files[fd].fnodeID; 974 | 975 | assert(fID < fs->fn_len && 976 | !FNODE_IS_FREE(fs, &fs->fn[fID])); 977 | 978 | fs->fn[fID].open_count--; 979 | 980 | MAYBE_FREE_FNODE(fs, fID); 981 | 982 | free_file(fs, &fs->files[fd]); 983 | 984 | return 0; 985 | } 986 | 987 | long imfs_read(struct imfs *fs, int fd, void *buf, size_t count) 988 | { 989 | fd -= 1U; 990 | if(!fs || !buf || fd < 0 || 991 | (size_t)fd >= fs->files_len || 992 | FILE_IS_FREE(fs, &fs->files[fd])) 993 | return -1; 994 | 995 | size_t fID = fs->files[fd].fnodeID; 996 | 997 | assert(fID < fs->fn_len); 998 | 999 | // Maybe someone deleted the file after we opened it 1000 | if (FNODE_IS_FREE(fs, &fs->fn[fID])) 1001 | return -1; 1002 | 1003 | assert(fs->fn[fID].type == IMFS_FILE); 1004 | 1005 | size_t tot_read = 0; 1006 | char *cbuf = (char *)buf; 1007 | 1008 | // Always try to set the read pointer if it is still NULL 1009 | // This is the case in the following scenario: 1010 | // 1) Open a new empty file or Open one with IMFS_TRUNC 1011 | // 2) Write some bytes to it 1012 | // 3) Read some bytes from it 1013 | if (!fs->files[fd].read_ptr.curr) 1014 | { 1015 | fs->files[fd].read_ptr.curr = fs->fn[fID].data_blocks_head; 1016 | fs->files[fd].read_ptr.prev = fs->fn[fID].data_blocks_tail; 1017 | } 1018 | // Update the prev pointer to the new tail when curr still points 1019 | // to the head but previous writes had created a new tail. 1020 | if (fs->files[fd].read_ptr.curr == fs->fn[fID].data_blocks_head && 1021 | fs->files[fd].read_ptr.prev != fs->fn[fID].data_blocks_tail) 1022 | { 1023 | fs->files[fd].read_ptr.prev = fs->fn[fID].data_blocks_tail; 1024 | } 1025 | 1026 | 1027 | struct fdatablock *r_curr = fs->files[fd].read_ptr.curr; 1028 | struct fdatablock *r_prev = fs->files[fd].read_ptr.prev; 1029 | struct fdatablock *r_next = NULL; 1030 | size_t r_b_index = fs->files[fd].read_ptr.b_index; 1031 | 1032 | assert((!r_curr && !r_prev) || 1033 | (FDATABLOCK_IS_VALID(fs, r_curr) && 1034 | FDATABLOCK_IS_VALID(fs, r_prev) && 1035 | !FDATABLOCK_IS_FREE(r_curr) && 1036 | !FDATABLOCK_IS_FREE(r_prev))); 1037 | 1038 | char last; 1039 | size_t block_size; 1040 | size_t bytes_avlbl; 1041 | size_t to_read; 1042 | size_t r_len; 1043 | 1044 | while (r_next != fs->fn[fID].data_blocks_head) 1045 | { 1046 | r_next = (struct fdatablock *) 1047 | (r_curr->h.xor ^ (uintptr_t)r_prev); 1048 | assert(FDATABLOCK_IS_VALID(fs, r_next) && 1049 | !FDATABLOCK_IS_FREE(r_next)); 1050 | 1051 | last = r_next == fs->fn[fID].data_blocks_head; 1052 | block_size = last * fs->fn[fID].last_block_used + 1053 | (1 - last) * IMFS_DATA_BLOCK_SIZE; 1054 | 1055 | assert(r_b_index <= block_size); 1056 | 1057 | bytes_avlbl = block_size - r_b_index; 1058 | to_read = count - tot_read; 1059 | r_len = to_read < bytes_avlbl ? to_read : bytes_avlbl; 1060 | memcpy(&cbuf[tot_read], &r_curr->data[r_b_index], r_len); 1061 | tot_read += r_len; 1062 | r_b_index += r_len; 1063 | 1064 | // Update pointer 1065 | if (r_b_index == IMFS_DATA_BLOCK_SIZE && !last) 1066 | { 1067 | r_b_index = 0; 1068 | r_prev = r_curr; 1069 | r_curr = r_next; 1070 | } 1071 | else break; 1072 | } 1073 | 1074 | // Update the read pointer 1075 | fs->files[fd].read_ptr.curr = r_curr; 1076 | fs->files[fd].read_ptr.prev = r_prev; 1077 | fs->files[fd].read_ptr.b_index = r_b_index; 1078 | 1079 | return tot_read; 1080 | } 1081 | 1082 | long imfs_write(struct imfs *fs, int fd, const void *buf, size_t count) 1083 | { 1084 | fd -= 1U; 1085 | if(!fs || !buf || fd < 0 || 1086 | (size_t)fd >= fs->files_len || 1087 | FILE_IS_FREE(fs, &fs->files[fd]) || 1088 | fs->files[fd].readonly) 1089 | return -1; 1090 | 1091 | size_t fID = fs->files[fd].fnodeID; 1092 | 1093 | assert(fID < fs->fn_len); 1094 | 1095 | // Maybe someone deleted the file after we opened it 1096 | if (FNODE_IS_FREE(fs, &fs->fn[fID])) 1097 | return -1; 1098 | 1099 | assert(fs->fn[fID].type == IMFS_FILE); 1100 | 1101 | return append_bytes_to_fnode(fs, &fs->fn[fID], buf, count, 1); 1102 | } 1103 | 1104 | struct imfs * 1105 | imfs_init(char *base, size_t size, struct imfs_conf *conf, bool format) 1106 | { 1107 | struct imfs *fs = (struct imfs *) 1108 | ALIGN_ADDR_POW2(base, _Alignof(struct imfs)); 1109 | 1110 | if (format || fs->magic != IMFS_MAGIC) 1111 | { 1112 | if(!conf || conf->max_opened_files >= INT_MAX) return NULL; 1113 | 1114 | struct file *files = (struct file *) 1115 | ALIGN_ADDR_POW2(fs + 1, _Alignof(struct file)); 1116 | 1117 | struct fnode *fnodes = (struct fnode *) 1118 | ALIGN_ADDR_POW2(files + conf->max_opened_files, 1119 | _Alignof(struct fnode)); 1120 | 1121 | struct fdatablock *fdatablocks = (struct fdatablock *) 1122 | ALIGN_ADDR_POW2(fnodes + conf->max_num_fnodes + 1, 1123 | _Alignof(struct fdatablock)); 1124 | 1125 | // Invalid fs descriptor is returned if 1126 | // we have no space for at least one fdatablock 1127 | if (((uintptr_t)base + size) <= 1128 | (uintptr_t)(fdatablocks+1)) return NULL; 1129 | 1130 | fs->magic = IMFS_MAGIC; 1131 | fs->mem_size = size; 1132 | fs->files = files; 1133 | fs->files_len = conf->max_opened_files; 1134 | fs->fn = fnodes; 1135 | fs->fn_len = conf->max_num_fnodes + 1; 1136 | fs->fb = fdatablocks; 1137 | fs->fb_len = ((uintptr_t)base + size - 1138 | (uintptr_t)fdatablocks) / 1139 | sizeof(struct fdatablock); 1140 | 1141 | // Initialize the fnode circular freelist 1142 | for (size_t i = 0; i < fs->fn_len-1; i++) 1143 | fs->fn[i].next = &fs->fn[i+1]; 1144 | 1145 | fs->fn[fs->fn_len-1].next = &fs->fn[0]; 1146 | fs->fn_cfl_tail = &fs->fn[fs->fn_len-1]; 1147 | 1148 | // Initialize the fdatablocks freelist 1149 | for (size_t i = 0; i < fs->fb_len-1; i++) 1150 | fs->fb[i].h.next = (struct fdatablock *) 1151 | ((uintptr_t)&fs->fb[i+1] | (uintptr_t)1); 1152 | 1153 | fs->fb[fs->fb_len-1].h.next = (struct fdatablock *) 1154 | ((uintptr_t)NULL | (uintptr_t)1); 1155 | fs->fb_fl_head = &fs->fb[0]; 1156 | 1157 | assert(_Alignof(fs->files) == _Alignof(struct file)); 1158 | assert(_Alignof(fs->files_cfl_tail) == _Alignof(struct file)); 1159 | assert(_Alignof(fs->fn) == _Alignof(struct fnode)); 1160 | assert(_Alignof(fs->fn_cfl_tail) == _Alignof(struct fnode)); 1161 | assert(_Alignof(fs->fb) == _Alignof(struct fdatablock)); 1162 | assert(_Alignof(fs->fb_fl_head) == _Alignof(struct fdatablock)); 1163 | 1164 | // Init the root directory 1165 | struct fnode *root = alloc_fnode(fs); 1166 | if (!root) return NULL; 1167 | assert (get_fnodeID(fs, root) == ROOT_DIR_FNODEID); 1168 | init_fnode_as_dir(fs, root, ROOT_DIR_FNODEID); 1169 | } 1170 | 1171 | // Initialize the file circular freelist. 1172 | // This is done even in the case of a mount. 1173 | for (size_t i = 0; i < fs->files_len-1; i++) 1174 | fs->files[i].next = &fs->files[i+1]; 1175 | 1176 | fs->files[fs->files_len-1].next = &fs->files[0]; 1177 | fs->files_cfl_tail = &fs->files[fs->files_len-1]; 1178 | 1179 | return fs; 1180 | } 1181 | 1182 | int imfs_link(struct imfs *fs, const char *oldpath, const char *newpath) 1183 | { 1184 | if (!fs || !oldpath || !newpath) return -1; 1185 | 1186 | size_t oldfID; 1187 | size_t newlen; 1188 | if (!pathname_lookup(fs, oldpath, false, &oldfID, &newlen)) 1189 | return -1; 1190 | 1191 | assert(oldfID < fs->fn_len && !FNODE_IS_FREE(fs, &fs->fn[oldfID])); 1192 | 1193 | if (fs->fn[oldfID].type != IMFS_FILE || 1194 | fs->fn[oldfID].link_count == USHRT_MAX) return -1; 1195 | 1196 | size_t newfID; 1197 | char *last = pathname_lookup(fs, newpath, true, &newfID, &newlen); 1198 | if (!last) return -1; 1199 | 1200 | assert(newlen <= IMFS_MAX_NAME_LEN && newfID < fs->fn_len && 1201 | !FNODE_IS_FREE(fs, &fs->fn[newfID]) && 1202 | fs->fn[newfID].type == IMFS_DIR); 1203 | 1204 | if (search_son_in_dir(fs, &fs->fn[newfID], last, newlen)) 1205 | return -1; 1206 | 1207 | struct direlem newfile = { 1208 | .fnodeID = oldfID, 1209 | .name_len = newlen 1210 | }; 1211 | strncpy(newfile.name, last, IMFS_MAX_NAME_LEN); 1212 | 1213 | if(append_bytes_to_fnode(fs, &fs->fn[newfID], &newfile, 1214 | sizeof(newfile), _Alignof(newfile)) 1215 | != (long)sizeof(newfile)) return -1; 1216 | 1217 | // Increment the link count 1218 | fs->fn[oldfID].link_count++; 1219 | 1220 | return 0; 1221 | } 1222 | 1223 | int imfs_unlink(struct imfs *fs, const char *pathname) 1224 | { 1225 | if (!fs || !pathname) return -1; 1226 | 1227 | size_t pID, len; 1228 | char *last = pathname_lookup(fs, pathname, true, 1229 | &pID, &len); 1230 | if (!last) return -1; 1231 | 1232 | assert(len <= IMFS_MAX_NAME_LEN && 1233 | pID < fs->fn_len && 1234 | !FNODE_IS_FREE(fs, &fs->fn[pID]) && 1235 | fs->fn[pID].type == IMFS_DIR); 1236 | 1237 | struct direlem *d = 1238 | search_son_in_dir(fs, &fs->fn[pID], last, 1239 | len); 1240 | if (!d) return -1; 1241 | 1242 | size_t fID = d->fnodeID; 1243 | 1244 | assert(fID < fs->fn_len && 1245 | !FNODE_IS_FREE(fs, &fs->fn[fID])); 1246 | 1247 | if (fs->fn[fID].type != IMFS_FILE) 1248 | return -1; 1249 | 1250 | // Delete the direlem d from fs->fn[pID] 1251 | // by cutting the last direlem from fs->fn[pID] 1252 | // and pasting in the place of d. 1253 | const size_t sd = sizeof(struct direlem); 1254 | size_t lbu = fs->fn[pID].last_block_used; 1255 | assert(lbu >= sd); 1256 | size_t idx = lbu - sd; 1257 | struct direlem *s = (struct direlem *) 1258 | &fs->fn[pID].data_blocks_tail->data[idx]; 1259 | 1260 | if (d != s) memcpy(d, s, sd); 1261 | fs->fn[pID].last_block_used -= sd; 1262 | 1263 | if (fs->fn[pID].last_block_used == 0) 1264 | { 1265 | struct fdatablock *p = 1266 | pop_fdatablock_from_fnode(fs, 1267 | &fs->fn[pID]); 1268 | assert(p); 1269 | free_fdatablock(fs, p); 1270 | } 1271 | 1272 | fs->fn[fID].link_count--; 1273 | 1274 | MAYBE_FREE_FNODE(fs, fID); 1275 | 1276 | return 0; 1277 | } 1278 | -------------------------------------------------------------------------------- /imfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Raffaele del Gaudio 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 5 | * and associated documentation files (the “Software”), to deal in the Software without restriction, 6 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 8 | * subject to the following conditions: 9 | * The above copyright notice and this permission notice shall be included in all copies or substantial 10 | * portions of the Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 13 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 16 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #ifndef IMFS_H 20 | #define IMFS_H 21 | 22 | #include 23 | #include 24 | 25 | /* 26 | * The fixed size of a data block allocated to a file. 27 | * By tweaking this parameter, you can adjust the tradeoff 28 | * between the ratio of useful bytes (payload) to total bytes 29 | * (payload + metadata) and the amount of wasted space. 30 | * Larger blocks increase the useful-to-total byte ratio but 31 | * may lead to more wasted space for small files, while smaller 32 | * blocks reduce waste but decrease the efficiency in terms of 33 | * useful-to-total byte ratio. 256 Bytes per block seems a 34 | * good value because it leads to a minimum of 97% efficiency 35 | * considering that any datablock has one pointer of overhead. 36 | * NOTE: This is a minimum value that is rounded to a multiple 37 | * of the direlem struct size 38 | */ 39 | #define IMFS_MIN_DATA_BLOCK_SIZE_POW2 8 40 | 41 | /* 42 | * The max length for a file or a directory name 43 | */ 44 | #define IMFS_MAX_NAME_LEN 32 45 | 46 | struct imfs; 47 | 48 | struct imfs_conf 49 | { 50 | size_t max_num_fnodes; 51 | unsigned int max_opened_files; 52 | }; 53 | 54 | /* 55 | * File flags 56 | */ 57 | 58 | /* Open the file in read-only mode */ 59 | #define IMFS_RDONLY 0 60 | /* Open the file in read-write mode */ 61 | #define IMFS_RDWR 1 62 | /* Create the file if it does not exists */ 63 | #define IMFS_CREAT 2 64 | /* Truncate the file */ 65 | #define IMFS_TRUNC 4 66 | 67 | /* 68 | * Initialize a IMFS at [base] of size [size] with [conf] as configuration. 69 | * If [format] is false and a IMFS is already initialized at [base] 70 | * this function just returns the descriptor of the previous IMFS 71 | * (This acts as a "mount" operation). 72 | * If [format] is true then a new IMFS is created with no regards 73 | * of previous (if any) IMFS with [conf] as configuration. 74 | */ 75 | struct imfs * 76 | imfs_init(char *base, size_t size, struct imfs_conf *conf, bool format); 77 | 78 | int imfs_link(struct imfs *fs, const char *oldpath, const char *newpath); 79 | int imfs_unlink(struct imfs *fs, const char *pathname); 80 | 81 | int imfs_mkdir(struct imfs *fs, const char *pathname); 82 | int imfs_rmdir(struct imfs *fs, const char *pathname); 83 | 84 | int imfs_open(struct imfs *fs, const char *pathname, int flags); 85 | int imfs_close(struct imfs *fs, int fd); 86 | long imfs_read(struct imfs *fs, int fd, void *buf, size_t count); 87 | long imfs_write(struct imfs *fs, int fd, const void *buf, size_t count); 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /tests.c: -------------------------------------------------------------------------------- 1 | #include "imfs.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | char *buffer; 9 | size_t capacity; 10 | size_t size; 11 | size_t read_pos; 12 | size_t write_pos; 13 | } ByteQueue; 14 | 15 | void queue_init(ByteQueue *queue, char *buffer, size_t capacity) { 16 | queue->buffer = buffer; 17 | queue->capacity = capacity; 18 | queue->size = 0; 19 | queue->read_pos = 0; 20 | queue->write_pos = 0; 21 | } 22 | 23 | long queue_write(ByteQueue *queue, const char *data, size_t len) { 24 | if (len > queue->capacity - queue->size) { 25 | return -1; 26 | } 27 | 28 | size_t write_end = queue->capacity - queue->write_pos; 29 | if (len > write_end) { 30 | memcpy(queue->buffer + queue->write_pos, data, write_end); 31 | memcpy(queue->buffer, data + write_end, len - write_end); 32 | } else { 33 | memcpy(queue->buffer + queue->write_pos, data, len); 34 | } 35 | queue->write_pos = (queue->write_pos + len) % queue->capacity; 36 | queue->size += len; 37 | return len; 38 | } 39 | 40 | long queue_read(ByteQueue *queue, char *buffer, size_t len) { 41 | if (queue->size == 0) { 42 | return 0; 43 | } 44 | 45 | if (len > queue->size) { 46 | len = queue->size; 47 | } 48 | 49 | size_t read_end = queue->capacity - queue->read_pos; 50 | if (len > read_end) { 51 | memcpy(buffer, queue->buffer + queue->read_pos, read_end); 52 | memcpy(buffer + read_end, queue->buffer, len - read_end); 53 | } else { 54 | memcpy(buffer, queue->buffer + queue->read_pos, len); 55 | } 56 | queue->read_pos = (queue->read_pos + len) % queue->capacity; 57 | queue->size -= len; 58 | return len; 59 | } 60 | 61 | #define MAX_DIR_NAME_LENGTH IMFS_MAX_NAME_LEN 62 | #define MAX_DEPTH 50 63 | #define MAX_PATH_LENGTH \ 64 | ((MAX_DIR_NAME_LENGTH + 1) * MAX_DEPTH + 1) 65 | 66 | static char *random_name(char *buffer, size_t min_len, size_t max_len) { 67 | static const char charset[] = 68 | ".abcdefghijklmnopqrstu" 69 | "vwxyzABCDEFGHIJKLMNOPQ" 70 | "RSTUVWXYZ0123456789"; 71 | 72 | if (min_len > max_len) 73 | min_len = max_len; 74 | if (max_len > MAX_DIR_NAME_LENGTH) 75 | max_len = MAX_DIR_NAME_LENGTH; 76 | 77 | size_t len = min_len + rand() % (max_len - min_len + 1); 78 | 79 | for (size_t i = 0; i < len; i++) 80 | *buffer++ = charset[rand() % (sizeof(charset) - 1)]; 81 | 82 | *buffer = '\0'; 83 | 84 | return buffer; 85 | } 86 | 87 | static char *random_path(size_t max_depth) { 88 | static char buf[MAX_PATH_LENGTH]; 89 | *buf = '/'; 90 | size_t depth = rand() % max_depth % MAX_DEPTH + 1; 91 | char *p = buf + 1; 92 | for (size_t i = 0; i < depth-1; i++) { 93 | p = random_name(p, MAX_DIR_NAME_LENGTH/4, MAX_DIR_NAME_LENGTH); 94 | *p++ = '/'; 95 | } 96 | p = random_name(p, MAX_DIR_NAME_LENGTH/4, MAX_DIR_NAME_LENGTH); 97 | *p = '\0'; 98 | 99 | return buf; 100 | } 101 | 102 | #define MSIZE 256*1024*1024 103 | char base_mem[MSIZE]; 104 | char queue_mem[MSIZE]; 105 | char read_bufs[2][MSIZE]; 106 | 107 | static bool test_init(void) 108 | { 109 | memset(base_mem, 0, MSIZE); 110 | struct imfs_conf c = { 111 | .max_num_fnodes = rand() % 100, 112 | .max_opened_files = rand() % 100 113 | }; 114 | 115 | struct imfs *ssd = imfs_init(base_mem, MSIZE, &c, true); 116 | if (!ssd) return false; 117 | 118 | struct imfs *ssd1 = imfs_init(base_mem, MSIZE, &c, false); 119 | if (ssd != ssd1) return false; 120 | 121 | if(imfs_init(base_mem, MSIZE, NULL, false) != ssd) return false; 122 | if(imfs_init(base_mem, MSIZE, NULL, true)) return false; 123 | 124 | return true; 125 | } 126 | 127 | static bool test_big_read_write(void) 128 | { 129 | memset(base_mem, 0, MSIZE); 130 | struct imfs_conf c = { 131 | .max_num_fnodes = 1, 132 | .max_opened_files = 1 133 | }; 134 | 135 | struct imfs *ssd = imfs_init(base_mem, MSIZE, &c, true); 136 | if (!ssd) return false; 137 | 138 | int fd = imfs_open(ssd, "/file", IMFS_CREAT | IMFS_RDWR); 139 | if (fd < 0) return false; 140 | 141 | ByteQueue bq; 142 | queue_init(&bq, queue_mem, MSIZE); 143 | 144 | char *random_str = random_path(MAX_DEPTH); 145 | size_t len = strlen(random_str); 146 | 147 | long ss_res = imfs_read(ssd, fd, read_bufs[0], MSIZE); 148 | long bq_res = queue_read(&bq, read_bufs[1], MSIZE); 149 | if (ss_res != bq_res) return false; 150 | 151 | ss_res = imfs_write(ssd, fd, random_str, len); 152 | bq_res = queue_write(&bq, random_str, len); 153 | if (ss_res != bq_res) return false; 154 | 155 | ss_res = imfs_read(ssd, fd, read_bufs[0], MSIZE); 156 | bq_res = queue_read(&bq, read_bufs[1], MSIZE); 157 | if ((ss_res != bq_res) || 158 | (ss_res >= 0 && 159 | memcmp(read_bufs[0], read_bufs[1], ss_res))) 160 | return false; 161 | 162 | return true; 163 | } 164 | 165 | static bool test_one_M_random_read_write(void) 166 | { 167 | memset(base_mem, 0, MSIZE); 168 | struct imfs_conf c = { 169 | .max_num_fnodes = 1, 170 | .max_opened_files = 1 171 | }; 172 | 173 | struct imfs *ssd = imfs_init(base_mem, MSIZE, &c, true); 174 | if (!ssd) return false; 175 | 176 | int fd = imfs_open(ssd, "/file", IMFS_CREAT | IMFS_RDWR); 177 | if (fd < 0) return false; 178 | 179 | ByteQueue bq; 180 | queue_init(&bq, queue_mem, MSIZE); 181 | 182 | for (size_t i = 0; i < 1000000; i++) 183 | { 184 | if (rand() % 2) 185 | { 186 | // Read 187 | size_t r_size = rand() % MSIZE; 188 | long ss_res = imfs_read(ssd, fd, read_bufs[0], r_size); 189 | long bq_res = queue_read(&bq, read_bufs[1], r_size); 190 | if ((ss_res != bq_res) || 191 | (ss_res >= 0 && 192 | memcmp(read_bufs[0], read_bufs[1], ss_res))) 193 | { 194 | printf("++ Read failed with ret val %ld instead of %ld\n", ss_res, bq_res); 195 | return false; 196 | } 197 | } 198 | else 199 | { 200 | // Write 201 | char *random = random_path(MAX_DEPTH); 202 | size_t len = strlen(random) % 1024; 203 | long ss_res = imfs_write(ssd, fd, random, len); 204 | long bq_res = queue_write(&bq, random, len); 205 | if (ss_res != bq_res) 206 | { 207 | printf("++ Write failed with ret val %ld instead of %ld. Write len was %ld\n", ss_res, bq_res, len); 208 | return false; 209 | } 210 | } 211 | } 212 | 213 | return true; 214 | } 215 | 216 | static bool test_file_openings(void) 217 | { 218 | #define NUMOPFLS 100 219 | memset(base_mem, 0, MSIZE); 220 | struct imfs_conf c = { 221 | .max_num_fnodes = 1, 222 | .max_opened_files = NUMOPFLS 223 | }; 224 | 225 | struct imfs *ssd = imfs_init(base_mem, MSIZE, &c, true); 226 | if (!ssd) return false; 227 | 228 | int fds[NUMOPFLS]; 229 | 230 | for (size_t i = 0; i < NUMOPFLS; i++) 231 | fds[i] = imfs_open(ssd, "/file", IMFS_CREAT); 232 | 233 | if (fds[NUMOPFLS-1] != NUMOPFLS) 234 | { 235 | printf("++ last expected is %u but is %u\n", fds[NUMOPFLS-1], fds[NUMOPFLS-1]); 236 | return false; 237 | } 238 | 239 | if (imfs_open(ssd, "/file", IMFS_CREAT) != -1) 240 | { 241 | printf("++ Expected no memory for file opening but file was opened\n"); 242 | return false; 243 | } 244 | 245 | for (long i = NUMOPFLS-1; i >= 0; i--) 246 | if (imfs_close(ssd, fds[i]) != 0) 247 | { 248 | printf("++ Unexpected error while closing file descriptors\n"); 249 | return false; 250 | } 251 | 252 | int last; 253 | if ((last = imfs_open(ssd, "/file", IMFS_CREAT)) != fds[0]) 254 | { 255 | printf("++ Unexpected file descriptor. Expected was %d but was %d\n", fds[0], last); 256 | return false; 257 | } 258 | 259 | return true; 260 | } 261 | 262 | static bool test_random_good_paths(void) 263 | { 264 | #define NUMDIR 1000 265 | memset(base_mem, 0, MSIZE); 266 | struct imfs_conf c = { 267 | .max_num_fnodes = NUMDIR, 268 | .max_opened_files = NUMDIR 269 | }; 270 | 271 | struct imfs *ssd = imfs_init(base_mem, MSIZE, &c, true); 272 | if (!ssd) 273 | { 274 | printf("++ Failed in first init\n"); 275 | return false; 276 | } 277 | 278 | for (size_t i = 0; i < NUMDIR; i++) 279 | if (imfs_mkdir(ssd, random_path(1)) != 0) 280 | { 281 | printf("++ Error in mkdir\n"); 282 | return false; 283 | } 284 | 285 | ssd = imfs_init(base_mem, MSIZE, &c, true); 286 | if (!ssd) 287 | { 288 | printf("++ Failed in second init\n"); 289 | return false; 290 | } 291 | 292 | for (size_t i = 0; i < NUMDIR; i++) 293 | if (imfs_open(ssd, random_path(1), IMFS_CREAT) <= 0) 294 | { 295 | printf("++ Error in file creation\n"); 296 | return false; 297 | } 298 | 299 | return true; 300 | } 301 | 302 | static bool test_common_usage(void) 303 | { 304 | #define NUMDIR 1000 305 | 306 | #define RBLEN 1000 307 | #define FNAME_MAX_LEN 5 308 | memset(base_mem, 0, MSIZE); 309 | struct imfs_conf c = { 310 | .max_num_fnodes = 2*NUMDIR, 311 | .max_opened_files = NUMDIR 312 | }; 313 | 314 | // Generate NUMDIR random file names, paths 315 | // and sizes of writes/reads 316 | static char comp_path[MAX_PATH_LENGTH+1+FNAME_MAX_LEN+1]; 317 | static char paths[NUMDIR][MAX_PATH_LENGTH]; 318 | static char fnames[NUMDIR][FNAME_MAX_LEN+1]; 319 | static long w_r_sizes[NUMDIR]; 320 | static char random_bytes[RBLEN]; 321 | static char read_buffer[RBLEN]; 322 | strncpy(random_bytes, random_path(MAX_DEPTH), RBLEN); 323 | for (size_t i = 0; i < NUMDIR; i++) 324 | { 325 | random_name(fnames[i], FNAME_MAX_LEN, FNAME_MAX_LEN); 326 | strcpy(paths[i], random_path(1)); 327 | w_r_sizes[i] = rand() % RBLEN; 328 | } 329 | 330 | 331 | struct imfs *ssd = imfs_init(base_mem, MSIZE, &c, true); 332 | if (!ssd) 333 | { 334 | printf("++ Failed in first init\n"); 335 | return false; 336 | } 337 | 338 | for (size_t i = 0; i < NUMDIR; i++) 339 | if (imfs_mkdir(ssd, paths[i]) != 0) 340 | { 341 | printf("++ Failed in first mkdir\n"); 342 | return false; 343 | } 344 | 345 | for (size_t i = 0; i < NUMDIR; i++) 346 | { 347 | char *p = strcpy(comp_path, paths[i]); 348 | p += strlen(paths[i]); 349 | *p++ = '/'; 350 | strcpy(p, fnames[i]); 351 | p += strlen(fnames[i]); 352 | *p = '\0'; 353 | 354 | int fd = imfs_open(ssd, comp_path, IMFS_CREAT | IMFS_RDWR); 355 | if (fd <= 0) 356 | { 357 | printf("++ Failed in first file opening. fd = %d\n", fd); 358 | return false; 359 | } 360 | 361 | long res; 362 | if ((res = imfs_write(ssd, fd, random_bytes, w_r_sizes[i])) != 363 | w_r_sizes[i]) 364 | { 365 | printf("++ First Write failed with ret val %ld instead of %ld.\n", res, w_r_sizes[i]); 366 | return false; 367 | } 368 | if ((res = imfs_read(ssd, fd, read_buffer, RBLEN)) != w_r_sizes[i]) 369 | { 370 | printf("++ First Read failed with ret val %ld instead of %ld.\n", res, w_r_sizes[i]); 371 | return false; 372 | } 373 | 374 | if (imfs_close(ssd, fd) != 0) 375 | { 376 | printf("++ Failed in first file closing. fd = %d\n", fd); 377 | return false; 378 | } 379 | } 380 | 381 | // Second round 382 | struct imfs *ssd2 = imfs_init(base_mem, MSIZE, &c, false); 383 | if (!ssd2) 384 | { 385 | printf("++ Failed in second init\n"); 386 | return false; 387 | } 388 | 389 | for (size_t i = 0; i < NUMDIR; i++) 390 | { 391 | char *p = strcpy(comp_path, paths[i]); 392 | p += strlen(paths[i]); 393 | *p++ = '/'; 394 | strcpy(p, fnames[i]); 395 | p += strlen(fnames[i]); 396 | *p = '\0'; 397 | 398 | int fd = imfs_open(ssd2, comp_path, IMFS_CREAT | IMFS_RDWR); 399 | if (fd <= 0) 400 | { 401 | printf("++ Failed in second file opening. fd = %d\n", fd); 402 | return false; 403 | } 404 | 405 | long res; 406 | if ((res = imfs_read(ssd2, fd, read_buffer, RBLEN)) != w_r_sizes[i] || 407 | memcmp(read_buffer, random_bytes, res)) 408 | { 409 | printf("++ Second Read failed with ret val %ld instead of %ld.\n", res, w_r_sizes[i]); 410 | return false; 411 | } 412 | 413 | if (imfs_close(ssd2, fd) != 0) 414 | { 415 | printf("++ Failed in second file closing. fd = %d\n", fd); 416 | return false; 417 | } 418 | 419 | // Reopen with IMFS_TRUNC, so read should return no bytes 420 | fd = imfs_open(ssd2, comp_path, IMFS_CREAT | IMFS_RDWR | IMFS_TRUNC); 421 | if (fd <= 0) 422 | { 423 | printf("++ Failed in third file opening. fd = %d\n", fd); 424 | return false; 425 | } 426 | 427 | if (imfs_read(ssd2, fd, read_buffer, RBLEN) != 0) 428 | { 429 | printf("++ Third Read failed with ret val %ld instead of 0.\n", res); 430 | return false; 431 | } 432 | 433 | if (imfs_close(ssd2, fd) != 0) 434 | { 435 | printf("++ Failed in third file closing. fd = %d\n", fd); 436 | return false; 437 | } 438 | } 439 | 440 | return true; 441 | } 442 | 443 | int main(void) 444 | { 445 | printf("==== IMFS Tests start ====\n"); 446 | 447 | printf("test_init: %s\n", 448 | test_init() ? "Success" : "Failed"); 449 | 450 | printf("test_file_openings: %s\n", 451 | test_file_openings() ? "Success" : "Failed"); 452 | 453 | printf("test_common_usage: %s\n", 454 | test_common_usage() ? "Success" : "Failed"); 455 | 456 | printf("test_random_good_paths: %s\n", 457 | test_random_good_paths() ? "Success" : "Failed"); 458 | 459 | printf("test_big_read_write: %s\n", 460 | test_big_read_write() ? "Success" : "Failed"); 461 | 462 | printf("test_one_M_random_read_write: %s\n", 463 | test_one_M_random_read_write() ? "Success" : "Failed"); 464 | 465 | printf("==== IMFS Tests end ====\n"); 466 | 467 | return 0; 468 | } --------------------------------------------------------------------------------