Files
mobile_comm/luatos/air780epm/docs/known_issues.md
T
wangfq 173dd6874f init: Air780EPM 官方 LuatOS 项目代码基线
- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码
- 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等)
- 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
2026-08-31 08:53:49 +08:00

15 KiB

VFS 统一测试 — 已知问题 / Bug 列表

本文件由 testcase/utest/fs/vfs_uniform/ 框架自动/手动追加, 汇总 vfs 统一接口在 6 个文件系统 (ram/posix/lfs2/fatfs/tfs/pgfs) 上的行为差异与已发现 bug.

格式

### <FS>::<test_name>  [severity]
- **Expected**: 一句话预期行为
- **Actual**: 一句话实际行为
- **Source**: 源文件:行号 (定位)
- **Repro**:
  1. 步骤 1
  2. 步骤 2
- **Tags**: fs=<fs>, mode=<mode>, feature=<area>

索引


ram

(empty)

posix

posix::test_dir_nested_mkdir_auto_parent [med]

  • Expected: io.mkdir("a/b/c") 在父目录 b 不存在时应自动创建 (与 ram 行为一致)
  • Actual: io.mkdir 失败, 不创建父目录
  • Source: luat/vfs/luat_fs_posix.c:200-208 (直接调原生 mkdir, 不会递归创建父目录)
  • Repro:
    1. io.mkdir("vfs_dir_a/b/c") (b 不存在)
  • Tags: fs=posix, mode=mkdir, feature=dir

posix::test_edge_deep_nesting [low]

  • Expected: io.mkdir 应能创建 7 级嵌套目录 (a/b/c/d/e/f/g)
  • Actual: io.mkdir 失败, 因为中间父目录 (a, a/b, …) 不存在
  • Source: luat/vfs/luat_fs_posix.c:200-208 (同上, 单一 mkdir 调用)
  • Repro:
    1. io.mkdir("vfs_edge_deep/a/b/c/d/e/f/g")
  • Tags: fs=posix, mode=mkdir, feature=dir

posix::test_meta_rename_overwrite [low]

  • Expected: os.rename 覆盖已存在目标文件应成功
  • Actual: os.rename 失败, 源文件保持不变
  • Source: luat/vfs/luat_fs_posix.c:105-112 (直接调原生 rename; Windows 不允许 overwrite existing target)
  • Repro:
    1. mkdir d; io.open("d/from.txt","wb"); io.open("d/to.txt","wb")
    2. os.rename("d/from.txt", "d/to.txt")
  • Tags: fs=posix, mode=rename, feature=meta, platform=windows

lfs2

lfs2 跑通 26/30 (跳过 1 个 C13 嵌套 mkdir). 真实 bug 4 个 (含 2 个 refcount 缺失).

lfs2::test_edge_long_filename [low]

  • Expected: 60 字符文件名应可创建 (与 ram 一致)
  • Actual: 60 字符文件名 open 失败
  • Source: luat/vfs/luat_fs_lfs2.c (lfs2 默认 name_max=63, 但 PC 配置/驱动对长名更敏感)
  • Repro:
    1. io.open("/lfs2/vfs_edge_<60 chars>.txt", "wb")
  • Tags: fs=lfs2, mode=create, feature=long-name

lfs2::test_edge_deep_nesting [low]

  • Expected: mkdir a/b/c/d/e/f/g 7 级嵌套应成功
  • Actual: 失败
  • Source: luat/vfs/luat_fs_lfs2.c:206-223 (mkdir 不自动创建父目录)
  • Repro:
    1. io.mkdir("/lfs2/vfs_edge_deep/a/b/c/d/e/f/g")
  • Tags: fs=lfs2, mode=mkdir, feature=deep-nesting

lfs2::test_refcount_remove_open_fails [med]

  • Expected: 打开中的文件不应被 os.remove 删除
  • Actual: os.remove 成功了
  • Source: luat/vfs/luat_fs_lfs2.c:134 (直接调 lfs_remove, 不检查引用计数)
  • Repro:
    1. 创文件 p
    2. f = io.open(p, "r")
    3. os.remove(p) -- 期望失败
    4. f:close()
  • Tags: fs=lfs2, mode=read+remove, feature=refcount

lfs2::test_refcount_rename_open_fails [med]

  • Expected: 打开中的源文件不应被 os.rename 重命名
  • Actual: os.rename 成功了
  • Source: luat/vfs/luat_fs_lfs2.c:134 (rename 路径同样不检查引用)
  • Repro:
    1. 创文件 src
    2. f = io.open(src, "r")
    3. os.rename(src, dst) -- 期望失败
    4. f:close()
  • Tags: fs=lfs2, mode=read+rename, feature=refcount

fatfs

fatfs::setup_module_not_found [high]

  • Expected: 当 fatfs 模块在 PC BSP 中不可用时, mount_fatfs.setup() 应返回 false 让 main.lua 自跳过整个测试套
  • Actual: mount_fatfs.lua:9require("fatfs") 在 PC BSP 上直接抛出 "module 'fatfs' not found", VM 在 main.lua 检测 fs_ok 之前就崩溃退出, 测试套根本没有 self-skip 机会
  • Source: testcase/utest/fs/vfs_uniform/scripts/mount_fatfs.lua:9
  • Repro:
    1. 在 PC 模拟器上跑 vfs_uniform_fatfs
    2. mount_fatfs.setup()require("fatfs") 失败, 抛错
    3. 错误沿着 requirevfs_uniform_mount.lua -> mount_fatfs.lua 一路冒泡, VM 退出
    4. main.luaif not fs_ok then ... os.exit(0) end 永远不会执行
  • Tags: fs=fatfs, mode=pc, feature=framework-bootstrap

fatfs::pc_build_fatfs_module_unavailable [high]

  • Expected: PC BSP 应当能 require("fatfs") 加载内置 fatfs 模块 (因为 LUAT_USE_FATFS 已在 bsp/pc/include/luat_conf_bsp.h:145 定义, 且 luaopen_fatfsbsp/pc/port/luat_base_mini.c:160 注册)
  • Actual: 当前 PC 二进制 bsp/pc/build/out/luatos-lua.exerequire("fatfs") 返回 "module not found", 同样 require("uart")/require("json") 等内置模块也都失败; 只有 io/os/string/log/rtos 这类 globals 仍可用. 整个 fatfs 测试套无法在 PC 上执行
  • Source: bsp/pc/port/luat_base_mini.c:159-161 (注册) vs PC BSP 实际加载行为
  • Repro:
    1. print(type(require("fatfs"))) 的最小 main.lua
    2. bsp/pc/build/out/luatos-lua.exe ... /tmp/test/
    3. 输出 module 'fatfs' not found 然后 Lua VM exit!! reboot in 1000ms
  • Tags: fs=fatfs, mode=pc, feature=module-registration

tfs

tfs::tfs_mount_format_name_marker [high]

  • Expected: lf.mount(flash, "/tfs0", 0, 256*1024, {fs="tfs"}) 在 PC 模拟器空白 flash 上应返回 true, mount 后 VFS 即可在 /tfs0 上读写文件
  • Actual: 第一次 lf.mount 返回 false, 日志显示 tfs: format ret=0 read_errors=0 (format 成功) 紧接着 tfs: open name marker failedtfs: format mount failed, 之后才出现 tfs: anchor written chunk=0 seq=4096; 二次 mount 找到 anchor 但仍 tfs: open name marker failed 触发 tfs: probe failed, reformatting, 再次 tfs: open name marker failedtfs: format or mount failed
  • Source: components/little_flash/luat_little_flash_tfs.c:478 (lf_tfs_write_name_markertfs_open 失败) 与 :1226 (tfs: format mount failed)
  • Repro:
    1. 在 PC 上跑 D:/github/LuatOS/bsp/pc/build/out/luatos-lua.exe 并加载 vfs_uniform_tfs/scripts
    2. mount_tfs.luaspi.deviceSetup(1,255,...) + lf.init(spidev) + lf.mount(flash,"/tfs0",0,256*1024,{fs="tfs"})
    3. 观察: format ret=0 → open name marker failed → format mount failed, 紧接着 anchor 写入但 mount 仍失败
  • Tags: fs=tfs, mode=mount, feature=format-name-marker

tfs::all_tests_skipped_mount_unavailable [high]

  • Expected: 30 个共享用例应在 /tfs0 上运行, 大部分通过
  • Actual: 30 个用例全部 0/0 跳过, 因为 tfs mount 失败直接 os.exit(0), 没有任何用例被执行
  • Source: 测试 main.luamount.setup() 返回 false 时退出; 根因是 tfs mount bug
  • Repro:
    1. 跑 vfs_uniform_tfs (与上面 tfs_mount_format_name_marker 同一触发条件)
    2. 日志最后两行: tfs mount 失败 + tfs FS 不可用, 退出
  • Tags: fs=tfs, mode=run, feature=suite-execution

pgfs

pgfs mounted successfully on PC simulator (/pgfs0, 256KB partition, virtual NAND W25N01GVZEIG). However the FTL has very few free blocks at mount time (next=1, total=2, bad=0), and the GC allocator reports alloc_segment: no free blocks whenever a write triggers a new segment allocation. This causes most write/read roundtrip assertions to fail with 0-byte content. Each test below is a separate symptom of the same root cause (or, in two cases, an independent semantic bug).

pgfs::test_refcount_remove_open_fails [med]

  • Expected: 打开中的文件不应被 os.remove 删除 (refcount 应阻止 remove)
  • Actual: os.remove 成功了 (pgfs 直接删除了正在被读取的文件)
  • Source: components/pgfs/luat_pgfs_posix.c (remove 路径未检查 refcount, 沿用 lfs2 的旧语义)
  • Repro:
    1. io.open("/pgfs0/vfs_refcount_rm.txt", "wb") 然后 close
    2. f = io.open("/pgfs0/vfs_refcount_rm.txt", "r")
    3. os.remove("/pgfs0/vfs_refcount_rm.txt") -- 期望失败, 实际成功
    4. f:close()
  • Tags: fs=pgfs, mode=read+remove, feature=refcount

pgfs::test_meta_rename_overwrite [low]

  • Expected: rename 覆盖已存在的目标文件应成功 (POSIX 行为)
  • Actual: rename 失败, 目标文件保持旧内容
  • Source: components/pgfs/luat_pgfs_posix.c rename 实现 (未先 unlink 目标)
  • Repro:
    1. io.mkdir("/pgfs0/d")
    2. /pgfs0/d/from.txt (内容 "new") 和 /pgfs0/d/to.txt (内容 "old")
    3. os.rename("/pgfs0/d/from.txt", "/pgfs0/d/to.txt") -- 期望成功, 实际失败
  • Tags: fs=pgfs, mode=rename, feature=metadata

pgfs::test_basic_write_read_roundtrip [high]

  • Expected: 写入 "hello" 后读取应得到 "hello"
  • Actual: 读出为空字符串 (write 静默失败, 文件大小为 0)
  • Source: components/pgfs/pgfs_alloc_gc.c:78 alloc_segment: no free blocks
    • components/pgfs/luat_pgfs_posix.c (write 路径未检测 segment 分配失败)
  • Repro:
    1. mount pgfs (256KB)
    2. f = io.open("/pgfs0/x.txt", "wb"); f:write("hello"); f:close()
    3. f = io.open("/pgfs0/x.txt", "rb"); f:read("*a") -- 期望 "hello", 实际 ""
  • Tags: fs=pgfs, mode=wb/rb, feature=write-data-loss

pgfs::test_basic_write_read_large [high]

  • Expected: 写入 64KB 数据后读回应得到 65536 字节
  • Actual: 读出 0 字节 (write 静默失败, 因 segment 分配失败)
  • Source: 同 test_basic_write_read_roundtrip (FTL 没有空闲块)
  • Repro:
    1. f = io.open("/pgfs0/big.bin", "wb"); f:write(<64KB>); f:close()
    2. io.open("/pgfs0/big.bin", "rb"):read("*a") -- 期望 65536 字节, 实际 0
  • Tags: fs=pgfs, mode=wb/rb, feature=large-write-data-loss

pgfs::test_edge_block_size_boundary [high]

  • Expected: 4096 字节精确写入读回后, 大小和内容完全一致
  • Actual: 读出 0 字节 (page-aligned 4KB 写入也静默失败)
  • Source: 同 test_basic_write_read_roundtrip (FTL 没有空闲块)
  • Repro:
    1. f = io.open("/pgfs0/block.bin", "wb"); f:write(<4096 bytes>); f:close()
    2. f = io.open("/pgfs0/block.bin", "rb"); f:read("*a") -- 期望 4096, 实际 0
  • Tags: fs=pgfs, mode=wb/rb, feature=block-boundary

pgfs::test_basic_seek_tell [high]

  • Expected: 写入 100 字节, seek('set', 50) 后 tell() 返回 50, 读出第 51 字节 = 50
  • Actual: seek() 返回 0, read 返回空 (文件为 0 字节, write 静默失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.bin", "wb"); f:write(<100 bytes>); f:close()
    2. f = io.open("/pgfs0/x.bin", "rb"); f:seek("set", 50); print(f:seek()) -- 期望 50, 实际 0
  • Tags: fs=pgfs, mode=rb, feature=seek-data-loss

pgfs::test_basic_seek_end [high]

  • Expected: 写入 100 字节后 seek('end') 给出位置 100
  • Actual: seek() 返回 0 (文件为空, write 失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.bin", "wb"); f:write("x"*100); f:close()
    2. f = io.open("/pgfs0/x.bin", "rb"); f:seek("end"); print(f:seek()) -- 期望 100, 实际 0
  • Tags: fs=pgfs, mode=rb, feature=seek-end

pgfs::test_basic_seek_cur [high]

  • Expected: seek('end') 后 seek('cur', -10) 给出位置 90
  • Actual: seek() 返回 0 (文件为空, write 失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.bin", "wb"); f:write("x"*100); f:close()
    2. f = io.open("/pgfs0/x.bin", "rb"); f:seek("end"); f:seek("cur", -10); print(f:seek()) -- 期望 90, 实际 0
  • Tags: fs=pgfs, mode=rb, feature=seek-cur

pgfs::test_meta_fsize [high]

  • Expected: 写入 1234 字节, fs.fsize 应返回 1234
  • Actual: fs.fsize 返回 0 (write 静默失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.bin", "wb"); f:write("Z"*1234); f:close()
    2. fs.fsize("/pgfs0/x.bin") -- 期望 1234, 实际 0
  • Tags: fs=pgfs, mode=wb, feature=fsize

pgfs::test_meta_rename_file [high]

  • Expected: rename a->b 应成功, 之后 b 存在且内容是 "data-a"
  • Actual: rename 返回 err=2 (源文件不存在 / 写入失败, 链式失败)
  • Source: 同 test_basic_write_read_roundtrip (写入失败导致源文件未创建, rename 报 ENOENT)
  • Repro:
    1. f = io.open("/pgfs0/a.txt", "wb"); f:write("data-a"); f:close()
    2. os.rename("/pgfs0/a.txt", "/pgfs0/b.txt") -- 期望 true, 实际 err=2
  • Tags: fs=pgfs, mode=rename, feature=metadata-chain-failure

pgfs::test_basic_append_mode [high]

  • Expected: "foo" + append "bar" 后读出 "foobar"
  • Actual: 读出空字符串 (write 静默失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.txt", "wb"); f:write("foo"); f:close()
    2. f = io.open("/pgfs0/x.txt", "ab"); f:write("bar"); f:close()
    3. io.open("/pgfs0/x.txt", "rb"):read("*a") -- 期望 "foobar", 实际 ""
  • Tags: fs=pgfs, mode=ab, feature=append-data-loss

pgfs::test_posix_mode_w_plus [high]

  • Expected: "w+" 打开后 write("xyz") 再 seek(0) read 应得到 "xyz"
  • Actual: read 返回空字符串 (write 静默失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.txt", "w+"); f:write("xyz"); f:seek("set", 0); print(f:read("*a")) -- 期望 "xyz", 实际 ""
  • Tags: fs=pgfs, mode=w+, feature=mode-flags

pgfs::test_posix_mode_r_plus [high]

  • Expected: "r+" 打开已有文件后 write + seek + read 应得到原长度 (首字节被覆盖)
  • Actual: read 返回 0 字节字符串 (写时静默失败, 文件被截断/清空)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败) + components/pgfs/luat_pgfs_posix.c (r+ 模式 open 后的截断/清空行为)
  • Repro:
    1. f = io.open("/pgfs0/x.txt", "wb"); f:write("initial"); f:close()
    2. f = io.open("/pgfs0/x.txt", "r+"); f:write("X"); f:seek("set", 0); print(f:read("*a")) -- 期望 "Xniti..."(7 字节), 实际 ""
  • Tags: fs=pgfs, mode=r+, feature=mode-flags

pgfs::test_posix_binary_text_same [high]

  • Expected: 写入 0x0A 0x0D 0x00 0xFF 后 read(4) 读出 4 字节
  • Actual: read(4) 返回 nil (文件为空, write 静默失败)
  • Source: 同 test_basic_write_read_roundtrip (根因 = FTL 段分配失败)
  • Repro:
    1. f = io.open("/pgfs0/x.bin", "wb"); f:write("\x0A\x0D\x00\xFF"); f:close()
    2. f = io.open("/pgfs0/x.bin", "rb"); f:read(4) -- 期望 4 字节, 实际 nil
  • Tags: fs=pgfs, mode=rb, feature=binary-mode