--:Minify:-- local passed, failed = 0, 0 local function pass(msg) syscall.devctl(1,"sfgc",10); print(" PASS "..msg); syscall.devctl(1,"sfgc",1) end local function fail(msg) syscall.devctl(1,"sfgc",2); print(" FAIL "..msg); syscall.devctl(1,"sfgc",1) end local function info(msg) syscall.devctl(1,"sfgc",14); print(" .... "..msg); syscall.devctl(1,"sfgc",1) end local function head(msg) syscall.devctl(1,"sfgc",4); print("\n"..msg); syscall.devctl(1,"sfgc",1) end local function check(label, ok, err) if ok then passed = passed + 1; pass(label) else failed = failed + 1; fail(label.." - "..tostring(err)) end end local function writeFile(path, data) local ok, fd = pcall(syscall.open, path, "w") if not ok then return false, fd end local ok2, err = pcall(syscall.write, fd, data) pcall(syscall.close, fd) return ok2, err end local function readFile(path) local ok, fd = pcall(syscall.open, path, "r") if not ok then return false, fd end local ok2, data = pcall(syscall.read, fd, 65536) pcall(syscall.close, fd) return ok2, data end local function rmrf(path) local t = syscall.type(path) if t == "directory" then local ok, entries = pcall(syscall.listdir, path) if ok then for _, name in ipairs(entries) do rmrf(path:gsub("/$","").."/"..name) end end pcall(syscall.remove, path) elseif t == "file" then pcall(syscall.remove, path) end end local SCRATCH = "/tmp/looptest_scratch" local SRC_DIR = SCRATCH.."/src" local BIND_MNT = SCRATCH.."/bind_mnt" local IMG_PATH = SCRATCH.."/test.hfs" local IMG_MNT = SCRATCH.."/img_mnt" local BIND_LOOP = nil local IMG_LOOP = nil rmrf(SCRATCH) pcall(syscall.mkdir, SCRATCH) pcall(syscall.mkdir, SRC_DIR) pcall(syscall.mkdir, BIND_MNT) pcall(syscall.mkdir, IMG_MNT) pcall(syscall.mkdir, SRC_DIR.."/subdir") writeFile(SRC_DIR.."/hello.txt", "hello from hyperion\n") writeFile(SRC_DIR.."/data.txt", "line1\nline2\nline3\n") writeFile(SRC_DIR.."/subdir/deep.txt", "deep file\n") head("[ 1 ] bind mode - losetup on a directory") do local ok, id = pcall(syscall.losetup, SRC_DIR) check("losetup(dir) returns a loop id", ok and type(id) == "string" and id:sub(1,4) == "loop", id) if ok then BIND_LOOP = id info("attached as "..id) end end head("[ 2 ] bind mode - mount and read files") do if BIND_LOOP then local mok = pcall(syscall.mount, BIND_MNT, BIND_LOOP) check("mount(bind_mnt, "..BIND_LOOP..")", mok, "mount failed") if mok then local lok, entries = pcall(syscall.listdir, BIND_MNT) check("listdir through bind mount", lok and type(entries) == "table", entries) local rok, data = readFile(BIND_MNT.."/hello.txt") check("read hello.txt through bind", rok and data == "hello from hyperion\n", rok and ("got: "..tostring(data):sub(1,40)) or tostring(data)) local rok2, data2 = readFile(BIND_MNT.."/subdir/deep.txt") check("read subdir/deep.txt through bind", rok2 and data2 == "deep file\n", rok2 and ("got: "..tostring(data2)) or tostring(data2)) end else check("mount (skipped - no loop id)", false, "losetup failed in [1]") end end head("[ 3 ] bind mode - write through loop mount, verify on host") do if BIND_LOOP then local wok, werr = writeFile(BIND_MNT.."/written.txt", "written via loop\n") check("write new file through bind mount", wok, werr) local rok, data = readFile(BIND_MNT.."/written.txt") check("read back through bind mount", rok and data == "written via loop\n", rok and ("got: "..tostring(data)) or tostring(data)) local rok2, data2 = readFile(SRC_DIR.."/written.txt") check("file visible on host path (bind is transparent)", rok2 and data2 == "written via loop\n", rok2 and ("got: "..tostring(data2)) or tostring(data2)) else check("write (skipped)", false, "bind mount not set up") end end head("[ 4 ] bind mode - lodetach while mounted returns EBUSY") do if BIND_LOOP then local ok = pcall(syscall.lodetach, BIND_LOOP) check("lodetach while mounted is refused (EBUSY)", not ok, "should have errored") else check("lodetach busy check (skipped)", false, "no bind loop") end end head("[ 5 ] bind mode - umount then lodetach") do if BIND_LOOP then local uok = pcall(syscall.umount, BIND_MNT) check("umount(bind_mnt)", uok, "umount failed") local dok = pcall(syscall.lodetach, BIND_LOOP) check("lodetach after umount", dok, "lodetach failed") if dok then BIND_LOOP = nil end else check("umount+lodetach (skipped)", false, "no bind loop") end end head("[ 6 ] loimgcreate - serialise directory to HFS image") do local ok, imgStr = pcall(syscall.loimgcreate, SRC_DIR) check("loimgcreate(srcdir) returns a string", ok and type(imgStr) == "string" and #imgStr > 0, imgStr) if ok then info("image size: "..#imgStr.." bytes") local isBHFS = imgStr:sub(1, 4) == "BHFS" check("image has BHFS magic header", isBHFS, "got: "..imgStr:sub(1,4):gsub(".", function(c) return string.format("%02X ", c:byte()) end)) check("image has correct version byte (0x01)", imgStr:byte(5) == 1, "version byte: "..tostring(imgStr:byte(5))) check("image contains FILE record (type=0x01)", imgStr:find("\001", 9, true) ~= nil, "no FILE type byte found") check("image contains DIR record (type=0x02)", imgStr:find("\002", 9, true) ~= nil, "no DIR type byte found") check("image ends with END record (type=0xFF)", imgStr:byte(#imgStr) == 0xFF, "last byte: 0x"..string.format("%02X", imgStr:byte(#imgStr))) local wok, werr = pcall(syscall.loimgwrite, imgStr, IMG_PATH) check("loimgwrite writes image file", wok, werr) check("image file exists on disk", syscall.type(IMG_PATH) == "file", "file not found") end end head("[ 7 ] HFS image - losetup attaches image file") do if syscall.type(IMG_PATH) == "file" then local ok, id = pcall(syscall.losetup, IMG_PATH) check("losetup(image.hfs) returns loop id", ok and type(id) == "string", id) if ok then IMG_LOOP = id info("image attached as "..id) local lok, devs = pcall(syscall.lolist) local found = false if lok then for lid, info_entry in pairs(devs) do if lid == id then found = true end end end check("lolist() contains new image device", found, "not found in lolist") end else check("losetup image (skipped - no image file)", false, "image not created in [6]") end end head("[ 8 ] HFS image - mount and read files") do if IMG_LOOP then local mok = pcall(syscall.mount, IMG_MNT, IMG_LOOP) check("mount(img_mnt, "..IMG_LOOP..")", mok, "mount failed") if mok then local lok, entries = pcall(syscall.listdir, IMG_MNT) check("listdir through image mount returns table", lok and type(entries) == "table", entries) if lok then local hasHello = false local hasSubdir = false for _, e in ipairs(entries) do if e == "hello.txt" then hasHello = true end if e == "subdir" then hasSubdir = true end end check("hello.txt visible in image root", hasHello, "not listed") check("subdir/ visible in image root", hasSubdir, "not listed") end local rok, data = readFile(IMG_MNT.."/hello.txt") check("read hello.txt from image", rok and data == "hello from hyperion\n", rok and ("got: "..tostring(data):sub(1,40)) or tostring(data)) local rok2, data2 = readFile(IMG_MNT.."/data.txt") check("read data.txt from image", rok2 and data2 == "line1\nline2\nline3\n", rok2 and ("got: "..tostring(data2)) or tostring(data2)) end else check("image mount read (skipped)", false, "no image loop") end end head("[ 9 ] HFS image - write new files into image mount") do if IMG_LOOP then local wok, werr = writeFile(IMG_MNT.."/newfile.txt", "created inside image\n") check("write new file into image mount", wok, werr) local rok, data = readFile(IMG_MNT.."/newfile.txt") check("read back newly written file", rok and data == "created inside image\n", rok and ("got: "..tostring(data)) or tostring(data)) local wok2, werr2 = writeFile(IMG_MNT.."/hello.txt", "overwritten\n") check("overwrite existing file in image", wok2, werr2) local rok2, data2 = readFile(IMG_MNT.."/hello.txt") check("overwritten content reads back correctly", rok2 and data2 == "overwritten\n", rok2 and ("got: "..tostring(data2)) or tostring(data2)) local rok3, orig = readFile(IMG_PATH) check("disk image file is unchanged after in-memory write", rok3 and orig and orig:find("/hello%.txt") ~= nil, rok3 and "filename record missing from image" or tostring(orig)) else check("image write test (skipped)", false, "image not mounted") end end head("[ 10 ] HFS image - sub-directory traversal") do if IMG_LOOP then local t = syscall.type(IMG_MNT.."/subdir") check("type(subdir) == 'directory'", t == "directory", "got: "..tostring(t)) local lok, entries = pcall(syscall.listdir, IMG_MNT.."/subdir") check("listdir(subdir) works", lok and type(entries) == "table", entries) local rok, data = readFile(IMG_MNT.."/subdir/deep.txt") check("read subdir/deep.txt from image", rok and data == "deep file\n", rok and ("got: "..tostring(data)) or tostring(data)) local mok = pcall(syscall.mkdir, IMG_MNT.."/subdir/newdir") check("mkdir inside image mount", mok, "mkdir failed") check("new dir has type 'directory'", syscall.type(IMG_MNT.."/subdir/newdir") == "directory", "wrong type") local wok, werr = writeFile(IMG_MNT.."/subdir/newdir/x.txt", "x\n") check("write file in newly created subdir", wok, werr) else check("subdir traversal (skipped)", false, "image not mounted") end end head("[ 11 ] HFS image - lodetach while mounted returns EBUSY") do if IMG_LOOP then local ok = pcall(syscall.lodetach, IMG_LOOP) check("lodetach image while mounted is refused", not ok, "should have errored EBUSY") else check("lodetach busy (skipped)", false, "no image loop") end end head("[ 12 ] HFS image - umount then lodetach") do if IMG_LOOP then local uok = pcall(syscall.umount, IMG_MNT) check("umount(img_mnt)", uok, "umount failed") local dok = pcall(syscall.lodetach, IMG_LOOP) check("lodetach after umount", dok, "lodetach failed") if dok then local lok, devs = pcall(syscall.lolist) local found = false if lok then for lid in pairs(devs) do if lid == IMG_LOOP then found = true end end end check("lolist no longer shows detached device", not found, "still present in lolist") IMG_LOOP = nil end else check("image umount+lodetach (skipped)", false, "no image loop") end end head("[ 13 ] lolist - reflects attached device count") do local ok1, id1 = pcall(syscall.losetup, SRC_DIR) local ok2, id2 = pcall(syscall.losetup, SRC_DIR) check("attach first device for lolist test", ok1, id1) check("attach second device for lolist test", ok2, id2) if ok1 and ok2 then local lok, devs = pcall(syscall.lolist) check("lolist() succeeds", lok, devs) if lok then local found1, found2 = false, false for lid in pairs(devs) do if lid == id1 then found1 = true end if lid == id2 then found2 = true end end check("lolist contains first device", found1, "missing "..id1) check("lolist contains second device", found2, "missing "..id2) local count = 0 for _ in pairs(devs) do count = count + 1 end info("lolist shows "..count.." device(s)") end end if ok1 then pcall(syscall.lodetach, id1) end if ok2 then pcall(syscall.lodetach, id2) end end head("[ 14 ] losetup - non-existent path returns error") do local ok = pcall(syscall.losetup, "/tmp/does_not_exist_xyz_looptest") check("losetup on missing path errors", not ok, "should have errored") end head("[ 15 ] mount - same loop device cannot be mounted twice") do local ok, id = pcall(syscall.losetup, SRC_DIR) check("losetup for double-mount test", ok, id) if ok then local m1ok = pcall(syscall.mount, BIND_MNT, id) check("first mount succeeds", m1ok, "mount failed") if m1ok then local m2ok = pcall(syscall.mount, IMG_MNT, id) check("second mount of same device is refused", not m2ok, "should have errored EBUSY") pcall(syscall.umount, BIND_MNT) end pcall(syscall.lodetach, id) end end head("[ 16 ] loimgcreate - on a regular file returns ENOTDIR") do local ok = pcall(syscall.loimgcreate, IMG_PATH) check("loimgcreate on a file errors (ENOTDIR)", not ok, "should have errored") end head("[ 17 ] HFS image - binary round-trip (all byte values)") do local bytes = {} for i = 0, 255 do bytes[i+1] = string.char(i) end local binData = table.concat(bytes) local binSrc = SCRATCH.."/binsrc" pcall(syscall.mkdir, binSrc) writeFile(binSrc.."/binary.bin", binData) local ok1, imgStr = pcall(syscall.loimgcreate, binSrc) check("loimgcreate handles binary content", ok1, imgStr) if ok1 then local binImg = SCRATCH.."/binary.hfs" pcall(syscall.loimgwrite, imgStr, binImg) local ok2, lid = pcall(syscall.losetup, binImg) check("losetup on binary image", ok2, lid) if ok2 then local mnt = SCRATCH.."/binmnt" pcall(syscall.mkdir, mnt) local mok = pcall(syscall.mount, mnt, lid) check("mount binary image", mok, "mount failed") if mok then local rok, readBack = readFile(mnt.."/binary.bin") check("binary file readable from image", rok, readBack) check("binary data round-trips without corruption", rok and readBack == binData, rok and string.format("length in=%d out=%d", #binData, #(readBack or "")) or tostring(readBack)) pcall(syscall.umount, mnt) end pcall(syscall.lodetach, lid) end end end head("[ 18 ] second-run safety - lolist is empty after full cleanup") do local lok, devs = pcall(syscall.lolist) check("lolist() call succeeds", lok, devs) if lok then local count = 0 for _ in pairs(devs) do count = count + 1 end check("no leftover loop devices after all tests", count == 0, count.." device(s) still attached: ".. (function() local ids = {} for id in pairs(devs) do ids[#ids+1] = id end return table.concat(ids, ", ") end)()) end end rmrf(SCRATCH) print("") syscall.devctl(1, "sfgc", failed == 0 and 10 or 2) print(string.format("Results: %d passed, %d failed", passed, failed)) syscall.devctl(1, "sfgc", 1) if failed > 0 then syscall.exit(1) end