跳转至

文件系统(io)

一、文件系统(io)的概述

Air8101 工业引擎通过lua标准io库,os库部分函数及luatos自定义扩展的io库函数和fs库,来支持文件系统的访问和操作,包括目录和文件的创建与删除,文件打开和关闭,文件写入和读取数据,文件重命名和查询文件大小,查询目录文件信息,查询文件系统挂载信息等。

通过luatos支持的虚拟文件系统(vfs)方式来实现多种文件系统类型共存使用,如littlefs,fatfs,ram,luadb等,封装统一的库函数接口调用,用户应用不需要关注底层接口细节,可以通过挂载路径来区分不同文件系统的使用。

Air8101 工业引擎默认挂载了以下类型文件系统:

  • littlefs,挂载路径 '/', 可读可写,内容支持掉电保存,划分空间 256KBytes;实际为 248KBytes可用(8KBytes为littlefs的保留空间),用户应用根据需求合理规划和使用该空间。
  • ram, 挂载路径 '/ram/', 可读可写,内容不支持掉电保存,应用可用空间 64KBytes,常用于临时数据的缓存等。
  • luadb, 挂载路径 '/luadb/', 只读,内容掉电保存, 用户可用脚本空间 256KBytes,存储脚本文件和应用资源文件。

通过os.lsmount()可以查询文件系统挂载信息;fs.fsstat(path) 可以查询该文件系统类型的信息,具体可参考章节4.3.7查询文件系统信息对该接口的使用介绍。

littlefs 是一个轻量级的文件系统,专为嵌入式系统设计,特别适用于闪存存储。其特点包括低资源消耗、高可靠性、支持断电保护、支持磨损平衡,以及灵活的配置选项,能够在有限的存储空间和内存资源下运行。
luadb 是luatos中自定义的文件打包格式(可以相当于自定义的一种文件系统类型),通过文件系统io库方式来访问里面的文件,有效的组织lua运行脚本和其他资源文件在系统运行时的读取。具体介绍请查看链接 https://wiki.luatos.com/develop/contribute/luadb.html
ram 是luatos中支持的一种自定义文件系统类型,可以通过统一的io接口函数来读写访问系统部分内存空间。

二、准备硬件环境

“古人云:‘工欲善其事,必先利其器。’在深入介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。”

参考:硬件环境清单,准备以及组装好硬件环境。

三、准备软件环境

“凡事预则立,不预则废。”在详细阐述本功能示例之前,我们需先精心筹备好以下软件环境。

1. Luatools 工具

2. 内核固件文件(底层 core 固件文件):LuatOS-SoC_V10001_Air8101.soc;参考项目使用的内核固件

3. luatos 需要的脚本和资源文件

脚本和资源文件:https://gitee.com/openLuat/LuatOS-Air8101/tree/master/demo/fs

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 开发板,将本篇文章中演示使用的脚本和资源文件烧录到 Air8101 开发板中。

四、文件系统(io)基本用法

4.1 接口功能简介

文件系统支持的接口除了lua标准定义的io库函数,还包括luatos扩展的io库函数,fs库函数及部分os库函数的接口;io库提供了文件处理功能,允许通过文件描述符来读写文件,Lua的文件 I/O 操作可以通过显式和隐式文件描述符来进行管理。

  • 显式文件描述符(Explicit file descriptors)
    • 用户在程序中显式地打开的文件,并且直接通过文件句柄来进行操作。在Lua中,显式文件描述符通常是通过io.open打开的文件句柄。使用这个文件句柄可以显式地控制文件的关闭以及读写等其他相关操作,例如:
-- file为io.open打开文件后返回的文件句柄
local file = io.open("/test.txt")
if file then
    -- 通过file来操作文件关闭
    file:close()
end
  • 隐式文件描述符(Implicit file descriptors)
    • 没有明确指定的文件句柄,但可以通过io库内置的文件对象直接对文件进行隐式的操作,不需要用户处理文件的打开和关闭。
      例如:io.lines("/test.txt")

4.2 文章内容引用

文章所使用的文件系统(io)接口函数不做详细介绍,可通过以下API接口说明参考链接查看具体介绍:

4.3 接口应用介绍

下面章节不同接口对应具体功能应用的介绍,其中描述的file为io.open调用返回的文件句柄。

4.3.1 文件创建打开、关闭与删除

1. 文件创建与打开

使用io.open可以打开文件进行操作,结合不同模式如"w+",若文件不存在,则会创建一个新文件。

    -- 返回参数(file)为文件句柄,如操作失败则返回nil,第二个参数带err信息;
    local file, err = io.open("/test.txt", "w+")

模式 描述
r 只读方式打开文件,该文件必须存在。
w 打开只写文件,若文件存在则文件长度清为零,写入数据会覆盖以旧数据,若文件不存在则创建文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)
r+ 以可读写方式打开文件,该文件必须存在。
w+ 打开可读写文件,若文件存在则文件长度清为零,写入数据会覆盖以旧数据,若文件不存在则创建文件。
a+ 与a类似,但此文件可读可写
b 二进制模式,如果操作二进制文件需加上b(如rb,wb,ab)
+ 表示对文件既可以读也可以写

注: 不同文件系统在文件创建访问有差异:
- littlefs:挂载路径"/" 该路径下创建文件的文件名长度最大为63字节。 - ram:挂载路径"/ram/" 该路径下创建文件的文件名长度最大为31字节, 最大允许创建8个文件。 - luadb: 挂载路径"/luadb/" 该路径下文件都是只读的,不能创建文件。

2. 文件关闭

文件操作完毕后,必须调用file:close()来关闭文件。这样可以释放资源,避免文件损坏。操作前一般需要确认file文件句柄是有效的。

3. 查询文件句柄可用状态

使用io.types(file)可以查询指定文件句柄所指文件的状态 返回值为"file"表示文件打开状态,"closed file"表示文件已关闭状态,nil表示文件句柄无效,第二个参数为err错误信息

4. 删除文件

通过os库中提供的os.remove(path)来进行文件删除操作;删除成功 ret返回true,失败返回nil,第二参数err返回失败信息。
此外可通过luatos扩展的io库接口io.exist(path)来查询文件是否存在;若文件存在则返回true否则返回false。
举例:

    local filename = "test.txt"
    --打开文件,若不存在则创建;
    local file = io.open("/" .. filename, "w+")
    if file then
        -- 检查文件打开状态
        local rc = io.type(file)
        log.info("fs", "io.type: ", "rc")
        file:close()
        -- 关闭文件,可以获取文件关闭状态
        rc = io.type(file)
        assert(string.find(rc, "closed") ~= nil, "Should be the closed file")
    end
    -- 删除文件
    local ret, err = os.remove("/" .. filename)
    -- 删除成功 ret返回true,失败返回nil,第二参数err返回失败信息
    assert(ret ~= nil and ret == true, "Remove file failed") 
    -- 查询文件不存在则文件删除成功
    ret = io.exists(pathto .. filename)
    -- 返回fasle表示文件不存在删除成功
    assert(ret == false, "Should not exist file") 

4.3.2 文件读取数据

1. 字符串带换行结束,可以按行读取文件数据

以下有多种接口可以按行读取数据。

  • 隐式文件描述符方式:

io.lines(path),(即默认为带"l"参数) 和 io.lines(path, "L"),逐行读取文件,返回一个迭代函数,每一次被调用都会返回文件中新的一行的内容,直到文件中所有的内容都被读完,不需要处理文件打开关闭

    -- 获取逐行数据,需要带文件路径作为入参
    for line in io.lines("/test.txt") do
        log.info("fs","readline ", line)
    end

  • 显示文件描述符方式:

file:lines(): (即默认为带"l"参数) 和 file:lines("L"),逐行读取文件,返回一个迭代函数,每一次被调用都会返回文件中新的一行的内容,直到文件中所有的内容都被读完,需要处理文件打开关闭。

    local fdr = io.open("/test.txt")
    -- 获取逐行数据
    for line in fdr:lines() do
        log.info("fs","readline ", line)
    end
    fdr:close()

file:read(): (即默认为带"l"参数) 和 file:read("L"):逐行读取文件

    local fdr = io.open("/test.txt")
    -- 循环按行方式读取,读取不到就退出循环
    while true do
        local line = fdr:read() 
        if not line or #line == 0 then
            break
        end
        log.info("fs","readline ", line)
    end
    fdr:close()

注:
参数"L"为逐行读取文本并返回数据,保留换行符,适用于保留格式
参数"l"为逐行读取文本并返回数据,忽略换行符,适用于处理行内容

2. 通用数据块的方式读取

若为二进制数据文件(bin文件),注意必须在文件打开时mode带上'b'参数(如"rb","wb","ab"等)。

  • 显式文件描述符方式

file:read(n): 读取n个字节数据

    local fdr = io.open("/test.txt", "rb")
    -- 循环按指定数据长度方式读取,读取不到就退出循环
    while true do
        local data = fdr:read(10) 
        if not data or #data == 0 then
            break
        end
        log.info("fs","readdata ", data)
    end
    fdr:close()
file:read("*a"): 读取全部数据并返回

  • 隐式文件描述符方式

luatos提供io.readFile方式,读取指定开始偏移和指定长度的数据;入参分别为文件路径,偏移和读取长度

    -- 从0开始读1024字节数据,若所有数据不到1024字节则返回实际长度数据
    local readdata = io.readFile("/test.txt", "r", 0, 1024)

4.3.3 文件写入数据

有两种模式:截断(覆盖文件)和追加;通过打开文件的模式来指定:

  • 含"w"模式打开文件(默认截断模式)时,若原来文件存在则文件长度清为零(原来内容相当于清空),从文件开头开始写入新的内容并覆盖旧数据;若不存在则新建文件。
  • 含"a"模式打开文件时,数据会被追加到文件末尾,而不会覆盖原有内容。

    -- 创建新文件并写入初始内容
    local file = io.open("/test.txt", "w")
    if file then
        local ret, err = file:write("Initial content\n")
        log.info("fs", "write ret: ", tostring(ret))
        file:close()
    end
    -- 使用写模式打开文件,截断文件
    file = io.open("/test.txt", "w")
    if file then
        local ret, err = file:write("Truncated content\n")
        log.info("fs", "write ret: ", tostring(ret))
        file:close()
    end
    -- 从文件末尾追加内容
    file = io.open("/test.txt", "a")
    if file then
        local ret, err = file:write("Appended content\n")
        log.info("fs", "write ret: ", tostring(ret))
        file:close()
    end
上述例子中,"/test.txt" 包含的字符串为"Truncated content\nAppended content\n"

注:file:write若成功写入则返回有效文件句柄,否则返回nil

此外,通过luatos扩展的io库接口io.writeFile也可以直接创建文件,以截断方式来写入文件数据

    -- 写数据成功返回true,失败返回false
    local rc = io.writeFile("/test.txt", "Hello\n")

4.3.4 文件指针的使用

文件指针决定了读取或写入数据的位置。可以通过file:seek函数来控制文件指针的位置;每次file:read或file:write操作后文件指针都会往后移动,移动offset的大小为写入或读取数据的长度。

file:seek(whence, offset),操作或获取文件指针位置。

  • whence参数有三种取值,不指定offset时可不填,默认值为"cur"

    • "set":文件指针从文件开始位置设置(基点为文件开头从0位置开始算),file:seek("set") 回到文件开头,返回0
    • "cur":文件指针从当前位置设置(基点为当前位置),file:seek("cur") 获取当前文件指针位置
    • "end":文件指针从文件末尾设置(基点为文件尾),file:seek("end")返回文件大小
  • offset为偏移量,默认值为0(可不填),负数为回退,正数为向前移动

当 seek 成功时,返回最终从文件开头计算起的文件的位置。 当 seek 失败时,返回 nil 加上一个错误描述字符串。

    local file = io.open("/test.txt", "r") -- 若测试文件包含数据为"Hello, World!"
    if file then
        file:seek("set", 7)  -- 将指针移动到文件开头0算起,偏移为7
        local content = file:read(2)  -- 当前文件指针位置读取2个字节
        log.info("fs", "content: ", content) -- 这里返回"Wo"
        log.info("fs", "pos", file:seek()) -- 读取后文件指针向后偏移2,这里当前文件指针位置为9
        file:close()
    end

4.3.5 文件重命名

通过os库中提供的os.rename来进行文件重命名操作;无法重命名只读文件或跨文件系统重命名文件,需要确保新文件路径所在的目录存在,以避免重命名失败。

注:重命名操作需要在同一文件系统类型挂载路径下才能操作。

    -- 若重命名成功ret返回true;否则返回nil,第二参数err带错误信息
    local ret, err = os.rename("/oldname.txt", "/newname.txt")
    log.info("fs", "rename ", ret, err)

4.3.6 目录创建删除与查看目录信息

luatos扩展io库函数支持文件系统的目录创建与删除,可以在新建目录下创建其他文件

1. 创建目录:通过io.mkdir来创建目录

    local ret, errio = io.mkdir("/data/")
    -- 返回值ret为true即创建目录成功,为false即创建失败,第二个参数返回错误信息
    log.info("fs", "mkdir", ret, errio)

2. 删除目录:通过io.rmdir来删除目录

    local ret, errio = io.rmdir("/data/")
    -- 返回值ret为true即删除目录成功,为false即删除失败,第二个参数返回错误信息
    log.info("fs", "rmdir", ret, errio)
注意非空目录不能删除;需要将目录中的子目录和文件都删除,该目录才可以被成功删除

3. 查看目录信息

通过io.lsdir(dirpath, len, offset)来查看目录信息

len表示输出最大多少个元素,最大50个;offset表示从第几个开始输出,常用于目录文件信息的分页查询;通过len与offset可以控制返回目录文件数目

    -- 查询lfs根目录信息,显示最多10条信息,从第0条开始显示
    local rc, data = io.lsdir("/", 10, 0)
    log.info("fs", "ls " .. pathto, json.encode(data))
返回结果举例: I/user.fs ls / [{"name":"tc304_dir_01","size":0,"type":1},{"name":"tc304_dir_02","size":0,"type":1},{"name":"tc304_file01.txt","size":0,"type":0},{"name":"tc304_file02.txt","size":17,"type":0}]

每个目录或文件信息包括name,size,type三部分键值对,type=0时为文件,type=1时为目录。size为目录或文件的大小(注:目录目前指示大小都默认为0)

注意:Air8101工业引擎当前默认支持的文件系统中,littlefs(lfs)才支持目录功能,ram和luadb不支持

4.3.7 查询文件系统信息

1. 查询文件系统挂载信息:io.lsmount()列出所有挂载点

    local data = io.lsmount() -- data返回table表
    log.info("fs", "lsmount", json.encode(data))
返回结果举例: I/user.fs lsmount [{"fs":"lfs2","path":"\/"},{"fs":"inline","path":"\/lua\/"},{"fs":"ram","path":"\/ram\/"},{"fs":"luadb","path":"\/luadb\/"}]

注:inline, 挂载路径 '/lua/', 只读,内容目前为corelib包括sys.luac,sysplus.luac的二进制文件, 用户可以不用关心这个区域。

2. 查询文件系统信息:fs.fsstat(path)
其中path参数为挂载点路径(如lfs 为"/")

    log.info("fsstat:lfs2 ", fs.fsstat("/"))
    log.info("fsstat:ram ", fs.fsstat("/ram/"))
    log.info("fsstat:luadb", fs.fsstat("/luadb/"))
返回结果举例:
I/user.fsstat:lfs2 true 64 2 4096 lfs
I/user.fsstat:ram true 64 0 1024 ram
I/user.fsstat:luadb true 512 4 512 luadb

注:返回值依次是获取信息成功指示(true or false),总的block数量,已使用的block数量,block大小,文件系统类型

4.3.8 查询文件大小

库接口提供了几个方法了获取文件长度 - io.fileSize(path) - fs.fsize(path) 返回文件长度,若文件不存在则返回0

    local filelen1 = io.fileSize("/test.txt") -- 查询lfs根目录下test.txt的长度
    local filelen2 = fs.fsize("/ram/test.txt") -- 查询ram文件系统根目录下test.txt的长度
此外也可以通过io.lsdir(dirpath, len, offset) 输出的目录信息来查询到目录和文件的大小

4.3.9 文件系统格式化

通过io.mkfs(path)来进行文件系统格式化,清空数据恢复到原始状态
path为挂载点路径,默认挂载的文件系统littlefs(lfs) 格式化路径为"/"

    local ret, errio = io.mkfs("/") -- 格式化lfs
    log.info("fs", "mkfs", ret, errio) -- 成功 ret为true,失败为false,errio返回错误信息

4.4 Demo功能介绍

对于文件系统demo功能测试设计了一系列的应用测试用例来测试验证库函数接口,可以尽可能覆盖所定义函数接口的使用,让使用该模块功能的读者通过测试用例可以更清晰了解具体库函数与注意事项。

Fs 的demo test包括以下两部分:

1)Test用例函数:基本功能测试部分(fs_test_part1.lua), 异常与边界测试部分(fs_test_part2.lua), 文件读写压力测试部分(fs_test_part3.lua); 每部分都定义一系列测试函数用于覆盖所定义文件系统支持的库函数接口,让使用该模块功能的读者通过用例函数可以更清晰了解具体库函数与注意事项;测试函数主体实现中使用函数assert()来指示测试结果失败信息,函数入口参数为文件系统路径,详情参考5.1.2章节和具体demo代码实现。

2)Test用例运行:main.lua 定义了不同文件系统类型需要执行的用例函数集;定义了一个test_run函数来轮询执行该用例函数集,使用pcall调用来完成测试函数的执行,可以捕获执行函数的assert异常错误且不影响下个用例函数调用的执行,详情参考5.1.2章节和具体demo代码实现。

下表展示了Test用例函数设计,与指示不同文件系统类型的执行与否, 目前主要测试默认挂载的lfs,ram,luadb三种文件系统,后续也可以增加挂载其他文件系统类型测试(由于每种文件系统类型底层实现不同,所以需要分别执行测试)。

Fs test description Fs test function(part1-part3) lfs ram luadb
用例0101 文件创建、打开、关闭与删除 tc0101_file_operation [Y] [Y] [ ]
用例0102 目录创建与删除 tc0102_directory_operation [Y] [ ] [ ]
用例0103 文件写入数据功能(标准库接口) tc0103_file_writedata_std [Y] [Y] [ ]
用例0104 文件写入数据功能(扩展库接口) tc0104_file_writedata_ext [Y] [Y] [ ]
用例0105 文件读取数据功能(扩展库接口) tc0105_file_readdata_ext [Y] [Y] [Y]
用例0106 文件读取数据功能(标准库接口) tc0106_file_readdata_std [Y] [Y] [Y]
用例0107 文件写入数据的截断与追加功能 tc0107_file_write_trunc_append [Y] [Y] [ ]
用例0108 文件指针偏移时写入立即读取数据 tc0108_file_pointer_readwrite [Y] [Y] [ ]
用例0109 文件写入读取Bin数据 tc0109_file_write_read_bindata [Y] [Y] [ ]
用例0110 文件重命名并写入读取数据 tc0110_file_rename_readwrite [Y] [Y] [ ]
用例0111 查询文件大小 tc0111_file_size [Y] [Y] [Y]
用例0112 查询目录大小 tc0112_directory_size [Y] [ ] [ ]
用例0113 查询文件系统信息 tc0113_filesystem_info [Y] [Y] [Y]
用例0114 文件系统格式化 tc0114_filesystem_format [Y] [ ] [ ]
用例0201 异常文件打开删除 tc0201_file_abnormal_operate [Y] [Y] [ ]
用例0202 异常目录打开删除 tc0202_directory_abnormal_operate [Y] [ ] [ ]
用例0203 不同文件模式读写操作异常 tc0203_file_abnormal_readwrite [Y] [ ] [Y]
用例0204 文件名最大长度 tc0204_filename_max [Y] [Y] [ ]
用例0205 根目录单文件最大写入和删除 tc0205_file_writemax [Y] [ ] [ ]
用例0206 文件系统满容量写入读取测试 tc0206_filesystem_full_readwrite [Y] [ ] [ ]
用例0207 不同文件同时写入读取数据 tc0207_files_multiopen_readwrite [Y] [Y] [ ]
用例0301 文件分块频繁写入删除 tc0301_file_write_chunkdata_remove [Y] [Y] [ ]
用例0302 文件分块频繁读取 tc0302_file_readbin_frequency [Y] [Y] [Y]
用例0303 文件按行频繁读取 tc0303_file_readline_frequency [Y] [Y] [Y]
用例0304 文件和目录频繁创建与删除 tc0304_file_openremove_frequency [Y] [Y] [ ]

注:用例0101-0114 对应基本功能测试部分(fs_test_part1.lua),用例0201-0207对应异常与边界测试部分(fs_test_part2.lua),用例0301-0304对应文件读写压力测试部分(fs_test_part3.lua);[Y]指示该文件系统类型需执行测试函数;[ ]指示该文件系统类型无需执行测试函数

代码实现通过assert来指示具体demo测试哪个函数失败,失败定位在函数哪个位置;也便于自动化测试的集成,用例执行后输出执行结果Test pass数量和fail数量(如5.1.1截图展示),后续执行可以先关注测试结果的报告在结合log信息定位执行失败测试项。

五、文件系统(io)整体演示

5.1 成果演示与深度解析

5.1.1 成果运行精彩呈现

按照下图所示下载fs demo测试脚本和资源文件: Alt text

注:调试遇到问题时可以勾选图中"脚本调试信息"选项(可以增加assert指示具体函数位置行号等调试信息)

fs demo 测试运行结果:

lfs路径"/" 测试运行结果展示:默认运行part1和part2的测试
demo中单独打开定义 PATH_LFS = "/"(详见main.lua中定义) Alt text

ram路径”/ram/:测试运行结果展示:默认运行part1和part2的测试
demo中单独打开定义 PATH_RAM = "/ram/"(详见main.lua中定义) Alt text

luadb路径”/luadb/:测试运行结果展示:默认运行part1和part2的测试
demo中单独打开定义 PATH_LUADB = "/luadb/"(详见main.lua中定义) Alt text

5.1.2 完整Demo实例深度剖析

关键代码实现与注释说明

  • main.lua:demo测试主函数实现,包括不同文件系统的测试函数集定义及测试运行任务函数定义
-- LuaTools需要PROJECT和VERSION这两个信息
PROJECT = "demo_fs_test"
VERSION = "1.0.0"

-- 引入必要的库文件(lua编写), 内部库不需要require
sys = require("sys")

-- 通过屏蔽变量来控制测试对应FS路径,默认打开LFS
local PATH_LFS = "/"
--local PATH_RAM = "/ram/"
--local PATH_LUADB = "/luadb/"

-- 通过屏蔽module来开关不同测试,默认不打开part3压力测试
require "fs_test_part1"
require "fs_test_part2"
--require "fs_test_part3"

-- test_run函数接受一个table参数,table中包含测试用例函数
-- 调用了sys.wait()需要在任务中调用
function test_run(test_cases, path)
    local successCount = 0  -- 成功的测试用例计数
    local failureCount = 0  -- 失败的测试用例计数

    -- 遍历table中的每个测试用例函数
    for name, testCase in pairs(test_cases) do
        -- 检查testCase是否为函数
        if type(testCase) == "function" then
            -- 使用pcall来捕获测试用例执行中的错误, 若有错误也继续往下执行
            local success, err = pcall(testCase, path)  
            if success then
                -- 测试成功,增加成功计数
                successCount = successCount + 1  
                log.info("fs", "Test case passed: " .. name)
            else
                -- 测试失败,增加失败计数
                failureCount = failureCount + 1  
                log.info("fs", "Test case failed: " .. name .. " - Error: " .. err)
            end
        else
            log.info("fs", "Skipping non-function entry: " .. name)
        end
        -- 稍等一会在继续测试
        sys.wait(1000)
    end
    -- 打印测试结果
    log.info("fs", "Test run complete - Success: " .. successCount .. ", Failures: " .. failureCount)
end

-- 测试用例的table,以函数名为键,函数为值,实际运行无序可以增加测试的随机性
-- lfs的测试函数集
local fs_test_suite_lfs = {
    --[[
    基本功能测试,需定义require "fs_test_part1"
    ]]-- 
    ["tc0101_file_operation"] = tc0101_file_operation,
    ["tc0102_directory_operation"] = tc0102_directory_operation,
    ["tc0103_file_writedata_std"] = tc0103_file_writedata_std, --
    ["tc0104_file_writedata_ext"] = tc0104_file_writedata_ext, --
    ["tc0105_file_readdata_ext"] = tc0105_file_readdata_ext,--
    ["tc0106_file_readdata_std"] = tc0106_file_readdata_std,--
    ["tc0107_file_write_trunc_append"] = tc0107_file_write_trunc_append,
    ["tc0108_file_pointer_readwrite"] = tc0108_file_pointer_readwrite,
    ["tc0109_file_write_read_bindata"] = tc0109_file_write_read_bindata,
    ["tc0110_file_rename_readwrite"] = tc0110_file_rename_readwrite,
    ["tc0111_file_size"] = tc0111_file_size,
    ["tc0112_directory_size"] = tc0112_directory_size, --
    ["tc0113_filesystem_info"] = tc0113_filesystem_info,
    --[[
    异常与边界测试,需定义require "fs_test_part2"
    ]]--
    ["tc0201_file_abnormal_operate"] = tc0201_file_abnormal_operate,
    ["tc0202_directory_abnormal_operate"] = tc0202_directory_abnormal_operate,
    ["tc0203_file_abnormal_readwrite"] = tc0203_file_abnormal_readwrite,
    ["tc0204_filename_max"] = tc0204_filename_max,
    ["tc0205_file_writemax"] = tc0205_file_writemax,
    ["tc0206_filesystem_full_readwrite"] = tc0206_filesystem_full_readwrite,
    ["tc0207_files_multiopen_readwrite"] = tc0207_files_multiopen_readwrite,
    --[[
    文件读写压力测试,默认不打开,需定义require "fs_test_part3",若需要测试建议单独打开测试
    ]]--    
    ["tc0301_file_write_chunkdata_remove"] = tc0301_file_write_chunkdata_remove,
    ["tc0302_file_readbin_frequency"] = tc0302_file_readbin_frequency,
    ["tc0303_file_readline_frequency"] = tc0303_file_readline_frequency,
    ["tc0304_file_openremove_frequency"] = tc0304_file_openremove_frequency,
}

-- ram的测试函数集
local fs_test_suite_ram = {
    --[[
    基本功能测试,需定义require "fs_test_part1"
    ]]--     
    ["tc0101_file_operation"] = tc0101_file_operation,
    ["tc0103_file_writedata_std"] = tc0103_file_writedata_std, --
    ["tc0104_file_writedata_ext"] = tc0104_file_writedata_ext, --
    ["tc0105_file_readdata_ext"] = tc0105_file_readdata_ext,--
    ["tc0106_file_readdata_std"] = tc0106_file_readdata_std,--
    ["tc0107_file_write_trunc_append"] = tc0107_file_write_trunc_append,
    ["tc0108_file_pointer_readwrite"] = tc0108_file_pointer_readwrite,
    ["tc0109_file_write_read_bindata"] = tc0109_file_write_read_bindata,
    ["tc0110_file_rename_readwrite"] = tc0110_file_rename_readwrite,
    ["tc0111_file_size"] = tc0111_file_size,
    ["tc0113_filesystem_info"] = tc0113_filesystem_info,
    --[[
    异常与边界测试,需定义require "fs_test_part2"
    ]]--
    ["tc0201_file_abnormal_operate"] = tc0201_file_abnormal_operate,
    ["tc0203_file_abnormal_readwrite"] = tc0203_file_abnormal_readwrite,--
    ["tc0204_filename_max"] = tc0204_filename_max,
    ["tc0207_files_multiopen_readwrite"] = tc0207_files_multiopen_readwrite,
    --[[
    文件读写压力测试, 默认不打开,需定义require "fs_test_part3",若需要测试建议单独打开测试
    ]]--
    ["tc0301_file_write_chunkdata_remove"] = tc0301_file_write_chunkdata_remove,
    ["tc0302_file_readbin_frequency"] = tc0302_file_readbin_frequency,
    ["tc0303_file_readline_frequency"] = tc0303_file_readline_frequency,
    ["tc0304_file_openremove_frequency"] = tc0304_file_openremove_frequency,
}

-- luadb的测试函数集
local fs_test_suite_luadb = {
    --[[
    基本功能测试,需定义require "fs_test_part1"
    ]]--    
    ["tc0105_file_readdata_ext"] = tc0105_file_readdata_ext,--
    ["tc0106_file_readdata_std"] = tc0106_file_readdata_std,--
    ["tc0111_file_size"] = tc0111_file_size,
    ["tc0113_filesystem_info"] = tc0113_filesystem_info,
    --[[
    异常与边界测试,需定义require "fs_test_part2"
    ]]--
    ["tc0203_file_abnormal_readwrite"] = tc0203_file_abnormal_readwrite,
    --[[
    文件读写压力测试, 默认不打开,需定义require "fs_test_part3",若需要测试建议单独打开测试
    ]]--
    ["tc0302_file_readbin_frequency"] = tc0302_file_readbin_frequency,
    ["tc0303_file_readline_frequency"] = tc0303_file_readline_frequency,
}

-- 定义任务来开始demo测试流程
sys.taskInit(function()
    -- 为了显示日志,这里特意延迟一秒
    -- 正常使用不需要delay
    sys.wait(1000)

    -- 运行测试套件
    if PATH_LFS then
        -- 开机首先获取文件系统根目录信息确认可掉电保存正常
        local rc, data = io.lsdir(PATH_LFS, 10, 0)
        log.info("fs", "ls " .. PATH_LFS, json.encode(data))
        log.info("fsstat: " .. PATH_LFS, fs.fsstat(PATH_LFS))
        -- 先格式化文件系统
        io.mkfs(PATH_LFS)
        --tc0114_filesystem_format(PATH_LFS)
        sys.wait(200)
        log.info("fs", "Test path on " .. "\"" .. PATH_LFS .. "\"")
        test_run(fs_test_suite_lfs, PATH_LFS)

        -- LFS根目录下创建一个新目录,原则上新目录需要完成根目录一致的测试确保新建目录中操作功能正常,
        -- 默认不打开
        --[[
        log.info("fs", "Test path on " .. "\"" .. PATH_LFS .. "test/" .. "\"")
        generate_directory(PATH_LFS, "test")
        test_run(fs_test_suite_lfs, PATH_LFS .. "test/")
        -- 递归删除,需保证目录内容为空才能删除目录
        recursive_delete_directory(PATH_LFS, "test")
        ]]--
    end

    if PATH_RAM then
        log.info("fsstat: " .. PATH_RAM, fs.fsstat(PATH_RAM))
        log.info("fs", "Test path on " .. PATH_RAM)
        test_run(fs_test_suite_ram, PATH_RAM)
    end

    if PATH_LUADB then
        log.info("fsstat: " .. PATH_LUADB, fs.fsstat(PATH_LUADB))
        log.info("fs", "Test path on " .. PATH_LUADB)
        test_run(fs_test_suite_luadb, PATH_LUADB)
    end
end)

-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后后面不要加任何语句!!!!!
  • fs_test_part1.lua: 基本功能测试函数实现
require "fs_test_utils" --导入封装的公共测试函数

--[[
用例0101 测试文件创建、打开、关闭与删除
]]--
function tc0101_file_operation(pathto)
    local filename = "tc101_data.txt"
    --打开文件,若不存在则创建;
    local file = io.open(pathto .. filename, "w+")
    assert(file ~= nil, "Should not creat file")
    if file then
        -- 检查文件打开状态
        local rc = io.type(file)
        --log.info("fs", "io.type: ", "rc")
        assert(rc == "file", "Should be the opened file")
        file:close()

        -- 关闭文件,可以获取文件关闭状态
        rc = file:__tostring()
        --log.info("fs", "io.type: ", "rc")
        assert(string.find(rc, "closed") ~= nil, "Should be the closed file")
        rc = io.type(file)
        assert(string.find(rc, "closed") ~= nil, "Should be the closed file")
    end

    --重复打开同一文件,会返回不同的文件句柄
    local file1 = io.open(pathto .. filename, "r+")
    assert(file1 ~= nil, "Failed to open file in in \"r+\" mode")
    local file2 = io.open(pathto .. filename, "a+")
    assert(file2 ~= nil, "Should not reopen the same file in \"a+\" mode")
    file1:close()
    file2:close()

    -- 删除文件
    local ret, err = os.remove(pathto .. filename)
    assert(ret ~= nil and ret == true, "Remove file failed")
    -- 查询文件不存在则文件删除成功
    ret = io.exists(pathto .. filename)
    assert(ret == false, "Should not exist file")
end

--[[
用例0102 测试文件系统创建与删除目录
]]--
function tc0102_directory_operation(pathto)
    local dirname = "tc102_dir"
    -- 判断目录是否存在
    local rc = check_dir_exist(pathto, dirname)
    local success, err = 0, 0
    if rc == 0 then 
        -- 创建一个新目录
        success, err = io.mkdir(pathto .. dirname .. "/")
        assert(success, "Failed to create directory: " .. tostring(err))
        rc = check_dir_exist(pathto, dirname)
        assert(rc == 1, "Not exist directory")
    end

    -- 删除创建的目录
    success, err = io.rmdir(pathto .. dirname .. "/")
    assert(success, "Failed to remove directory: " .. tostring(err))

    -- 验证目录是否被成功删除
    rc = check_dir_exist(pathto, dirname)
    assert(rc == 0, "Directory should be removed")
end

--[[
用例0103 测试打开文件写入数据功能(标准库接口)
]]--
function tc0103_file_writedata_std(pathto)
    local file = "tc103_file_01.txt"
    local testdata = "Test contents:\n123456\n"
    -- 写模式创建/打开文件,标准库io接口写入数据
    local fdw = io.open(pathto .. file, "w")
    if fdw then
        -- 标准库io接口写入数据
        fdw:write(testdata)
        fdw:close()
    end
    -- 只读模式打开文件
    local fdr = io.open(pathto .. file, "r")
    assert(fdr ~= nil, "Cannot open file")
    if fdr then
        -- 标准库io接口读取全部数据
        local readdata = fdr:read("*a")
        fdr:close()
        -- 比较读取数据和测试数据是否一致
        assert(readdata == testdata, "Read data should be as same as Test data")
    end
    -- 清理工作
    if pathto == "/ram/" then 
        -- 对于/ram/ 测试完删除文件,因为ram文件系统最大允许文件数为8个超过就无法新建文件
        os.remove(pathto .. file)
    end
end

--[[
用例0104 测试打开文件写入数据功能(扩展库接口)
]]--
function tc0104_file_writedata_ext(pathto)
    local file = "tc104_file.txt"
    local testdata = "Test contents:\n123456\n"
    -- 通过luatos扩展io库接口来直接向文件写入数据
    local rc = io.writeFile(pathto .. file, testdata)
    assert(rc, "Cannot Write file")
    -- 只读模式打开文件
    local fdr = io.open(pathto .. file, "r")
    assert(fdr ~= nil, "Cannot open file")
    if fdr then
        -- 标准库io接口读取全部数据
        local readdata = fdr:read("*a")
        fdr:close()
        -- 比较读取数据和测试数据是否一致
        assert(readdata == testdata, "Read data should be as same as Test data")
    end
    -- 清理工作
    if pathto == "/ram/" then 
        -- 对于/ram/ 测试完删除文件,因为ram文件系统最大允许文件数为8个超过就无法新建文件
        os.remove(pathto .. file)
    end
end

--[[
用例0105 测试打开文件读取数据功能(扩展库接口)
]]--
function tc0105_file_readdata_ext(pathto)
    local filename = "tc105_file.txt"
    local testdata = "Test contents:\r\n12345\r\n"
    local fdw = io.open(pathto .. filename, "w+")
    if fdw then
        -- 标准库io接口写入数据
        fdw:write(testdata)
        fdw:close()
    end
    -- 若"/luadb/"则直接指定测试文件
    if pathto == "/luadb/" then
        filename = "test_fewline.txt" -- same as testdata
    end
    -- 通过luatos扩展io库接口io.readFile读取数据,从0位置开始读指定长度大小
    local readdata = io.readFile(pathto .. filename, "r", 0, #testdata)
    log.info("fs", "read data ", readdata)
    assert(readdata == testdata, "Read data is not as same as Test data")

    -- 清理工作
    if pathto == "/ram/" then 
        -- 对于/ram/ 测试完删除文件,因为ram文件系统最大允许文件数为8个超过就无法新建文件
        os.remove(pathto .. filename)
    end
end

--[[
用例0106 测试打开文件读取数据功能(标准库接口)
]]--
function tc0106_file_readdata_std(pathto)
    local filename = "tc106_file.txt"
    local testdata = "Test contents:\r\n12345\r\n"
    local fdw = io.open(pathto .. filename, "w+")
    if fdw then
        -- 标准库io接口写入数据
        fdw:write(testdata)
        fdw:close()
    end

    -- 若"/luadb/"则直接指定测试文件
    if pathto == "/luadb/" then
        filename = "test_fewline.txt" -- same as testdata
    end
    -- 只读模式打开文件,通过标准io库接口file:read方式读取数据
    local fdr = io.open(pathto .. filename, "r")
    if fdr then
        local readdata = fdr:read(1)
        log.info("fs", "read data", readdata)
        while true do
            -- 循环按指定字节数读取,读取不到就退出循环
            local data = fdr:read(10)
            if not data or #data == 0 then
                break
            end
            log.info("fs", "read data", data)
            readdata = readdata .. data
        end
        fdr:close()
        assert(readdata == testdata, "Read data != Test data")
    end
    -- 只读模式打开文件,通过标准io库接口file:read方式读取数据
    fdr = io.open(pathto .. filename, "r")
    --assert(fdr ~= nil, "Cannot open file")
    if fdr then
        local content = {}
        while true do
            -- 循环按行方式读取,读取不到就退出循环
            local line = fdr:read() -- 不带参数即默认与fdr:read("l")方式一致
            if not line or #line == 0 then
                break
            end
            log.info("fs", "read line", line)
            table.insert(content, line)
        end
        fdr:close()
        assert(#content == 2, "Should be two lines")
        assert(content[1] == "Test contents:\r", "Should be first line")
        assert(content[2] == "12345\r", "Should be second line")
    end
    -- 只读模式打开文件,通过标准io库接口file:read方式读取数据
    fdr = io.open(pathto .. filename, "r")
    --assert(fdr ~= nil, "Cannot open file")
    if fdr then
        local content = {}
        -- 标准库io接口按行方式获取数据
        for line in fdr:lines() do
            log.info("fs", "read line", line)
            table.insert(content, line)
        end
        fdr:close()
        assert(#content == 2, "Should be two lines")
        assert(content[1] == "Test contents:\r", "Not the first line")
        assert(content[2] == "12345\r", "Not the second line")
    end
    -- 通过标准io库接口io.lines按行方式读取
    local lines = io.lines(pathto .. filename) --相当于io.lines(pathto .. filename, "l")
    local content = {}
    for line in lines do
        log.info("fs", "read line ", line)
        table.insert(content, line)
    end
    assert(#content == 2, "Should be two lines")
    assert(content[1] == "Test contents:\r", "Should be first line")
    assert(content[2] == "12345\r", "Should be second line")
end

--[[
用例0107 测试打开文件写入数据的截断与追加功能
]]--
function tc0107_file_write_trunc_append(pathto)
    local filename = "tc107_file.txt"
    local file_path = pathto .. filename
    -- 测试前先删掉之前的测试文件(若存在)
    if io.exists(file_path) then
        os.remove(file_path)
    end
    -- 创建新文件并写入初始内容
    local file = io.open(file_path, "w+")
    --assert(fdr ~= nil, "Cannot open file")
    if file then
        file:write("Initial content\n")
        file:close()
        -- 使用写模式打开文件,截断文件
        file = io.open(file_path, "w+")
        file:write("Truncated content\n")
        file:close()
        -- 从文件末尾追加内容
        file = io.open(file_path, "a")
        file:write("Appended content\n")
        file:close()
        -- 读取文件内容并验证
        file = io.open(file_path, "r")
        local content = file:read("*a")
        file:close()
        log.info("fs", "data: ", content)
        assert(content == "Truncated content\nAppended content\n", "Content should match after truncation and append")
    end
    -- 清理工作
    if pathto == "/ram/" then 
        -- 对于/ram/ 测试完删除文件,因为ram文件系统最大允许文件数为8个超过就无法新建文件
        os.remove(pathto .. filename)
    end
end

--[[
用例0108 测试文件指针偏移时写入数据立即读取数据功能
]]--
function tc0108_file_pointer_readwrite(pathto)
    local filename = "tc108_file.txt"
    local file = io.open(pathto .. filename, "w+")
    file:write("Hello, World!")
    -- 设置文件指针到文件开始偏移7字节的位置
    file:seek("set", 7)
    -- 读取完2字节,文件指针也向后偏移2
    local content = file:read(2)
    log.info("fs", "content: ", content)
    assert(content == "Wo", "Read data wrong")
    -- 获取当前文件指针位置
    local cur_pos = file:seek() -- 等同于file:seek("cur")
    assert(cur_pos == 9, "File pointer should be at the offset 9 not " .. tostring(cur_pos))
    -- 设定为"end",offset为-2(回退2),获取文件指针
    local end_pos = file:seek("end", -2) -- 倒数第2的位置
    assert(end_pos == 11, "File pointer should be at the offset 11 not " .. tostring(end_pos))
    -- 设置文件指针到开始的位置
    file:seek("set", 0)
    content = file:read("*a")
    -- 读取完,文件指针已到文件尾,相当于获取文件大小
    local pos = file:seek()
    file:close()
    log.info("fs", "content: ", content)
    assert(pos == 13, "File pointer should be at the end after read")
end

--[[
用例0109 测试文件写入读取Bin数据
]]--
function tc0109_file_write_read_bindata(pathto)
    local filename = "tc109_data.bin"
    -- 若路径为luadb,仅测试读取该路径下test_fewdata.bin数据
    if pathto == "/luadb/" then
        filename = "/luadb/test_fewdata.bin"
        local dst_bindata = "\x00\xAA\xBB\xCC\xDD\xEE\xFF\x13\x46\x80\x12\x34\x56\x78\x90\x00"
        -- 扩展接口读取二进制数据
        local readdata2 = io.readFile(pathto .. filename, "r", 0, #dst_bindata)
        assert(readdata2 == dst_bindata, "Read data != Test data")
    else
        -- 准备二进制数据
        local binaryData1 = string.char(0, 1, 2, 255, 254)
        local binaryData2 = "\x12\x34\x56\78\90\xAB\xCD\xEF"
        --log.info("fs", "data1:" .. string.toHex(binaryData1))
        --log.info("fs", "data1+data2:" .. string.toHex(binaryData1 .. binaryData2))
        -- 扩展接口写入二进制数据
        local rc = io.writeFile(pathto .. filename, binaryData1)
        assert(rc, "Write Bin File Error")
        -- 标准库接口:读取二进制数据
        local file = io.open(pathto .. filename, "rb")
        assert(file, "Failed to open file for reading")
        local readData = file:read("*a")
        file:close()
        assert(readData == binaryData1, "Read data != written data")
        -- 标准库接口:追加写入二进制数据
        local file = io.open(pathto .. filename, "ab")
        assert(file ~= nil, "Failed to open file for writing")
        file:write(binaryData2)
        file:close()
        -- 扩展接口读取二进制数据
        local readdata2 = io.readFile(pathto .. filename, "r", 0, #binaryData1 + #binaryData2)
        assert(readdata2 == binaryData1 .. binaryData2, "Read data != Test data")
        -- 清理工作
        if pathto == "/ram/" then 
            -- 对于/ram/ 测试完删除文件,因为ram文件系统最大允许文件数为8个超过就无法新建文件
            os.remove(pathto .. filename)
        end
    end
end

--[[
用例0110 测试文件重命名并写入读取数据
]]--
function tc0110_file_rename_readwrite(pathto)
    local filename = "tc110_file.txt"
    local filename_chg = "tc110_file_new.txt"
    local testdata = "Test contents:\n123456\n"
    local chg_data = "Hello\n123"

    local file = io.open(pathto .. filename, "w+")
    assert(file ~= nil, "Failed to open file for writing")
    file:write(testdata)
    file:close()
    -- 文件重命名
    os.rename(pathto .. filename, pathto .. filename_chg)
    assert(io.exists(pathto .. filename) == false, "Should not exit file")
    -- 扩展接口读取数据,文件内容不变
    local readdata = io.readFile(pathto .. filename_chg, "r", 0, #testdata)
    assert(readdata == testdata, "Read data should be as same as Test data")
    -- 打开重命名的文件写入新数据(截断文件)
    file = io.open(pathto .. filename_chg, "w")
    if file then
        file:write(chg_data)
        file:close()
    end

    -- 扩展接口读取数据
    log.info("fs", "chg data:" .. chg_data)
    readdata = io.readFile(pathto .. filename_chg, "r", 0, #chg_data)
    log.info("fs", "data:" .. readdata)
    assert(readdata == chg_data, "Read data should be as same as Test data")
end

--[[
用例0111 测试查询文件大小
]]--
function tc0111_file_size(pathto)
    local filename = "tc111_file.txt"
    local testdata = "Test contents:\r\n12345\r\n"
    local fdw = io.open(pathto .. filename, "w")
    if fdw then
        fdw:write(testdata)
        fdw:close()
    end
    -- 若"/luadb/"则直接指定测试文件
    if pathto == "/luadb/" then
        filename = "test_fewline.txt" -- same as testdata
    end
    -- 验证文件长度大小, 有三种方法可以获取的文件长度
    local filelen1 = io.fileSize(pathto .. filename)
    local filelen2 = fs.fsize(pathto .. filename)
    local filelen3 = get_size_from_lsdir(pathto, filename, io.FILE)
    log.info("fs", "filelen1: ", filelen1)
    log.info("fs", "filelen2: ", filelen2)
    log.info("fs", "filelen3: ", filelen3)
    assert((filelen1 == #testdata 
            and filelen2 == #testdata 
            and filelen3 == #testdata), 
            "Read lengths are not equal " .. #testdata)
    -- 若"/luadb/"则测试到这个地方结束        
    if pathto == "/luadb/" then return end

    -- 继续追加写入数据,检查文件大小变化
    fdw = io.open(pathto .. filename, "a+")
    if fdw then
        fdw:write(testdata)
        fdw:close()
    end
    filelen1 = io.fileSize(pathto .. filename)
    filelen2 = fs.fsize(pathto .. filename)
    filelen3 = get_size_from_lsdir(pathto, filename, io.FILE)
    log.info("fs", "filelen1: ", filelen1)
    log.info("fs", "filelen2: ", filelen2)
    log.info("fs", "filelen3: ", filelen3)
    assert((filelen1 == #testdata * 2
            and filelen2 == #testdata * 2
            and filelen3 == #testdata * 2), 
            "Read lengths are not equal")  
end

--[[
用例0112 测试查询目录大小
]]--
function tc0112_directory_size(pathto)
    -- 验证目录大小
    local dirname01 = "tc112_dir_01"
    local dirname02 = "tc112_dir_02"
    local filename = "tc112_file.txt"
    local success, err = 0, 0
    --log.info("fsstat: " .. pathto, fs.fsstat(pathto))
    -- 创建一个新目录
    success, err = io.mkdir(pathto .. dirname01 .. "/")
    assert(success, "Failed to create dir: " .. tostring(err))
    success, err = io.mkdir(pathto .. dirname02 .. "/")
    assert(success, "Failed to create dir: " .. tostring(err))
    -- 在其中一个新目录创建文件
    local rc = io.writeFile(pathto .. dirname01 .. "/" .. filename, "Hello1234\n")
    assert(rc, "Write File Error")
    local rc, data = io.lsdir(pathto .. dirname01 .. "/", 10, 0)
    if rc then
        log.info("fs", "ls " .. pathto .. dirname01 .. "/", json.encode(data))
    end
    log.info("fsstat: " .. pathto, fs.fsstat(pathto))
    -- 获取目录大小(目录占用空间而不是整个目录的文件大小)
    local size1 = get_size_from_lsdir(pathto, dirname01, io.DIR)
    local size2 = get_size_from_lsdir(pathto, dirname02, io.DIR)
    -- 清理工作 先删除文件,非空目录才能删除
    os.remove(pathto .. dirname01 .. "/" .. filename)
    -- 清理工作 删除创建的目录
    success, err = io.rmdir(pathto .. dirname01 .. "/")
    assert(success, "Failed to remove dir: " .. tostring(err))
    success, err = io.rmdir(pathto .. dirname02 .. "/")
    assert(success, "Failed to remove dir: " .. tostring(err))
    -- 判断结果 当前返回的DIR size都为0?
    log.info("fs", pathto .. dirname01 .. "/" .. "  dir size: " .. size1)
    log.info("fs", pathto .. dirname02 .. "/" .. "  dir size: " .. size2)
    assert(size1 == 0, "Reture dir size is " .. tostring(size1)) 
    assert(size2 == 0, "Reture dir size is " .. tostring(size2))    
end

--[[
用例0113 测试查询文件系统信息
]]--
function tc0113_filesystem_info(pathto)
    -- 获取所有文件系统挂载信息
    local data = io.lsmount()
    log.info("fs", "lsmount", json.encode(data))
    -- 获取指定文件系统信息
    log.info("fsstat: " .. pathto, fs.fsstat(pathto))

    if pathto ~= "/luadb/" then
        -- 在目录下新建文件
        local filename = "tc113_file.txt"
        generate_file(pathto, filename)
        log.info("fsstat: " .. pathto, fs.fsstat(pathto))
        if pathto == "/" then -- 测试lfs时支持目录
            local dirname = "tc113_dir"
            generate_directory(pathto, dirname)
            -- 在新建目录里创建文件
            generate_file(pathto .. dirname .. "/", filename)
            -- 获取新建目录的目录信息
            local rc, data1 = io.lsdir(pathto .. dirname .. "/", 10, 0)
            log.info("fs", "ls " .. pathto .. dirname .. "/", json.encode(data1))
        end
    end
    -- 获取文件系统目录信息
    local rc, data1 = io.lsdir(pathto, 10, 0)
    log.info("fs", "ls " .. pathto, json.encode(data1))
end

--[[
用例0114 文件系统格式化
]]--
function tc0114_filesystem_format(pathto)
    --log.info("fsstat: " .. pathto, fs.fsstat(pathto))
    -- 创建一个新的空目录
    local success, err = io.mkdir(pathto .. "tc114_dir")
    assert(success, "Failed to create directory: " .. pathto .. "tc114_dir" .. tostring(err))
    -- 创建一个新的空文件
    local fd = io.open(pathto .. "tc114_file.txt", "w+")
    if fd then fd:close() end
    local rc, data = io.lsdir(pathto, 10, 0)
    log.info("fs", "ls " .. pathto, json.encode(data))
    --log.info("fs", "table len=", #data)
    -- 指定挂载路径,文件系统格式化
    local rc, err = io.mkfs(pathto)
    assert(rc, "FS format error")
    log.info("fsstat: " .. pathto, fs.fsstat(pathto))
    -- 格式化后 挂载目录下无目录和文件
    rc, data = io.lsdir(pathto, 10, 0)
    --log.info("fs", "ls " .. pathto, json.encode(data))
    --log.info("fs", "table len=", #data)
    assert(#data == 0, "root dir not empty")
end
  • fs_test_utils.lua:用于测试的公共函数

--[[
    fs_test_utils.lua, 封装用于测试的公共函数
]]--

-- 通过lsdir信息检查目录是否存在
function check_dir_exist(pathto, dir)
    local rc, data = io.lsdir(pathto, 10, 0)
    if rc then
        --log.info("fs", "ls " .. pathto, json.encode(data))
        for _, v in ipairs(data) do
            if v["name"] == dir and v["type"] == io.DIR then
                --print("find!!!")
                return 1
            end
        end
    end
    return 0
end

-- 通过lsdir信息获取目录或文件的大小
function get_size_from_lsdir(pathto, name, type)
    local rc, data = io.lsdir(pathto, 10, 0)
    if rc then
        log.info("fs", "ls " .. pathto, json.encode(data))
        for _, v in ipairs(data) do
            if v["name"] == name and v["type"] == type then
                --print("find!!!")
                return v["size"]
            end
        end
    end
    return 0
end

-- 封装一个用于检查并生成目录的测试函数
function generate_directory(pathto, dirname)
    local dir_path = pathto .. dirname .. "/"
    -- 判断目录是否存在
    local rc = check_dir_exist(pathto, dirname)
    if rc == 1 then 
        io.rmdir(dir_path)
    end
    -- 创建新目录
    local success, err = io.mkdir(dir_path)
    assert(success, "Failed to create directory: " .. dir_path .. tostring(err))
    rc = check_dir_exist(pathto, dirname)
    assert(rc == 1, "Not exist directory")
end

-- 封装一个用于检查并生成文件的测试函数
function generate_file(pathto, filename)
    -- 判断文件是否存在
    local rc = io.exists(pathto, filename)
    if rc  then 
        os.remove(pathto .. filename)
    end
    -- 创建新文件并写入固定数量测试数据
    local fd = io.open(pathto .. filename, "w+")
    local ret, err
    if fd then 
        ret, err = fd:write("123456789012345678901234567890123456789012345678901234567890\n")
        fd:close()
        return ret
    else
        return fd
    end
end

-- 递归方式删除目录(先删除文件保证空目录在删除目录)
function recursive_delete_directory(pathto, dirname)
    local dir_path = pathto .. dirname .. "/"
    -- 删除目录内所有文件
    local rc, data = io.lsdir(dir_path, 20, 0)
    if rc then
        log.info("fs", "ls", json.encode(data))
        for _, v in ipairs(data) do
            if v["type"] == 0 then
                --log.info("fs", "find0!!!")
                rc = os.remove(dir_path .. v["name"])
                assert(rc, "Not remove")
            elseif v["type"] == 1 then
                --log.info("fs", "find1!!!")
                recursive_delete_directory(dir_path, v["name"])
            end
        end
    end
    -- 删除目录
    local success, err = io.rmdir(dir_path)
    log.info("fs", "rmdir ", dir_path)
    assert(success, "Failed to remove directory: " .. tostring(err))
end

-- 文件写入数据块指定次数
function file_chunkdata_write(fd, chunkdata, write_times)
    if fd == nil then
        return nil
    end
    -- 分块写入数据
    local ret, err
    for i = 1, write_times do
        ret, err = fd:write(chunkdata)
        sys.wait(100)
        -- 写成功ret值为file handle写失败为nil
        --log.info("fs", "write ret: ", tostring(ret) .. " block: " .. tostring(i) )
        --log.info("fsstat: " .. pathto, fs.fsstat(pathto))
        if ret == nil then 
            return ret
        end
    end
    return ret
end
此外,fs_test_part2.lua和fs_test_part3.lua为基本功能之外的异常边界测试和压力测试,请参考具体demo code这里不在详述。

六、总结

通过本篇文章,我们深入了解了文件系统(io)库中的常用功能,包括文件的创建、读取、写入、删除和重命名,目录创建删除,查询文件大小与文件系统信息等操作。通过demo测试不同的目录文件操作,掌握目录文件操作的常见接口及需要关注的事项,可以在处理文件时更加灵活高效。此外,在处理大文件或二进制数据时,合理选择接口、模式尤为重要。

七、常见问题

  • 文件无法打开:如果文件不存在或路径错误,io.open()会返回nil和一个错误信息。文件打开失败可能是因为文件不存在、权限不足、路径错误等问题;建议文件操作时总是检查io.open()的返回值,以确保文件成功打开。
  • 使用io.lines()访问数据未关闭文件:在一些特殊的场景下,如果文件比较大,io.lines()可能不会按预期关闭文件,导致文件句柄泄漏;可以选择使用file:lines()或file:read("l")并显式关闭文件句柄。
  • 处理大文件时内存占用过高:在处理大文件时,使用file:read("*a")会一次性将整个文件内容加载到内存中,这可能导致内存占用过高,特别是在文件非常大的时候;可以使用file:read(size)来指定块大小来分块读取文件,逐块处理内容。

八、扩展

  • 日志输出重定向到文件:可以实现将程序的输出重定向到文件。
  • 扩展网络文件系统IO库操作:结合socket网络功能来实现网络访问及操作文件系统的功能。
  • 任务间通信与共享数据:任务之间可以通过临时文件实现简单的任务间通信。
  • 文件访问监控:在文件创建打开、删除或读写时增加文件监控通知功能,可以让应用知道某些文件的状态变化。