mfs.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. /*
  2. * https://gitlab.com/bztsrc/minix3fs
  3. *
  4. * Copyright (C) 2023 bzt (MIT license)
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to
  8. * deal in the Software without restriction, including without limitation the
  9. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  10. * sell copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY
  19. * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
  21. * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. *
  23. * @brief a bare minimum, memory allocation-free, read-only Minix3 filesystem driver
  24. */
  25. #include <stdint.h>
  26. #ifndef MFS_H
  27. #define MFS_H
  28. /**
  29. * Sector loader, must be implemented by the host
  30. */
  31. void loadsec(uint32_t lba, uint32_t cnt, void *buf);
  32. /**
  33. * The API that we provide
  34. */
  35. uint32_t mfs_open(const char *fn);
  36. uint32_t mfs_read(uint32_t offs, uint32_t size, void *dst);
  37. void mfs_close(void);
  38. #ifdef MFS_IMPLEMENTATION
  39. /**
  40. * Configurable by the host
  41. */
  42. #ifndef PATH_MAX
  43. #define PATH_MAX 1024
  44. #endif
  45. #ifndef MFS_DIRSEP
  46. #define MFS_DIRSEP '/'
  47. #endif
  48. /**
  49. * MFS specific defines
  50. */
  51. #define MFS_ROOT_INO 1 /* Root inode */
  52. #define MFS_DIRSIZ 60
  53. #define MFS_NR_DZONES 7 /* # direct zone numbers in a V2 inode */
  54. #define MFS_NR_TZONES 10 /* total # zone numbers in a V2 inode */
  55. /* i_mode, file permission bitmasks */
  56. #define S_IXOTH 0x0001 /* execute by others */
  57. #define S_IWOTH 0x0002 /* write by others */
  58. #define S_IROTH 0x0004 /* read by others */
  59. #define S_IXGRP 0x0008 /* execute by group */
  60. #define S_IWGRP 0x0010 /* write by group */
  61. #define S_IRGRP 0x0020 /* read by group */
  62. #define S_IXUSR 0x0040 /* execute by owner */
  63. #define S_IWUSR 0x0080 /* write by owner */
  64. #define S_IRUSR 0x0100 /* read by owner */
  65. #define S_ISUID 0x0400 /* set user on execution */
  66. #define S_ISGID 0x0800 /* set group on execution */
  67. /* i_mode, inode formats */
  68. #define S_IFDIR 0x4000
  69. #define S_IFREG 0x8000
  70. #define S_IFLNK 0xA000
  71. #define MFS_FILETYPE(x) ((x) & 0xF000)
  72. /**
  73. * The superblock, always located at offset 1024, no matter the sector size or block size
  74. */
  75. typedef struct {
  76. uint32_t s_ninodes; /* # usable inodes on the minor device */
  77. uint16_t s_nzones; /* total device size, including bit maps etc */
  78. uint16_t s_imap_blocks; /* # of blocks used by inode bit map */
  79. uint16_t s_zmap_blocks; /* # of blocks used by zone bit map */
  80. uint16_t s_firstdatazone_old; /* number of first data zone (small) */
  81. uint16_t s_log_zone_size; /* log2 of blocks/zone */
  82. uint16_t s_flags; /* FS state flags */
  83. int32_t s_max_size; /* maximum file size on this device */
  84. uint32_t s_zones; /* number of zones (replaces s_nzones in V2) */
  85. int16_t s_magic; /* magic number to recognize super-blocks */
  86. /* The following items are valid on disk only for V3 and above */
  87. int16_t s_pad2; /* try to avoid compiler-dependent padding */
  88. uint16_t s_block_size; /* block size in bytes. */
  89. int8_t s_disk_version; /* filesystem format sub-version */
  90. } __attribute__((packed)) superblock_t;
  91. /**
  92. * One inode slot in the inode table, 64 bytes
  93. * Inode table located at offset (2 + s_imap_blocks + s_zmap_blocks) * s_block_size, and has s_ninodes slots
  94. */
  95. typedef struct { /* V2/V3 disk inode */
  96. uint16_t i_mode; /* file type, protection, etc. */
  97. uint16_t i_nlinks; /* how many links to this file. */
  98. int16_t i_uid; /* user id of the file's owner. */
  99. uint16_t i_gid; /* group number */
  100. uint32_t i_size; /* current file size in bytes */
  101. uint32_t i_atime; /* when was file data last accessed */
  102. uint32_t i_mtime; /* when was file data last changed */
  103. uint32_t i_ctime; /* when was inode data last changed */
  104. uint32_t i_zone[MFS_NR_TZONES];/* zone nums for direct, ind, and dbl ind */
  105. } __attribute__((packed)) inode_t;
  106. /**
  107. * Directory entry format, 64 bytes
  108. */
  109. typedef struct {
  110. uint32_t d_ino;
  111. char d_name[MFS_DIRSIZ];
  112. } __attribute__((packed)) direct_t;
  113. /**
  114. * Private static variables (no memory allocation in this library)
  115. */
  116. uint32_t ind[2], inode_table, inode_nr, block_size = 0, sec_per_blk, blk_per_block, blk_per_block2, mfs_inode = 0, *blklst;
  117. uint8_t mfs_data[32768], mfs_dir[32768], mfs_buf[65536], mfs_fn[PATH_MAX];
  118. inode_t *ino;
  119. /**
  120. * Load an inode
  121. * Returns:
  122. * - mfs_inode: the loaded inode's number, or 0 on error
  123. * - ino: points to the inode_t of the loaded inode
  124. */
  125. static void loadinode(uint32_t inode)
  126. {
  127. uint32_t block_offs = (inode - 1) * sizeof(inode_t) / block_size;
  128. uint32_t inode_offs = (inode - 1) * sizeof(inode_t) % block_size;
  129. /* failsafe, boundary check */
  130. if(inode < 1 || inode >= inode_nr) { mfs_inode = 0; return; }
  131. /* load the block with our inode structure */
  132. loadsec((inode_table + block_offs) * sec_per_blk, sec_per_blk, mfs_buf);
  133. /* copy inode from the table into the beginning of our buffer */
  134. __builtin_memcpy(mfs_buf, mfs_buf + inode_offs, sizeof(inode_t));
  135. /* reset indirect block cache */
  136. __builtin_memset(ind, 0, sizeof(ind));
  137. mfs_inode = inode;
  138. }
  139. /**
  140. * Look up directories, set mfs_inode and return file size
  141. * Returns:
  142. * - the opened file's size on success, or
  143. * - 0 on file not found error and
  144. * - 0xffffffff if MFS was not recognized on the storage
  145. */
  146. uint32_t mfs_open(const char *fn)
  147. {
  148. superblock_t *sb = (superblock_t*)mfs_buf;
  149. direct_t *de;
  150. uint32_t offs, i, rem, redir = 0;
  151. uint8_t *s = mfs_fn, *e;
  152. if(!fn || !*fn) return 0;
  153. /* copy the file name into a buffer, because resolving symbolic links might alter it */
  154. for(e = (uint8_t*)fn; *e && s - mfs_fn + 1 < PATH_MAX; s++, e++) *s = *e;
  155. s = mfs_fn;
  156. /* initialize file system */
  157. if(!block_size) {
  158. __builtin_memset(mfs_buf, 0, 512);
  159. loadsec(2, 1, mfs_buf);
  160. if(sb->s_magic != 0x4D5A || sb->s_block_size < 1024 || (sb->s_block_size & 511))
  161. return -1U;
  162. /* save values from the superblock */
  163. block_size = sb->s_block_size; /* one block's size */
  164. sec_per_blk = block_size >> 9; /* calculate frequently used block size multiples */
  165. blk_per_block = block_size / sizeof(uint32_t);
  166. blk_per_block2 = blk_per_block * blk_per_block;
  167. inode_table = 2 + sb->s_imap_blocks + sb->s_zmap_blocks; /* get the start and length of the inode table */
  168. inode_nr = sb->s_ninodes;
  169. mfs_inode = 0; ino = (inode_t*)mfs_buf; /* set up inode buffer */
  170. blklst = (uint32_t*)(mfs_buf + sizeof(inode_t));
  171. }
  172. /* do path traversal */
  173. again:
  174. loadinode(MFS_ROOT_INO); /* start from the root directory */
  175. if(*s == MFS_DIRSEP) s++; /* remove leading directory separator if any */
  176. for(e = s; *e && *e != MFS_DIRSEP; e++); /* find the end of the file name in path string */
  177. offs = 0;
  178. while(offs < ino->i_size) { /* iterate on directory entries, read one block at a time */
  179. /* read in the next block in the directory */
  180. if(!mfs_read(offs, block_size, mfs_dir)) break;
  181. rem = ino->i_size - offs; if(rem > block_size) rem = block_size;
  182. offs += block_size;
  183. /* check filenames in directory entries */
  184. for(i = 0, de = (direct_t*)mfs_dir; i < rem; i += sizeof(direct_t), de++) {
  185. if(de->d_ino && e - s < MFS_DIRSIZ && !__builtin_memcmp(s, (uint8_t*)de->d_name, e - s) && !de->d_name[e - s]) {
  186. loadinode(de->d_ino);
  187. if(!mfs_inode) goto err;
  188. /* symlink */
  189. if(MFS_FILETYPE(ino->i_mode) == S_IFLNK) {
  190. i = ino->i_size; if(i > PATH_MAX - 1) i = PATH_MAX - 1;
  191. /* read in the target path */
  192. if(redir >= 8 || !mfs_read(0, i, mfs_dir)) goto err;
  193. mfs_dir[i] = 0; redir++;
  194. /* this is a minimalistic implementation, we just do string manipulations.
  195. * you should resolve target path in mfs_dir to mfs_inode / ino instead */
  196. if(mfs_dir[0] == MFS_DIRSEP) {
  197. /* starts with the directory separator, so an absolute path. Replace the entire string */
  198. __builtin_memcpy(mfs_fn, mfs_dir + 1, i);
  199. } else {
  200. /* relative path, manipulate string, skip "./" and remove last file name when "../" */
  201. for(e = mfs_dir; *e && s < &mfs_fn[PATH_MAX - 1]; e++) {
  202. if(e[0] == '.' && e[1] == MFS_DIRSEP) e++; else
  203. if(e[0] == '.' && e[1] == '.' && e[2] == MFS_DIRSEP) {
  204. e += 2; if(s > mfs_fn) for(s--; s > mfs_fn && s[-1] != MFS_DIRSEP; s--);
  205. } else *s++ = *e;
  206. }
  207. *s = 0;
  208. }
  209. /* restart traverse */
  210. s = mfs_fn; goto again;
  211. }
  212. /* regular file and end of path */
  213. if(!*e) { if(MFS_FILETYPE(ino->i_mode) == S_IFREG) { return ino->i_size; } goto err; }
  214. /* directory and not end of path */
  215. if(MFS_FILETYPE(ino->i_mode) == S_IFDIR) {
  216. /* with directories:
  217. * - we simply replace mfs_inode and ino (already done by loadinode above),
  218. * - adjust pointer in path to the next file name elment,
  219. * - and restart our loop */
  220. for(s = e + 1, e = s; *e && *e != MFS_DIRSEP; e++);
  221. offs = 0;
  222. break; /* break from for, continue while */
  223. } else
  224. /* anything else (device, fifo etc.) or file in the middle or directory at the end */
  225. goto err;
  226. }
  227. }
  228. }
  229. err:mfs_inode = 0;
  230. return 0;
  231. }
  232. /**
  233. * Read from opened file (current inode in mfs_inode)
  234. * Returns: the number of bytes loaded, or 0 on error
  235. */
  236. uint32_t mfs_read(uint32_t offs, uint32_t size, void *dst)
  237. {
  238. uint32_t blk, rem, i, j, k, os = 0, rs = block_size;
  239. uint8_t *buf = (uint8_t*)dst;
  240. if(!mfs_inode || !block_size || offs >= ino->i_size || !size || !buf) return 0;
  241. /* make sure we won't read out of bounds */
  242. if(offs + size > ino->i_size) size = ino->i_size - offs;
  243. rem = size;
  244. /* calculate file block number from offset */
  245. os = offs % block_size;
  246. offs /= block_size;
  247. if(os) rs = block_size - os;
  248. while(rem) {
  249. /* convert file offset block number to file system block number */
  250. /* we keep indirect records cached in blklst, and the block numbers where they were loaded from in ind[X]
  251. * ind[0] -> blklst[0 .. blk_per_block-1]
  252. * ind[1] -> blklst[blk_per_block .. 2*blk_per_block-1] */
  253. blk = 0;
  254. if(offs < MFS_NR_DZONES) { /* direct */ blk = ino->i_zone[offs]; } else
  255. if(offs >= MFS_NR_DZONES && offs < MFS_NR_DZONES + blk_per_block) {
  256. /* indirect */
  257. if(ind[0] != ino->i_zone[MFS_NR_DZONES]) {
  258. ind[0] = ino->i_zone[MFS_NR_DZONES];
  259. loadsec(ind[0] * sec_per_blk, sec_per_blk, (uint8_t*)blklst);
  260. }
  261. blk = blklst[offs - MFS_NR_DZONES];
  262. } else
  263. if(offs >= MFS_NR_DZONES + blk_per_block && offs < MFS_NR_DZONES + blk_per_block + blk_per_block2) {
  264. /* double indirect */
  265. i = offs - MFS_NR_DZONES - blk_per_block;
  266. k = MFS_NR_DZONES + 1 + i / blk_per_block2;
  267. if(k < MFS_NR_TZONES) {
  268. if(ind[0] != ino->i_zone[k]) {
  269. ind[0] = ino->i_zone[k];
  270. loadsec(ind[0] * sec_per_blk, sec_per_blk, (uint8_t*)blklst);
  271. }
  272. j = blklst[i / blk_per_block];
  273. if(ind[1] != j) {
  274. ind[1] = j;
  275. loadsec(ind[1] * sec_per_blk, sec_per_blk, (uint8_t*)blklst + block_size);
  276. }
  277. blk = blklst[blk_per_block + (i % blk_per_block)];
  278. }
  279. }
  280. if(!blk) break;
  281. /* load data */
  282. loadsec(blk * sec_per_blk, sec_per_blk, mfs_data);
  283. /* copy to its final place */
  284. if(rs > rem) rs = rem;
  285. __builtin_memcpy(buf, mfs_data + os, rs); os = 0;
  286. buf += rs; rem -= rs; rs = block_size;
  287. offs++;
  288. }
  289. return (size - rem);
  290. }
  291. /**
  292. * Close the opened file
  293. */
  294. void mfs_close(void)
  295. {
  296. mfs_inode = 0;
  297. }
  298. #endif /* MFS_IMPLEMENTATION */
  299. #endif /* MFS_H */