// SCHFS mkfs -- disk image builder. // // Aligned with fs.c improvements: // - Writes atime/mtime/ctime timestamps into every inode. // - Computes and writes CRC32c checksum into every inode and the superblock, // so fsinit() and ilock() pass their validation checks on first boot. // - OVERFLOW_DIR_START / OVERFLOW_DIR_END replace all magic "113 * BSIZE" // literals; both loops in iappend_init_dir are unified into one helper. // - alloc_bmap gains a T_EXTENT path that writes proper struct fsv1_extent // entries (inline extents only; mkfs files are small). // - ialloc writes a proper struct dinode including the new fields so the // on-disk image is byte-compatible with what the kernel reads. // - updatesb writes a CRC32c checksum of the superblock. // - check_disk_space prints a helpful message before dying. // - Dead code (cal_block_index_for_dinode, test_xpr) removed. #include #include #include #include #include #include #include #include // Windows opens files in *text* mode by default, which turns every 0x0A // written into 0x0D 0x0A. A filesystem image written that way is corrupt in a // way that is invisible until the kernel tries to mount it, so every open() // below is explicitly binary. O_BINARY does not exist on Unix, where there is // no translation to disable. #ifndef O_BINARY #define O_BINARY 0 #endif // bzero/bcopy are pre-POSIX.1-2008 BSD spellings. glibc still declares them, // mingw-w64 does not, and this file has seventeen calls. They are defined here // rather than rewritten at each call site on purpose: bcopy takes (src, dst) // and memmove takes (dst, src), so a mechanical rewrite has seventeen chances // to silently reverse a copy -- and the result would be a filesystem image // that builds without complaint and is quietly wrong. #define bzero(p, n) memset((p), 0, (n)) #define bcopy(src, dst, n) memmove((dst), (src), (n)) #define stat xv6_stat #define sigset_t xv6_sigset_t #define dirent schfs_dirent #include "../include/core/types.h" #include "../include/core/param.h" #include "../include/fs/fs.h" #include "../include/uapi/stat.h" #undef dirent #undef stat #include // After the #undef above, so this gets the host's struct stat and not xv6's. #include // struct dirent's d_type is a BSD/glibc extension; mingw-w64 has no such // field. The loops below already handle DT_UNKNOWN by probing with opendir(), // which is the portable answer and was written for filesystems that decline to // report a type -- so on Windows every entry simply takes that path. // // This block must stay *below* , not up with the other compatibility // shims. glibc spells these as enumerators with matching macros, so asking // #ifndef before the header is read always says "missing" -- and then the // header's own `enum { DT_UNKNOWN = 0, ... }` expands to `enum { 0 = 0, ... }` // and the build dies on a syntax error a long way from the cause. The header // cannot move up either: it has to follow the `#undef dirent` above, or it // would define the xv6 struct dirent instead of the host's. #ifndef DT_UNKNOWN #define DT_UNKNOWN 0 #define DT_DIR 4 #define DT_REG 8 #endif #ifdef _WIN32 #define DIRENT_TYPE(dp) DT_UNKNOWN #else #define DIRENT_TYPE(dp) ((dp)->d_type) #endif #ifndef static_assert #define static_assert(a, b) do { switch (0) case 0: case (a): ; } while (0) #endif #ifndef RW #define RW 3 #endif #ifndef HASHRANGE #define HASHRANGE 16384 #endif // Overflow bucket bounds -- must match fs.c definitions. #ifndef OVERFLOW_DIR_START #define OVERFLOW_DIR_START (113 * BSIZE) #define OVERFLOW_DIR_END (115 * BSIZE) #endif // --------------------------------------------------------------------------- // CRC32c (Castagnoli) -- must stay byte-for-byte identical to the fs.c copy. // --------------------------------------------------------------------------- static uint32_t crc32c_table[256]; static void crc32c_init(void) { uint32_t i, j, crc; for (i = 0; i < 256; i++) { crc = i; for (j = 0; j < 8; j++) crc = (crc >> 1) ^ (crc & 1 ? 0x82F63B78U : 0); crc32c_table[i] = crc; } } static uint32_t crc32c(uint32_t crc, const void *buf, uint32_t len) { const uint8_t *p = (const uint8_t *)buf; crc = ~crc; while (len--) crc = (crc >> 8) ^ crc32c_table[(crc ^ *p++) & 0xFF]; return ~crc; } static uint32_t inode_checksum(struct dinode *dip) { uint32_t saved = dip->checksum; dip->checksum = 0; uint32_t ck = crc32c(0, dip, sizeof(*dip)); dip->checksum = saved; return ck; } // --------------------------------------------------------------------------- // File system geometry // --------------------------------------------------------------------------- #define NINODES 12000 // Image size in blocks. Defaults to FSSIZE (the main root image) but is // overridable with --size, so a small ramdisk root can be built by the same // tool without recompiling param.h. Anything derived from it is recomputed in // main() after argument parsing. int fssize = FSSIZE; // Inode count, likewise overridable (--ninodes). It is not a free choice on a // small image: the inode table is preallocated, so 12000 inodes costs 1501 // blocks whether or not anything uses them. On the 150000-block root image that // is 1% and irrelevant; on the 6000-block ramdisk root it was a quarter of the // entire image, spent to describe about 260 files. int ninodes = NINODES; int nibitmap; int nbitmap; int ninodeblocks; int nlog = LOGSIZE; int nmeta; int nblocks; // Paths to leave out of the image, relative to the sysroot root and without a // leading slash (e.g. "usr/share/misc"). Matches a whole path component prefix, // so naming a directory drops its entire subtree. // // This exists because the ramdisk root is *embedded in the kernel binary*, which // makes what goes into it a licensing question and not only a size one: anything // staged in sysroot/ when that image is built becomes part of a distributable // artifact. An exclude list enforces that mechanically, rather than relying on // someone having remembered to unstage something first. #define MAXEXCLUDE 32 const char *excludes[MAXEXCLUDE]; int nexcludes; // strlen(sysroot_path) + 1, so a host path can be turned back into the // sysroot-relative one the exclude patterns are written against. size_t sysroot_prefix; // Is this sysroot-relative path excluded? Matches exactly, or as a directory // prefix so that "usr/share/misc" also drops "usr/share/misc/big.dat". static int is_excluded(const char *relpath) { for (int i = 0; i < nexcludes; i++) { size_t n = strlen(excludes[i]); if (strncmp(relpath, excludes[i], n) == 0 && (relpath[n] == '\0' || relpath[n] == '/')) return 1; } return 0; } // Shared by both tree walkers: decide whether to descend into a host path, and // say so when the answer is no. // // Checked before the entry is dispatched rather than inside sysroot_add_dir(), // so an excluded directory is never opened and never gets an inode. An empty // directory in the image would still cost one and would wrongly imply the // content was supposed to be there. static int sysroot_skip(const char *child) { const char *rel = child + sysroot_prefix; if (!is_excluded(rel)) return 0; fprintf(stderr, "mkfs: excluding %s\n", rel); return 1; } int fsfd; struct superblock sb; char zeroes[BSIZE]; uint freeinode = 1; uint freeblock; // --------------------------------------------------------------------------- // Forward declarations // --------------------------------------------------------------------------- void balloc(int); void image_init(void); void image_flush(void); void wsect(uint, void *); void winode(uint, struct dinode *); void rinode(uint, struct dinode *); void rsect(uint, void *); uint ialloc(ushort type); void iappend(uint inum, void *p, int n); void iappend_init_dir(uint inum, struct schfs_dirent *de); void setibmap(int); void updatesb(void); void die(const char *); void check_disk_space(void); static uint alloc_bmap(struct dinode *dp, uint fbn); static void sysroot_add_dir(uint parent_ino, const char *host_path, const char *name); static void sysroot_add_file(uint parent_ino, const char *host_path, const char *name); static void sysroot_populate(uint rootino, const char *sysroot_path); // --------------------------------------------------------------------------- // Byte-order helpers (little-endian) // --------------------------------------------------------------------------- ushort xshort(ushort x) { ushort y; uchar *a = (uchar *)&y; a[0] = x; a[1] = x >> 8; return y; } uint xint(uint x) { uint y; uchar *a = (uchar *)&y; a[0] = x; a[1] = x >> 8; a[2] = x >> 16; a[3] = x >> 24; return y; } // --------------------------------------------------------------------------- // Content-based hard-link dedup // // sysroot/bin holds one full, independent copy of the toybox binary per applet // name (57 of them, ~822 KB each) because this tool understood neither symlinks // nor hard links -- about 44 MB of identical bytes in every image. Rather than // teach it symlinks, dedup by *content*: identical files share a single inode // and the extra directory entries become real hard links (nlink > 1), which the // on-disk format already supports. // // Keyed on (size, CRC32c of contents); a candidate is re-read and compared // byte-for-byte before its inode is shared, so a checksum collision cannot // silently merge two different files. // --------------------------------------------------------------------------- struct dedup_ent { dev_t hdev; // host device+inode: when these match, ino_t hino; // it is literally the same file off_t size; uint32_t crc; uint inum; char path[512]; }; static struct dedup_ent dedup_tab[512]; static int dedup_n; // A file the host already told us is the same file. sysroot/bin holds 236 // names for one toybox binary, and ports/toybox/Makefile makes them real hard // links -- so this answers for all but the first of them without opening // anything. // // This matters more than it looks. Without it, every one of those 236 names // was read once to checksum it and then *both* files were re-read a byte at a // time through getc() to confirm the match: about half a gigabyte of I/O and // 380 million getc calls, which took 45 seconds of every build across WSL's // filesystem boundary. The content path below is still there, and still the // only thing that can dedup two files that merely happen to be identical. static uint dedup_lookup_ino(dev_t hdev, ino_t hino) { if (hino == 0) return 0; // host gave us nothing usable for (int i = 0; i < dedup_n; i++) if (dedup_tab[i].hdev == hdev && dedup_tab[i].hino == hino) return dedup_tab[i].inum; return 0; } // Returns an existing inum holding identical content, or 0 for "not seen". static uint dedup_lookup(const char *host_path, off_t size, uint32_t crc) { for (int i = 0; i < dedup_n; i++) { if (dedup_tab[i].size != size || dedup_tab[i].crc != crc) continue; FILE *a = fopen(dedup_tab[i].path, "rb"); FILE *b = fopen(host_path, "rb"); if (!a || !b) { if (a) fclose(a); if (b) fclose(b); continue; } // Block-at-a-time, not getc-at-a-time: same answer, ~1000x fewer // trips through stdio for the multi-megabyte files this actually sees. char abuf[16384], bbuf[16384]; int same = 1; for (;;) { size_t na = fread(abuf, 1, sizeof(abuf), a); size_t nb = fread(bbuf, 1, sizeof(bbuf), b); if (na != nb || memcmp(abuf, bbuf, na) != 0) { same = 0; break; } if (na == 0) break; } fclose(a); fclose(b); if (same) return dedup_tab[i].inum; } return 0; } static void dedup_remember(const char *host_path, dev_t hdev, ino_t hino, off_t size, uint32_t crc, uint inum) { if (dedup_n >= (int)(sizeof(dedup_tab) / sizeof(dedup_tab[0]))) return; // table full: stop deduping, stay correct struct dedup_ent *e = &dedup_tab[dedup_n++]; e->hdev = hdev; e->hino = hino; e->size = size; e->crc = crc; e->inum = inum; snprintf(e->path, sizeof(e->path), "%s", host_path); } // Bump nlink on an inode that is already written out, for a dedup hit. // // winode() writes the struct verbatim -- it does NOT recompute the checksum, // so every caller must refresh it after touching any field. Missing this here // shipped images whose deduped inodes carried a stale CRC32c, and the kernel // panicked with "ilock: inode checksum mismatch" the first time one was opened. static void inode_addlink(uint inum) { struct dinode din; rinode(inum, &din); din.nlink = xshort(xshort(din.nlink) + 1); din.checksum = inode_checksum(&din); winode(inum, &din); } // --------------------------------------------------------------------------- // Sysroot tree walker — mirrors sysroot/ into the FS image // --------------------------------------------------------------------------- static void sysroot_add_file(uint parent_ino, const char *host_path, const char *name) { int fd = open(host_path, O_RDONLY | O_BINARY); if (fd < 0) { perror(host_path); exit(1); } // Ask the host whether this is a file we have already taken in. A hard // link is the same inode by definition, so this is exact -- and it costs // one fstat instead of reading the file twice. struct stat st; dev_t hdev = 0; ino_t hino = 0; uint ino = 0; if (fstat(fd, &st) == 0 && st.st_nlink > 1) { hdev = st.st_dev; hino = st.st_ino; ino = dedup_lookup_ino(hdev, hino); } if (ino) { close(fd); inode_addlink(ino); } else { // Checksum the contents, so files that are identical without being // linked can still share one inode. char buf[BSIZE]; int n; off_t fsz = 0; uint32_t crc = 0; while ((n = read(fd, buf, sizeof(buf))) > 0) { crc = crc32c(crc, buf, (uint32_t)n); fsz += n; } ino = dedup_lookup(host_path, fsz, crc); if (ino) { // Identical content is already in the image: emit another // directory entry for that same inode -- a real hard link -- and // write no data. close(fd); inode_addlink(ino); } else { ino = ialloc(T_FILE); if (lseek(fd, 0, SEEK_SET) != 0) die(host_path); while ((n = read(fd, buf, sizeof(buf))) > 0) iappend(ino, buf, n); close(fd); dedup_remember(host_path, hdev, hino, fsz, crc, ino); } } struct schfs_dirent de; bzero(&de, sizeof(de)); de.inum = xshort(ino); strncpy(de.name, name, DIRSIZ); iappend_init_dir(parent_ino, &de); } static void sysroot_add_dir(uint parent_ino, const char *host_path, const char *name) { DIR *d = opendir(host_path); if (!d) { perror(host_path); exit(1); } uint dir_ino = ialloc(T_DIR); struct schfs_dirent de; bzero(&de, sizeof(de)); de.inum = xshort(dir_ino); strncpy(de.name, name, DIRSIZ); iappend_init_dir(parent_ino, &de); bzero(&de, sizeof(de)); de.inum = xshort(dir_ino); strcpy(de.name, "."); iappend(dir_ino, &de, sizeof(de)); bzero(&de, sizeof(de)); de.inum = xshort(parent_ino); strcpy(de.name, ".."); iappend(dir_ino, &de, sizeof(de)); struct dirent *dp; while ((dp = readdir(d)) != NULL) { if (dp->d_name[0] == '.') continue; char child[4096]; snprintf(child, sizeof(child), "%s/%s", host_path, dp->d_name); if (sysroot_skip(child)) continue; unsigned char dtype = DIRENT_TYPE(dp); if (dtype == DT_UNKNOWN) { DIR *probe = opendir(child); if (probe) { closedir(probe); dtype = DT_DIR; } else dtype = DT_REG; } if (dtype == DT_DIR) sysroot_add_dir(dir_ino, child, dp->d_name); else if (dtype == DT_REG) sysroot_add_file(dir_ino, child, dp->d_name); } closedir(d); } // Merge the contents of sysroot_path/ directly into the root inode. static void sysroot_populate(uint rootino, const char *sysroot_path) { DIR *d = opendir(sysroot_path); if (!d) { perror(sysroot_path); exit(1); } sysroot_prefix = strlen(sysroot_path) + 1; struct dirent *dp; while ((dp = readdir(d)) != NULL) { if (dp->d_name[0] == '.') continue; char child[4096]; snprintf(child, sizeof(child), "%s/%s", sysroot_path, dp->d_name); if (sysroot_skip(child)) continue; unsigned char dtype = DIRENT_TYPE(dp); if (dtype == DT_UNKNOWN) { DIR *probe = opendir(child); if (probe) { closedir(probe); dtype = DT_DIR; } else dtype = DT_REG; } if (dtype == DT_DIR) sysroot_add_dir(rootino, child, dp->d_name); else if (dtype == DT_REG) sysroot_add_file(rootino, child, dp->d_name); } closedir(d); } // --------------------------------------------------------------------------- // main // --------------------------------------------------------------------------- int main(int argc, char *argv[]) { uint rootino; struct schfs_dirent de; static_assert(sizeof(int) == 4, "Integers must be 4 bytes!"); if (argc < 2) { fprintf(stderr, "Usage: mkfs fs.img [options] --sysroot \n" " mkfs fs.img [options] file...\n" "\n" " --size image size (default FSSIZE = %d)\n" " --ninodes inode table size (default %d)\n" " --exclude omit a sysroot-relative path and its subtree;\n" " may be given more than once\n", FSSIZE, NINODES); exit(1); } // Options let one tool build both the main root image and the much smaller // ramdisk root without recompiling param.h. // // Parsed by compacting argv in place, because the two modes below still // identify themselves positionally: argv[2] is either "--sysroot" or the // first file of the legacy flat list. The previous version of this loop // handled exactly one option and then `break`, which silently ignored a // second one -- fine while --size was the only flag, a trap now. { int out = 2; for (int i = 2; i < argc; i++) { int has_arg = (i + 1 < argc); if (has_arg && strcmp(argv[i], "--size") == 0) { fssize = atoi(argv[++i]); if (fssize < 64) { fprintf(stderr, "mkfs: --size %d is too small\n", fssize); exit(1); } } else if (has_arg && strcmp(argv[i], "--ninodes") == 0) { ninodes = atoi(argv[++i]); if (ninodes < 16) { fprintf(stderr, "mkfs: --ninodes %d is too small\n", ninodes); exit(1); } } else if (has_arg && strcmp(argv[i], "--exclude") == 0) { if (nexcludes >= MAXEXCLUDE) { fprintf(stderr, "mkfs: more than %d --exclude paths\n", MAXEXCLUDE); exit(1); } // Tolerate a leading slash, since "/usr/share/misc" is the // natural way to write the path even though the patterns are // matched relative to the sysroot. const char *p = argv[++i]; excludes[nexcludes++] = (*p == '/') ? p + 1 : p; } else { argv[out++] = argv[i]; } } argc = out; } // Geometry must be computed now, not at static-initialiser time: it depends // on --size and --ninodes. nbitmap = fssize / (BSIZE * 8) + 1; nibitmap = ninodes / (BSIZE * 8) + 1; ninodeblocks = ninodes / IPB + 1; assert((BSIZE % sizeof(struct dinode)) == 0); assert((BSIZE % sizeof(struct schfs_dirent)) == 0); crc32c_init(); fsfd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0666); if (fsfd < 0) die(argv[1]); nmeta = 2 + nlog + ninodeblocks + nbitmap + nibitmap; nblocks = fssize - nmeta; sb.magic = FSMAGIC; sb.size = xint(fssize); sb.nblocks = xint(nblocks); sb.ninodes = xint(ninodes); sb.nlog = xint(nlog); sb.logstart = xint(2); sb.ibmapstart = xint(2 + nlog); sb.inodestart = xint(2 + nlog + nibitmap); sb.bmapstart = xint(2 + nlog + nibitmap + ninodeblocks); printf("nmeta %d blocks %d total %d\n", nmeta, nblocks, fssize); freeblock = nmeta; // calloc gives us the zeroed image; there is nothing to write out yet. image_init(); { char buf[BSIZE]; memset(buf, 0, sizeof(buf)); memmove(buf, &sb, sizeof(sb)); wsect(1, buf); } // Root directory. rootino = ialloc(T_DIR); assert(rootino == ROOTINO); bzero(&de, sizeof(de)); de.inum = xshort(rootino); strcpy(de.name, "."); iappend(rootino, &de, sizeof(de)); bzero(&de, sizeof(de)); de.inum = xshort(rootino); strcpy(de.name, ".."); iappend(rootino, &de, sizeof(de)); if (argc >= 4 && strcmp(argv[2], "--sysroot") == 0) { // New mode: mirror a sysroot directory tree into the image. sysroot_populate(rootino, argv[3]); } else { // Legacy mode: flat file list → everything goes into /bin. uint binino = ialloc(T_DIR); bzero(&de, sizeof(de)); de.inum = xshort(binino); strcpy(de.name, "bin"); iappend_init_dir(rootino, &de); bzero(&de, sizeof(de)); de.inum = xshort(binino); strcpy(de.name, "."); iappend(binino, &de, sizeof(de)); bzero(&de, sizeof(de)); de.inum = xshort(rootino); strcpy(de.name, ".."); iappend(binino, &de, sizeof(de)); char buf[BSIZE]; for (int i = 2; i < argc; i++) { char *shortname = argv[i]; if (strncmp(shortname, "build/user/", 11) == 0) shortname += 11; else if (strncmp(shortname, "user/", 5) == 0) shortname += 5; char *last_slash = strrchr(shortname, '/'); if (last_slash) shortname = last_slash + 1; int fd = open(argv[i], O_RDONLY | O_BINARY); if (fd < 0) die(argv[i]); if (shortname[0] == '_') shortname++; uint inum = ialloc(T_FILE); bzero(&de, sizeof(de)); de.inum = xshort(inum); strncpy(de.name, shortname, DIRSIZ); if (strcmp(shortname, "README") == 0) iappend_init_dir(rootino, &de); else iappend_init_dir(binino, &de); int cc; while ((cc = read(fd, buf, sizeof(buf))) > 0) iappend(inum, buf, cc); close(fd); } } // Finalize: bitmap, inode bitmap, superblock (with checksum). balloc(freeblock); setibmap(freeinode); updatesb(); // Nothing has touched the file until now. image_flush(); exit(0); } // --------------------------------------------------------------------------- // Disk I/O // --------------------------------------------------------------------------- void check_disk_space(void) { if (freeblock >= (uint)fssize) { // Naming every lever, because the old message said only "increase // FSSIZE in param.h" -- and the image that actually overflowed was the // ramdisk root, whose size comes from RAMDISK_BLOCKS in the Makefile. // FSSIZE was 150000 and entirely innocent, which cost a while to work // out. A wrong pointer in an error message is worse than none. fprintf(stderr, "mkfs: image full -- used %u of %u blocks" " (%d metadata: %d log, %d inode table, %d bitmaps).\n", freeblock, fssize, nmeta, nlog, ninodeblocks, nbitmap + nibitmap); fprintf(stderr, " Which knob depends on the image:\n" " the main root image -> FSSIZE in include/core/param.h\n" " the ramdisk root -> RAMDISK_BLOCKS in the Makefile\n" " Before growing it, check whether the content belongs here at all:\n" " --exclude drops a subtree, and --ninodes shrinks the\n" " %d-block preallocated inode table (currently %d inodes for %u used).\n", ninodeblocks, ninodes, freeinode); exit(1); } } // The image is assembled in memory and written out once, in image_flush(). // // It used to be built directly on the file: every wsect() was an lseek plus a // 512-byte write, and building a 150000-block image meant over 300,000 // syscall pairs -- including 150,000 of them just to zero it. That is // tolerable on a local disk and ruinous across a filesystem boundary; on // WSL's /mnt/d it made `make` take a hundred seconds *when nothing had // changed*. Holding the whole image (77 MB at the default size) costs less // than that by every measure worth caring about. static unsigned char *image; void image_init(void) { image = calloc((size_t)fssize, BSIZE); if (!image) { fprintf(stderr, "mkfs: out of memory for a %d-block image\n", fssize); exit(1); } } void image_flush(void) { size_t total = (size_t)fssize * BSIZE; size_t tail = total; size_t done = 0; // Everything above the last allocated block is zero, and a file that ends // early reads as zeros past its end -- so write only the part that has // anything in it and set the length with ftruncate. At the default size // that is under 2 MB written instead of 77, which is the difference // between a filesystem image being cheap to rebuild and being the slowest // step in the build. // // The last non-zero block is *found* rather than assumed to be `freeblock` // -- scanning memory costs nothing next to the write, and it does not // depend on my reading of where the allocator stops being able to place // things. while (tail > 0 && image[tail - 1] == 0) tail--; tail = ((tail + BSIZE - 1) / BSIZE) * BSIZE; // round up to a whole block while (done < tail) { ssize_t n = write(fsfd, image + done, tail - done); if (n <= 0) die("write"); done += (size_t)n; } if (ftruncate(fsfd, (off_t)total) < 0) die("ftruncate"); } void wsect(uint sec, void *buf) { if (sec >= (uint)fssize) { fprintf(stderr, "mkfs: wsect %u out of range\n", sec); exit(1); } memmove(image + (size_t)sec * BSIZE, buf, BSIZE); } void rsect(uint sec, void *buf) { if (sec >= (uint)fssize) { fprintf(stderr, "mkfs: rsect %u out of range\n", sec); exit(1); } memmove(buf, image + (size_t)sec * BSIZE, BSIZE); } void winode(uint inum, struct dinode *ip) { char buf[BSIZE]; uint bn = IBLOCK(inum, sb); rsect(bn, buf); struct dinode *dip = ((struct dinode *)buf) + (inum % IPB); *dip = *ip; wsect(bn, buf); } void rinode(uint inum, struct dinode *ip) { char buf[BSIZE]; uint bn = IBLOCK(inum, sb); rsect(bn, buf); struct dinode *dip = ((struct dinode *)buf) + (inum % IPB); *ip = *dip; } // --------------------------------------------------------------------------- // Inode allocation // --------------------------------------------------------------------------- // Current wall-clock time as a 32-bit Unix timestamp. static uint32_t mkfs_now(void) { return (uint32_t)time(NULL); } uint ialloc(ushort type) { uint inum = freeinode++; struct dinode din; bzero(&din, sizeof(din)); din.type = xshort(type); din.nlink = xshort(1); din.size = xint(0); din.rwmode = RW; din.supermode = 0; din.showmode = 1; // Timestamps: all three set to "now" at image creation time. uint32_t ts = mkfs_now(); din.atime = ts; din.mtime = ts; din.ctime = ts; // Checksum must be computed AFTER all fields are populated. din.checksum = inode_checksum(&din); winode(inum, &din); return inum; } // --------------------------------------------------------------------------- // Block allocation bitmap // --------------------------------------------------------------------------- void balloc(int used) { uchar buf[BSIZE]; int bmap_block = 0; printf("balloc: first %d blocks have been allocated\n", used); bzero(buf, BSIZE); for (int i = 0; i < used; i++) { buf[(i % (BSIZE * 8)) / 8] |= (1 << (i % 8)); if ((i + 1) % (BSIZE * 8) == 0 || i == used - 1) { wsect(xint(sb.bmapstart) + bmap_block, buf); bzero(buf, BSIZE); bmap_block++; } } } // --------------------------------------------------------------------------- // alloc_bmap: map logical file block `fbn` to a physical disk block, // allocating one if needed. Supports classic (direct/indirect) inodes and // T_EXTENT inodes (inline extent array only -- sufficient for mkfs). // --------------------------------------------------------------------------- #define min(a, b) ((a) < (b) ? (a) : (b)) static uint alloc_bmap_extent(struct dinode *dp, uint fbn) { struct fsv1_extent *ext = (struct fsv1_extent *)dp->addrs; // Search existing inline extents. int n = 0; for (n = 0; n < (int)MAX_INLINE_EXTENTS; n++) { if (ext[n].len == 0) break; if (fbn >= ext[n].start_block && fbn < ext[n].start_block + ext[n].len) return ext[n].start_blkaddr + (fbn - ext[n].start_block); } // Allocate a new physical block. check_disk_space(); uint addr = freeblock++; // Try to extend the previous extent if the blocks are contiguous. if (n > 0) { struct fsv1_extent *last = &ext[n - 1]; if (fbn == last->start_block + last->len && addr == last->start_blkaddr + last->len) { last->len++; return addr; } } else { // Very first block. ext[0].start_block = 0; ext[0].start_blkaddr = addr; ext[0].len = 1; return addr; } // Need a new extent entry. if (n >= (int)MAX_INLINE_EXTENTS) { fprintf(stderr, "alloc_bmap_extent: too many extents for inline array " "(MAX_INLINE_EXTENTS=%d). " "Add indirect extent block support if files are this large.\n", (int)MAX_INLINE_EXTENTS); exit(1); } ext[n].start_block = fbn; ext[n].start_blkaddr = addr; ext[n].len = 1; return addr; } static uint alloc_bmap(struct dinode *dp, uint fbn) { // Dispatch to extent path for T_EXTENT inodes. if (xshort(dp->type) == T_EXTENT) return alloc_bmap_extent(dp, fbn); // Classic direct / singly / doubly / triply indirect. uint indirect[NINDIRECT]; if (fbn < NDIRECT) { if (xint(dp->addrs[fbn]) == 0) { check_disk_space(); dp->addrs[fbn] = xint(freeblock++); } return xint(dp->addrs[fbn]); } fbn -= NDIRECT; // Singly indirect. if (fbn < NINDIRECT) { if (xint(dp->addrs[NDIRECT]) == 0) { check_disk_space(); dp->addrs[NDIRECT] = xint(freeblock++); } rsect(xint(dp->addrs[NDIRECT]), (char *)indirect); if (indirect[fbn] == 0) { check_disk_space(); indirect[fbn] = xint(freeblock++); wsect(xint(dp->addrs[NDIRECT]), (char *)indirect); } return xint(indirect[fbn]); } fbn -= NINDIRECT; // Doubly indirect. if (fbn < NINDIRECT * NINDIRECT) { if (xint(dp->addrs[NDIRECT + 1]) == 0) { check_disk_space(); dp->addrs[NDIRECT + 1] = xint(freeblock++); } rsect(xint(dp->addrs[NDIRECT + 1]), (char *)indirect); uint idx1 = fbn / NINDIRECT; if (indirect[idx1] == 0) { check_disk_space(); indirect[idx1] = xint(freeblock++); wsect(xint(dp->addrs[NDIRECT + 1]), (char *)indirect); } uint lvl2 = xint(indirect[idx1]); uint ind2[NINDIRECT]; rsect(lvl2, (char *)ind2); uint idx2 = fbn % NINDIRECT; if (ind2[idx2] == 0) { check_disk_space(); ind2[idx2] = xint(freeblock++); wsect(lvl2, (char *)ind2); } return xint(ind2[idx2]); } fbn -= NINDIRECT * NINDIRECT; // Triply indirect. if (fbn < NINDIRECT * NINDIRECT * NINDIRECT) { if (xint(dp->addrs[NDIRECT + 2]) == 0) { check_disk_space(); dp->addrs[NDIRECT + 2] = xint(freeblock++); } rsect(xint(dp->addrs[NDIRECT + 2]), (char *)indirect); uint idx1 = fbn / (NINDIRECT * NINDIRECT); if (indirect[idx1] == 0) { check_disk_space(); indirect[idx1] = xint(freeblock++); wsect(xint(dp->addrs[NDIRECT + 2]), (char *)indirect); } uint lvl2 = xint(indirect[idx1]); uint ind2[NINDIRECT]; rsect(lvl2, (char *)ind2); uint idx2 = (fbn / NINDIRECT) % NINDIRECT; if (ind2[idx2] == 0) { check_disk_space(); ind2[idx2] = xint(freeblock++); wsect(lvl2, (char *)ind2); } uint lvl3 = xint(ind2[idx2]); uint ind3[NINDIRECT]; rsect(lvl3, (char *)ind3); uint idx3 = fbn % NINDIRECT; if (ind3[idx3] == 0) { check_disk_space(); ind3[idx3] = xint(freeblock++); wsect(lvl3, (char *)ind3); } return xint(ind3[idx3]); } die("alloc_bmap: fbn out of range"); return 0; } // --------------------------------------------------------------------------- // iappend: append `n` bytes from `xp` to inode `inum`. // --------------------------------------------------------------------------- void iappend(uint inum, void *xp, int n) { char *p = (char *)xp; uint fbn, off, n1; struct dinode din; char buf[BSIZE]; uint x; rinode(inum, &din); off = xint(din.size); while (n > 0) { fbn = off / BSIZE; assert(fbn < MAXFILE); x = alloc_bmap(&din, fbn); n1 = min((uint)n, (fbn + 1) * BSIZE - off); rsect(x, buf); bcopy(p, buf + off - (fbn * BSIZE), n1); wsect(x, buf); n -= n1; off += n1; p += n1; } din.size = xint(off); din.mtime = mkfs_now(); din.ctime = mkfs_now(); // Recompute checksum after modifying size/mtime/ctime/addrs. din.checksum = inode_checksum(&din); winode(inum, &din); } // --------------------------------------------------------------------------- // Directory HTree helpers // --------------------------------------------------------------------------- static unsigned int BKDRHash(char *str) { unsigned int seed = 131; unsigned int hash = 0; while (*str) hash = hash * seed + (*str++); return (hash % HASHRANGE) + 16; } // Ensure that all blocks from 0 up to the block containing `target_off` exist. // Returns the physical block address of the block containing `target_off`. static uint ensure_blocks_up_to(struct dinode *dp, uint target_off) { uint target_block = target_off / BSIZE; uint baddr = 0; for (uint b = 0; b <= target_block; b++) baddr = alloc_bmap(dp, b); uint required = (target_block + 1) * BSIZE; if (required > xint(dp->size)) dp->size = xint(required); return baddr; } // Search one bucket range [range_start, range_end) for a free dirent slot. // Returns 1 and writes the entry if found; 0 if the bucket is full. static int try_insert_in_range(struct dinode *dp, struct schfs_dirent *de, uint range_start, uint range_end) { char buf[BSIZE]; for (uint off = range_start; off < range_end; off += sizeof(struct schfs_dirent)) { uint baddr = ensure_blocks_up_to(dp, off); rsect(baddr, buf); struct schfs_dirent *slot = (struct schfs_dirent *)(buf + (off % BSIZE)); if (slot->inum == 0) { bcopy(de, buf + (off % BSIZE), sizeof(struct schfs_dirent)); wsect(baddr, buf); assert(off % 16 == 0); return 1; } } return 0; } // Insert `de` into the HTree directory `inum`. // Primary bucket determined by BKDR hash; overflow region used if full. void iappend_init_dir(uint inum, struct schfs_dirent *de) { struct dinode dir_inode; rinode(inum, &dir_inode); unsigned int hash = BKDRHash(de->name); uint primary_start = (uint)(hash * DIRENT_BUCKET_BYTES); uint primary_end = (uint)((hash * 16 + 16) * sizeof(struct schfs_dirent)); // Try primary bucket. if (try_insert_in_range(&dir_inode, de, primary_start, primary_end)) { dir_inode.mtime = mkfs_now(); dir_inode.ctime = mkfs_now(); dir_inode.checksum = inode_checksum(&dir_inode); winode(inum, &dir_inode); return; } // Primary bucket full -- fall back to overflow region. printf("iappend_init_dir: '%s' overflows primary bucket (hash=%u), " "using overflow region [%u, %u)\n", de->name, hash, OVERFLOW_DIR_START, OVERFLOW_DIR_END); if (try_insert_in_range(&dir_inode, de, OVERFLOW_DIR_START, OVERFLOW_DIR_END)) { dir_inode.mtime = mkfs_now(); dir_inode.ctime = mkfs_now(); dir_inode.checksum = inode_checksum(&dir_inode); winode(inum, &dir_inode); return; } fprintf(stderr, "mkfs: directory full -- '%s' could not be inserted " "(both primary bucket and overflow region are full).\n" "Increase OVERFLOW_DIR_END or HASHRANGE.\n", de->name); exit(1); } // --------------------------------------------------------------------------- // Inode bitmap // --------------------------------------------------------------------------- void setibmap(int used) { uchar buf[BSIZE]; int bmap_block = 0; printf("setibmap: first %d inodes have been allocated\n", used); bzero(buf, BSIZE); for (int i = 0; i < used; i++) { buf[(i % (BSIZE * 8)) / 8] |= (1 << (i % 8)); if ((i + 1) % (BSIZE * 8) == 0 || i == used - 1) { wsect(xint(sb.ibmapstart) + bmap_block, buf); bzero(buf, BSIZE); bmap_block++; } } } // --------------------------------------------------------------------------- // Superblock finalisation (called last) // --------------------------------------------------------------------------- void updatesb(void) { uchar buf[BSIZE]; sb.freeinodes = xint(ninodes - freeinode); sb.freeblocks = xint(fssize - freeblock); // Compute superblock checksum (same algorithm as fs.c::updatesb). sb.checksum = 0; sb.checksum = crc32c(0, &sb, sizeof(sb)); memset(buf, 0, sizeof(buf)); memmove(buf, &sb, sizeof(sb)); wsect(1, buf); printf("updatesb: %u free blocks, %u free inodes, checksum=0x%08X\n", xint(sb.freeblocks), xint(sb.freeinodes), sb.checksum); } // --------------------------------------------------------------------------- // Error helper // --------------------------------------------------------------------------- void die(const char *s) { perror(s); exit(1); }