428 lines
16 KiB
Plaintext
428 lines
16 KiB
Plaintext
--: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
|