]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
kernel: add overlayfs root (initrd+tmpfs) for writable /
authorTulio A M Mendes <[email protected]>
Sat, 7 Feb 2026 23:56:43 +0000 (20:56 -0300)
committerTulio A M Mendes <[email protected]>
Sat, 7 Feb 2026 23:56:43 +0000 (20:56 -0300)
include/overlayfs.h [new file with mode: 0644]
include/tmpfs.h
src/arch/x86/vmm.c
src/kernel/fs.c
src/kernel/init.c
src/kernel/overlayfs.c [new file with mode: 0644]
src/kernel/tmpfs.c
user/init.c

diff --git a/include/overlayfs.h b/include/overlayfs.h
new file mode 100644 (file)
index 0000000..19e976e
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef OVERLAYFS_H
+#define OVERLAYFS_H
+
+#include "fs.h"
+
+fs_node_t* overlayfs_create_root(fs_node_t* lower_root, fs_node_t* upper_root);
+
+#endif
index 8a57ec2c09e3dc70774b673de277c9f7aa9ea5f8..cc5608714a781925c4637f9a57bcd5f02a81e21b 100644 (file)
@@ -7,4 +7,7 @@
 fs_node_t* tmpfs_create_root(void);
 int tmpfs_add_file(fs_node_t* root_dir, const char* name, const uint8_t* data, uint32_t len);
 
+int tmpfs_mkdir_p(fs_node_t* root_dir, const char* path);
+fs_node_t* tmpfs_create_file(fs_node_t* root_dir, const char* path, const uint8_t* data, uint32_t len);
+
 #endif
index f3aa87e8eae0c911baa966d8063be97d247259e9..e671b0b0890eb892d6fe6027f2df171fc3215f2e 100644 (file)
@@ -100,6 +100,9 @@ uintptr_t vmm_as_create_kernel_clone(void) {
         pd_virt[i] = boot_pd[i];
     }
 
+    // Fix recursive mapping: PDE[1023] must point to this PD, not boot_pd.
+    pd_virt[1023] = pd_phys | X86_PTE_PRESENT | X86_PTE_RW;
+
     return (uintptr_t)pd_phys;
 }
 
index 7c162b1cb37f0a7931c9cb2a0213b128649adcd6..de72c38506b4b905409e6db1534658c7e6acb0ac 100644 (file)
@@ -93,12 +93,6 @@ void vfs_close(fs_node_t* node) {
 fs_node_t* vfs_lookup(const char* path) {
     if (!path || !fs_root) return NULL;
 
-    if (strcmp(path, "/") == 0) return fs_root;
-
-    const char* start_path = path;
-    while (*start_path == '/') start_path++;
-    if (*start_path == 0) return fs_root;
-
     fs_node_t* base = fs_root;
     const char* rel = path;
     size_t best_len = 0;
@@ -117,6 +111,7 @@ fs_node_t* vfs_lookup(const char* path) {
         }
     }
 
+    if (!rel) return NULL;
     while (*rel == '/') rel++;
     if (*rel == 0) return base;
 
index 8b96d8588fa8fdda96f43aeee6f31638ca54796d..c0361acd12b88039cfdfc4e65d5ebfb495a04085 100644 (file)
@@ -4,6 +4,7 @@
 
 #include "fs.h"
 #include "initrd.h"
+#include "overlayfs.h"
 #include "tmpfs.h"
 #include "tty.h"
 #include "uart_console.h"
@@ -43,6 +44,16 @@ int init_start(const struct boot_info* bi) {
         }
     }
 
+    if (fs_root) {
+        fs_node_t* upper = tmpfs_create_root();
+        if (upper) {
+            fs_node_t* ovl = overlayfs_create_root(fs_root, upper);
+            if (ovl) {
+                (void)vfs_mount("/", ovl);
+            }
+        }
+    }
+
     fs_node_t* tmp = tmpfs_create_root();
     if (tmp) {
         static const uint8_t hello[] = "hello from tmpfs\n";
diff --git a/src/kernel/overlayfs.c b/src/kernel/overlayfs.c
new file mode 100644 (file)
index 0000000..3fa32e5
--- /dev/null
@@ -0,0 +1,197 @@
+#include "overlayfs.h"
+
+#include "heap.h"
+#include "utils.h"
+#include "tmpfs.h"
+
+#include <stdint.h>
+
+struct overlayfs {
+    fs_node_t* lower;
+    fs_node_t* upper;
+};
+
+struct overlay_node {
+    fs_node_t vfs;
+    struct overlayfs* ofs;
+
+    fs_node_t* lower;
+    fs_node_t* upper;
+
+    char path[256];
+};
+
+static struct fs_node* overlay_finddir_impl(struct fs_node* node, char* name);
+
+static void overlay_str_copy_n(char* dst, size_t dst_sz, const char* src, size_t src_n) {
+    if (!dst || dst_sz == 0) return;
+    size_t i = 0;
+    for (; i + 1 < dst_sz && i < src_n; i++) {
+        char c = src[i];
+        if (c == 0) break;
+        dst[i] = c;
+    }
+    dst[i] = 0;
+}
+
+static uint32_t overlay_read_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+    if (!node) return 0;
+    struct overlay_node* on = (struct overlay_node*)node;
+
+    fs_node_t* src = on->upper ? on->upper : on->lower;
+    if (!src) return 0;
+    return vfs_read(src, offset, size, buffer);
+}
+
+static fs_node_t* overlay_copy_up_file(struct overlay_node* on) {
+    if (!on || on->upper) return on ? on->upper : NULL;
+    if (!on->ofs || !on->ofs->upper) return NULL;
+    if (!on->lower) return NULL;
+    if (on->vfs.flags != FS_FILE) return NULL;
+
+    uint32_t len = on->lower->length;
+    uint8_t* buf = NULL;
+    if (len) {
+        buf = (uint8_t*)kmalloc(len);
+        if (!buf) return NULL;
+        uint32_t rd = vfs_read(on->lower, 0, len, buf);
+        if (rd != len) {
+            if (buf) kfree(buf);
+            return NULL;
+        }
+    }
+
+    fs_node_t* created = tmpfs_create_file(on->ofs->upper, on->path, buf, len);
+    if (buf) kfree(buf);
+    if (!created) return NULL;
+
+    on->upper = created;
+    on->vfs.length = created->length;
+    on->vfs.inode = created->inode;
+    return created;
+}
+
+static uint32_t overlay_write_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
+    if (!node) return 0;
+    struct overlay_node* on = (struct overlay_node*)node;
+
+    fs_node_t* dst = on->upper;
+    if (!dst) {
+        dst = overlay_copy_up_file(on);
+    }
+    if (!dst) return 0;
+
+    uint32_t wr = vfs_write(dst, offset, size, buffer);
+    if (dst->length > on->vfs.length) on->vfs.length = dst->length;
+    return wr;
+}
+
+static fs_node_t* overlay_wrap_child(struct overlay_node* parent, const char* name, fs_node_t* lower_child, fs_node_t* upper_child) {
+    if (!parent || !parent->ofs || !name) return NULL;
+    if (!lower_child && !upper_child) return NULL;
+
+    struct overlay_node* c = (struct overlay_node*)kmalloc(sizeof(*c));
+    if (!c) return NULL;
+    memset(c, 0, sizeof(*c));
+
+    strcpy(c->vfs.name, name);
+    c->ofs = parent->ofs;
+    c->lower = lower_child;
+    c->upper = upper_child;
+
+    if (upper_child) {
+        c->vfs.flags = upper_child->flags;
+        c->vfs.inode = upper_child->inode;
+        c->vfs.length = upper_child->length;
+    } else {
+        c->vfs.flags = lower_child->flags;
+        c->vfs.inode = lower_child->inode;
+        c->vfs.length = lower_child->length;
+    }
+
+    c->vfs.read = &overlay_read_impl;
+    c->vfs.write = (c->vfs.flags == FS_FILE) ? &overlay_write_impl : 0;
+    c->vfs.open = 0;
+    c->vfs.close = 0;
+    c->vfs.finddir = (c->vfs.flags == FS_DIRECTORY) ? &overlay_finddir_impl : 0;
+
+    if (parent->path[0] == 0) {
+        c->path[0] = '/';
+        c->path[1] = 0;
+    } else {
+        strcpy(c->path, parent->path);
+    }
+
+    size_t l = strlen(c->path);
+    if (l == 0) {
+        c->path[0] = '/';
+        c->path[1] = 0;
+        l = 1;
+    }
+    if (l + 1 < sizeof(c->path) && c->path[l - 1] != '/') {
+        c->path[l++] = '/';
+        c->path[l] = 0;
+    }
+
+    size_t cur = strlen(c->path);
+    if (cur + 1 < sizeof(c->path)) {
+        size_t rem = sizeof(c->path) - cur - 1;
+        overlay_str_copy_n(c->path + cur, rem + 1, name, strlen(name));
+    }
+
+    return &c->vfs;
+}
+
+static struct fs_node* overlay_finddir_impl(struct fs_node* node, char* name) {
+    if (!node || !name) return 0;
+    if (node->flags != FS_DIRECTORY) return 0;
+
+    struct overlay_node* dir = (struct overlay_node*)node;
+
+    fs_node_t* upper_child = NULL;
+    fs_node_t* lower_child = NULL;
+
+    if (dir->upper && dir->upper->finddir) {
+        upper_child = dir->upper->finddir(dir->upper, name);
+    }
+    if (dir->lower && dir->lower->finddir) {
+        lower_child = dir->lower->finddir(dir->lower, name);
+    }
+
+    if (!upper_child && !lower_child) return 0;
+    return overlay_wrap_child(dir, name, lower_child, upper_child);
+}
+
+fs_node_t* overlayfs_create_root(fs_node_t* lower_root, fs_node_t* upper_root) {
+    if (!lower_root || !upper_root) return NULL;
+
+    struct overlayfs* ofs = (struct overlayfs*)kmalloc(sizeof(*ofs));
+    if (!ofs) return NULL;
+    ofs->lower = lower_root;
+    ofs->upper = upper_root;
+
+    struct overlay_node* root = (struct overlay_node*)kmalloc(sizeof(*root));
+    if (!root) {
+        kfree(ofs);
+        return NULL;
+    }
+    memset(root, 0, sizeof(*root));
+
+    root->ofs = ofs;
+    root->lower = lower_root;
+    root->upper = upper_root;
+
+    root->vfs.name[0] = 0;
+    root->vfs.flags = FS_DIRECTORY;
+    root->vfs.inode = upper_root->inode;
+    root->vfs.length = 0;
+    root->vfs.read = 0;
+    root->vfs.write = 0;
+    root->vfs.open = 0;
+    root->vfs.close = 0;
+    root->vfs.finddir = &overlay_finddir_impl;
+
+    root->path[0] = 0;
+
+    return &root->vfs;
+}
index c1219fbd6afc0dbdf7d2597a3202b3ee9fbdceee..f3efc33a13779564a13ed7cb75e1de25e3f8de47 100644 (file)
@@ -15,6 +15,9 @@ struct tmpfs_node {
 
 static uint32_t g_tmpfs_next_inode = 1;
 
+static struct fs_node* tmpfs_finddir_impl(struct fs_node* node, char* name);
+static uint32_t tmpfs_write_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer);
+
 static struct tmpfs_node* tmpfs_node_alloc(const char* name, uint32_t flags) {
     struct tmpfs_node* n = (struct tmpfs_node*)kmalloc(sizeof(*n));
     if (!n) return NULL;
@@ -49,6 +52,48 @@ static void tmpfs_child_add(struct tmpfs_node* dir, struct tmpfs_node* child) {
     dir->first_child = child;
 }
 
+static struct tmpfs_node* tmpfs_child_ensure_dir(struct tmpfs_node* dir, const char* name) {
+    if (!dir || !name || name[0] == 0) return NULL;
+    struct tmpfs_node* existing = tmpfs_child_find(dir, name);
+    if (existing) {
+        if (existing->vfs.flags != FS_DIRECTORY) return NULL;
+        return existing;
+    }
+
+    struct tmpfs_node* nd = tmpfs_node_alloc(name, FS_DIRECTORY);
+    if (!nd) return NULL;
+    nd->vfs.read = 0;
+    nd->vfs.write = 0;
+    nd->vfs.open = 0;
+    nd->vfs.close = 0;
+    nd->vfs.finddir = &tmpfs_finddir_impl;
+    tmpfs_child_add(dir, nd);
+    return nd;
+}
+
+static int tmpfs_split_next(const char** p_inout, char* out, size_t out_sz) {
+    if (!p_inout || !*p_inout || !out || out_sz == 0) return 0;
+    const char* p = *p_inout;
+    while (*p == '/') p++;
+    if (*p == 0) {
+        *p_inout = p;
+        out[0] = 0;
+        return 0;
+    }
+
+    size_t i = 0;
+    while (*p != 0 && *p != '/') {
+        if (i + 1 < out_sz) {
+            out[i++] = *p;
+        }
+        p++;
+    }
+    out[i] = 0;
+    while (*p == '/') p++;
+    *p_inout = p;
+    return out[0] != 0;
+}
+
 static uint32_t tmpfs_read_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
     if (!node || !buffer) return 0;
     if (node->flags != FS_FILE) return 0;
@@ -145,3 +190,79 @@ int tmpfs_add_file(fs_node_t* root_dir, const char* name, const uint8_t* data, u
     tmpfs_child_add(dir, f);
     return 0;
 }
+
+int tmpfs_mkdir_p(fs_node_t* root_dir, const char* path) {
+    if (!root_dir || root_dir->flags != FS_DIRECTORY) return -1;
+    if (!path) return -1;
+
+    struct tmpfs_node* cur = (struct tmpfs_node*)root_dir;
+    const char* p = path;
+    char part[128];
+
+    while (tmpfs_split_next(&p, part, sizeof(part))) {
+        struct tmpfs_node* next = tmpfs_child_ensure_dir(cur, part);
+        if (!next) return -1;
+        cur = next;
+    }
+
+    return 0;
+}
+
+fs_node_t* tmpfs_create_file(fs_node_t* root_dir, const char* path, const uint8_t* data, uint32_t len) {
+    if (!root_dir || root_dir->flags != FS_DIRECTORY) return NULL;
+    if (!path) return NULL;
+
+    struct tmpfs_node* cur = (struct tmpfs_node*)root_dir;
+    const char* p = path;
+    char part[128];
+    char leaf[128];
+    leaf[0] = 0;
+
+    while (tmpfs_split_next(&p, part, sizeof(part))) {
+        if (*p == 0) {
+            strcpy(leaf, part);
+            break;
+        }
+        struct tmpfs_node* next = tmpfs_child_ensure_dir(cur, part);
+        if (!next) return NULL;
+        cur = next;
+    }
+
+    if (leaf[0] == 0) return NULL;
+
+    struct tmpfs_node* existing = tmpfs_child_find(cur, leaf);
+    if (existing) {
+        if (existing->vfs.flags != FS_FILE) return NULL;
+        if (len && data) {
+            uint8_t* buf = (uint8_t*)kmalloc(len);
+            if (!buf) return NULL;
+            memcpy(buf, data, len);
+            (void)tmpfs_write_impl(&existing->vfs, 0, len, buf);
+            kfree(buf);
+        }
+        return &existing->vfs;
+    }
+
+    struct tmpfs_node* f = tmpfs_node_alloc(leaf, FS_FILE);
+    if (!f) return NULL;
+
+    f->vfs.read = &tmpfs_read_impl;
+    f->vfs.write = &tmpfs_write_impl;
+    f->vfs.open = 0;
+    f->vfs.close = 0;
+    f->vfs.finddir = 0;
+
+    if (len && data) {
+        f->data = (uint8_t*)kmalloc(len);
+        if (!f->data) {
+            kfree(f);
+            return NULL;
+        }
+        memcpy(f->data, data, len);
+        f->cap = len;
+        f->vfs.length = len;
+    }
+
+    tmpfs_child_add(cur, f);
+    return &f->vfs;
+}
index 2b9ffa9798fde2e8bda7dbe60d9ab31555266f50..aa3c1db5baf5c56139bbf78386ed4c2445a99f17 100644 (file)
@@ -154,14 +154,14 @@ void _start(void) {
     static const char path[] = "/bin/init.elf";
     int fd = sys_open(path, 0);
     if (fd < 0) {
-        sys_write(1, "[init] open failed\n", 18);
+        sys_write(1, "[init] open failed\n", (uint32_t)(sizeof("[init] open failed\n") - 1));
         sys_exit(1);
     }
 
     uint8_t hdr[4];
     int rd = sys_read(fd, hdr, (uint32_t)sizeof(hdr));
     if (sys_close(fd) < 0) {
-        sys_write(1, "[init] close failed\n", 19);
+        sys_write(1, "[init] close failed\n", (uint32_t)(sizeof("[init] close failed\n") - 1));
         sys_exit(1);
     }
 
@@ -169,177 +169,262 @@ void _start(void) {
         sys_write(1, "[init] open/read/close OK (ELF magic)\n",
                   (uint32_t)(sizeof("[init] open/read/close OK (ELF magic)\n") - 1));
     } else {
-        sys_write(1, "[init] read failed or bad header\n", 30);
+        sys_write(1, "[init] read failed or bad header\n",
+                  (uint32_t)(sizeof("[init] read failed or bad header\n") - 1));
         sys_exit(1);
     }
 
     fd = sys_open("/bin/init.elf", 0);
     if (fd < 0) {
-        sys_write(1, "[init] open2 failed\n", 19);
+        sys_write(1, "[init] overlay open failed\n",
+                  (uint32_t)(sizeof("[init] overlay open failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t orig0 = 0;
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &orig0, 1) != 1) {
+        sys_write(1, "[init] overlay read failed\n",
+                  (uint32_t)(sizeof("[init] overlay read failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t x = (uint8_t)(orig0 ^ 0xFF);
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &x, 1) != 1) {
+        sys_write(1, "[init] overlay write failed\n",
+                  (uint32_t)(sizeof("[init] overlay write failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[init] overlay close failed\n",
+                  (uint32_t)(sizeof("[init] overlay close failed\n") - 1));
+        sys_exit(1);
+    }
+
+    fd = sys_open("/bin/init.elf", 0);
+    if (fd < 0) {
+        sys_write(1, "[init] overlay open2 failed\n",
+                  (uint32_t)(sizeof("[init] overlay open2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    uint8_t chk = 0;
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_read(fd, &chk, 1) != 1 || chk != x) {
+        sys_write(1, "[init] overlay verify failed\n",
+                  (uint32_t)(sizeof("[init] overlay verify failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_lseek(fd, 0, SEEK_SET) < 0 || sys_write(fd, &orig0, 1) != 1) {
+        sys_write(1, "[init] overlay restore failed\n",
+                  (uint32_t)(sizeof("[init] overlay restore failed\n") - 1));
+        sys_exit(1);
+    }
+
+    if (sys_close(fd) < 0) {
+        sys_write(1, "[init] overlay close2 failed\n",
+                  (uint32_t)(sizeof("[init] overlay close2 failed\n") - 1));
+        sys_exit(1);
+    }
+
+    sys_write(1, "[init] overlay copy-up OK\n",
+              (uint32_t)(sizeof("[init] overlay copy-up OK\n") - 1));
+
+    fd = sys_open("/bin/init.elf", 0);
+    if (fd < 0) {
+        sys_write(1, "[init] open2 failed\n", (uint32_t)(sizeof("[init] open2 failed\n") - 1));
         sys_exit(1);
     }
 
     struct stat st;
     if (sys_fstat(fd, &st) < 0) {
-        sys_write(1, "[init] fstat failed\n", 19);
+        sys_write(1, "[init] fstat failed\n", (uint32_t)(sizeof("[init] fstat failed\n") - 1));
         sys_exit(1);
     }
 
     if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) {
-        sys_write(1, "[init] fstat bad\n", 16);
+        sys_write(1, "[init] fstat bad\n", (uint32_t)(sizeof("[init] fstat bad\n") - 1));
         sys_exit(1);
     }
 
     if (sys_lseek(fd, 0, SEEK_SET) < 0) {
-        sys_write(1, "[init] lseek set failed\n", 24);
+        sys_write(1, "[init] lseek set failed\n",
+                  (uint32_t)(sizeof("[init] lseek set failed\n") - 1));
         sys_exit(1);
     }
 
     uint8_t m2[4];
     if (sys_read(fd, m2, 4) != 4) {
-        sys_write(1, "[init] read2 failed\n", 19);
+        sys_write(1, "[init] read2 failed\n", (uint32_t)(sizeof("[init] read2 failed\n") - 1));
         sys_exit(1);
     }
     if (m2[0] != 0x7F || m2[1] != 'E' || m2[2] != 'L' || m2[3] != 'F') {
-        sys_write(1, "[init] lseek/read mismatch\n", 27);
+        sys_write(1, "[init] lseek/read mismatch\n",
+                  (uint32_t)(sizeof("[init] lseek/read mismatch\n") - 1));
         sys_exit(1);
     }
 
     if (sys_close(fd) < 0) {
-        sys_write(1, "[init] close2 failed\n", 20);
+        sys_write(1, "[init] close2 failed\n", (uint32_t)(sizeof("[init] close2 failed\n") - 1));
         sys_exit(1);
     }
 
     if (sys_stat("/bin/init.elf", &st) < 0) {
-        sys_write(1, "[init] stat failed\n", 18);
+        sys_write(1, "[init] stat failed\n", (uint32_t)(sizeof("[init] stat failed\n") - 1));
         sys_exit(1);
     }
     if ((st.st_mode & S_IFMT) != S_IFREG || st.st_size == 0) {
-        sys_write(1, "[init] stat bad\n", 15);
+        sys_write(1, "[init] stat bad\n", (uint32_t)(sizeof("[init] stat bad\n") - 1));
         sys_exit(1);
     }
 
-    sys_write(1, "[init] lseek/stat/fstat OK\n", 27);
+    sys_write(1, "[init] lseek/stat/fstat OK\n",
+              (uint32_t)(sizeof("[init] lseek/stat/fstat OK\n") - 1));
 
     fd = sys_open("/tmp/hello.txt", 0);
     if (fd < 0) {
-        sys_write(1, "[init] tmpfs open failed\n", 24);
+        sys_write(1, "[init] tmpfs open failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs open failed\n") - 1));
         sys_exit(1);
     }
 
     if (sys_stat("/tmp/hello.txt", &st) < 0) {
-        sys_write(1, "[init] tmpfs stat failed\n", 24);
+        sys_write(1, "[init] tmpfs stat failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs stat failed\n") - 1));
         sys_exit(1);
     }
     if ((st.st_mode & S_IFMT) != S_IFREG) {
-        sys_write(1, "[init] tmpfs stat not reg\n", 26);
+        sys_write(1, "[init] tmpfs stat not reg\n",
+                  (uint32_t)(sizeof("[init] tmpfs stat not reg\n") - 1));
         sys_exit(1);
     }
     if (st.st_size == 0) {
-        sys_write(1, "[init] tmpfs stat size 0\n", 25);
+        sys_write(1, "[init] tmpfs stat size 0\n",
+                  (uint32_t)(sizeof("[init] tmpfs stat size 0\n") - 1));
         sys_exit(1);
     }
 
     struct stat fst;
     if (sys_fstat(fd, &fst) < 0) {
-        sys_write(1, "[init] tmpfs fstat failed\n", 25);
+        sys_write(1, "[init] tmpfs fstat failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs fstat failed\n") - 1));
         sys_exit(1);
     }
     if (fst.st_size != st.st_size) {
-        sys_write(1, "[init] tmpfs stat size mismatch\n", 31);
+        sys_write(1, "[init] tmpfs stat size mismatch\n",
+                  (uint32_t)(sizeof("[init] tmpfs stat size mismatch\n") - 1));
         sys_exit(1);
     }
 
     int end = sys_lseek(fd, 0, SEEK_END);
     if (end < 0 || (uint32_t)end != st.st_size) {
-        sys_write(1, "[init] tmpfs lseek end bad\n", 27);
+        sys_write(1, "[init] tmpfs lseek end bad\n",
+                  (uint32_t)(sizeof("[init] tmpfs lseek end bad\n") - 1));
         sys_exit(1);
     }
 
     uint8_t eofb;
     if (sys_read(fd, &eofb, 1) != 0) {
-        sys_write(1, "[init] tmpfs eof read bad\n", 27);
+        sys_write(1, "[init] tmpfs eof read bad\n",
+                  (uint32_t)(sizeof("[init] tmpfs eof read bad\n") - 1));
         sys_exit(1);
     }
 
     if (sys_lseek(fd, 0, 999) >= 0) {
-        sys_write(1, "[init] tmpfs lseek whence bad\n", 30);
+        sys_write(1, "[init] tmpfs lseek whence bad\n",
+                  (uint32_t)(sizeof("[init] tmpfs lseek whence bad\n") - 1));
         sys_exit(1);
     }
 
     if (sys_lseek(fd, 0, SEEK_SET) < 0) {
-        sys_write(1, "[init] tmpfs lseek set failed\n", 30);
+        sys_write(1, "[init] tmpfs lseek set failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs lseek set failed\n") - 1));
         sys_exit(1);
     }
 
     uint8_t tbuf[6];
     if (sys_read(fd, tbuf, 5) != 5) {
-        sys_write(1, "[init] tmpfs read failed\n", 24);
+        sys_write(1, "[init] tmpfs read failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs read failed\n") - 1));
         sys_exit(1);
     }
     tbuf[5] = 0;
     if (tbuf[0] != 'h' || tbuf[1] != 'e' || tbuf[2] != 'l' || tbuf[3] != 'l' || tbuf[4] != 'o') {
-        sys_write(1, "[init] tmpfs bad data\n", 22);
+        sys_write(1, "[init] tmpfs bad data\n", (uint32_t)(sizeof("[init] tmpfs bad data\n") - 1));
         sys_exit(1);
     }
 
     if (sys_close(fd) < 0) {
-        sys_write(1, "[init] tmpfs close failed\n", 25);
+        sys_write(1, "[init] tmpfs close failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs close failed\n") - 1));
         sys_exit(1);
     }
 
     if (sys_open("/tmp/does_not_exist", 0) >= 0) {
-        sys_write(1, "[init] tmpfs open nonexist bad\n", 32);
+        sys_write(1, "[init] tmpfs open nonexist bad\n",
+                  (uint32_t)(sizeof("[init] tmpfs open nonexist bad\n") - 1));
         sys_exit(1);
     }
 
     fd = sys_open("/tmp/hello.txt", 0);
     if (fd < 0) {
-        sys_write(1, "[init] tmpfs open3 failed\n", 25);
+        sys_write(1, "[init] tmpfs open3 failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs open3 failed\n") - 1));
         sys_exit(1);
     }
 
     if (sys_fstat(fd, &fst) < 0) {
-        sys_write(1, "[init] tmpfs fstat2 failed\n", 26);
+        sys_write(1, "[init] tmpfs fstat2 failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs fstat2 failed\n") - 1));
         sys_exit(1);
     }
 
     if (sys_lseek(fd, 0, SEEK_END) < 0) {
-        sys_write(1, "[init] tmpfs lseek end2 failed\n", 31);
+        sys_write(1, "[init] tmpfs lseek end2 failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs lseek end2 failed\n") - 1));
         sys_exit(1);
     }
 
-    static const char suf[] = "XYZ";
+    char suf[3];
+    suf[0] = 'X';
+    suf[1] = 'Y';
+    suf[2] = 'Z';
     if (sys_write(fd, suf, 3) != 3) {
-        sys_write(1, "[init] tmpfs write failed\n", 25);
+        sys_write(1, "[init] tmpfs write failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs write failed\n") - 1));
         sys_exit(1);
     }
 
     if (sys_fstat(fd, &fst) < 0) {
-        sys_write(1, "[init] tmpfs fstat3 failed\n", 26);
+        sys_write(1, "[init] tmpfs fstat3 failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs fstat3 failed\n") - 1));
         sys_exit(1);
     }
     if (fst.st_size != st.st_size + 3) {
-        sys_write(1, "[init] tmpfs size not grown\n", 27);
+        sys_write(1, "[init] tmpfs size not grown\n",
+                  (uint32_t)(sizeof("[init] tmpfs size not grown\n") - 1));
         sys_exit(1);
     }
 
     if (sys_lseek(fd, -3, SEEK_END) < 0) {
-        sys_write(1, "[init] tmpfs lseek back failed\n", 31);
+        sys_write(1, "[init] tmpfs lseek back failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs lseek back failed\n") - 1));
         sys_exit(1);
     }
     uint8_t s2[3];
     if (sys_read(fd, s2, 3) != 3 || s2[0] != 'X' || s2[1] != 'Y' || s2[2] != 'Z') {
-        sys_write(1, "[init] tmpfs suffix mismatch\n", 27);
+        sys_write(1, "[init] tmpfs suffix mismatch\n",
+                  (uint32_t)(sizeof("[init] tmpfs suffix mismatch\n") - 1));
         sys_exit(1);
     }
 
     if (sys_close(fd) < 0) {
-        sys_write(1, "[init] tmpfs close3 failed\n", 26);
+        sys_write(1, "[init] tmpfs close3 failed\n",
+                  (uint32_t)(sizeof("[init] tmpfs close3 failed\n") - 1));
         sys_exit(1);
     }
 
-    sys_write(1, "[init] tmpfs/mount OK\n", 22);
+    sys_write(1, "[init] tmpfs/mount OK\n", (uint32_t)(sizeof("[init] tmpfs/mount OK\n") - 1));
 
     enum { NCHILD = 100 };
     int children[NCHILD];