An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
1244 lines
39 KiB
C
1244 lines
39 KiB
C
// 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 <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <assert.h>
|
|
#include <time.h>
|
|
#include <stdint.h>
|
|
|
|
// 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))
|
|
|
|
// 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.
|
|
#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
|
|
|
|
#define stat xv6_stat
|
|
#define sigset_t xv6_sigset_t
|
|
#define dirent schfs_dirent
|
|
#include "../include/types.h"
|
|
#include "../include/param.h"
|
|
#include "../include/fs.h"
|
|
#include "../include/stat.h"
|
|
#undef dirent
|
|
#undef stat
|
|
#include <dirent.h>
|
|
// After the #undef above, so this gets the host's struct stat and not xv6's.
|
|
#include <sys/stat.h>
|
|
|
|
#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 <path>\n"
|
|
" mkfs fs.img [options] file...\n"
|
|
"\n"
|
|
" --size <blocks> image size (default FSSIZE = %d)\n"
|
|
" --ninodes <n> inode table size (default %d)\n"
|
|
" --exclude <path> 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/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 <path> 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);
|
|
}
|