diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index b9b3150..9ab0591 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ *.o *.d *.so + +test/bin + zvfs/func_test zvfs_meta.txt zvfs/zvfs_meta.txt \ No newline at end of file diff --git a/.gitmodules b/.gitmodules old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index ed1a695..6a94de9 --- a/Makefile +++ b/Makefile @@ -1,9 +1,16 @@ -.PHONY: all clean zvfs +.PHONY: all clean zvfs test run-test all: zvfs zvfs: $(MAKE) -C zvfs +test: + $(MAKE) -C test + +run-test: + $(MAKE) -C test run-test + clean: - $(MAKE) -C zvfs clean \ No newline at end of file + $(MAKE) -C zvfs clean + $(MAKE) -C test clean diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 48563eb..b257a15 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ ```shell git submodule update --init --recursive -cd /home/lian/share/10.1-spdk/spdk +cd spdk +./scripts/pkgdep.sh ./configure --with-shared make -j diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..749ce65 --- /dev/null +++ b/plan.md @@ -0,0 +1,346 @@ +# ZVFS -> RocksDB LD_PRELOAD 可重入改造计划(给 Codex 执行) + +> 目标:这是一份“可中断、可恢复、可分段提交”的实施计划。 +> 原则:每阶段都能独立编译、独立验证、独立回滚,不要求一次性改完。 + +--- + +## 0. 执行约束(必须遵守) + +1. **一次只做一个阶段**,每阶段结束后必须通过该阶段验收再进入下一阶段。 +2. **不跨阶段混改**:例如阶段 1 不改 hook 覆盖,阶段 3 不重构线程模型。 +3. **每阶段必须留下恢复锚点**: + - 文档状态更新(本文件“阶段状态”区) + - 代码中保留临时兼容开关(若有) + - 能独立提交 commit(建议) +4. **失败可回退**:阶段内失败仅回退本阶段改动,不影响已完成阶段。 +5. **默认保持旧行为可用**,新增能力通过新路径启用,逐步替换旧路径。 + +--- + +## 1. 阶段状态(可重入) + +- [ ] Phase 1: IO 请求结构体落地(单线程兼容模式) +- [ ] Phase 2: Worker + 多线程执行模型落地 +- [ ] Phase 3: 偏移型 IO 完整化(pread/pwrite 语义) +- [ ] Phase 4: RocksDB 核心 hook 补齐(P0) +- [ ] Phase 5: 目录/锁/截断接口补齐(P1) +- [ ] Phase 6: 元数据一致性与崩溃恢复增强 +- [ ] Phase 7: 性能增强与观测 + +> 重入方式:中断后先看此状态区 + 对应阶段“完成定义”,从未完成阶段继续。 + +--- + +## 2. 基线与分支策略 + +### 2.1 基线要求 +- 当前 `libzvfs.so` 可正常构建。 +- 当前 `func_test` 可运行。 +- 已确认 `/zvfs` 路径仍由现有 hook 接管。 + +### 2.2 分支与提交建议 +- 每阶段 1 个分支或 1 个 commit: + - `phase1-req-struct` + - `phase2-worker-thread` + - `phase3-offset-io` + - ... +- commit message 模板: + - `phaseX: + ` + +--- + +## 3. 分阶段实施 + +## Phase 1: IO 请求结构体落地(不改线程模型) + +### 目标 +先把“每次系统调用复用 file 临时字段”的问题解开;引入可扩展请求对象,为后续多线程做准备。 +**本阶段仍允许单线程执行路径**(内部可继续使用现有 waiter)。 + +### 改动范围 +- `zvfs/zvfs.h` +- `zvfs/zvfs.c` +- 尽量不改 `zvfs/zvfs_hook.c` 的接口覆盖面(只做必要适配) + +### 任务清单 +1. 新增 `zvfs_req_t`(最小字段): + - `op`, `file`, `buf`, `count`, `offset`, `ret`, `op_errno`, `done` + - 可选:`need_copy_back`, `actual_io_count` +2. 给现有读写流程加“请求入参”版本: + - 新增 `zvfs_read_req(req)` / `zvfs_write_req(req)` 内部函数 + - 旧 API `zvfs_read/zvfs_write` 变成薄包装(构造 req 后调用) +3. 去除对 `file->io_count/write_staging_buf/finished` 的强依赖: + - 保留字段可兼容,但主流程改为优先使用 req 字段 +4. 错误返回统一: + - 内部负 errno -> 外部返回值 + errno 映射规则固定 + +### 完成定义(DoD) +- 编译通过。 +- 现有 `func_test` 全通过。 +- 无并发改造前提下,行为与旧版一致(回归结果一致)。 + +### 重入点 +- 代码里出现 `zvfs_req_t` 且 `zvfs_read/zvfs_write` 已封装请求对象。 +- 若中断,优先检查旧路径是否仍可用,再继续替换剩余 call path。 + +--- + +## Phase 2: Worker + 多线程执行模型 + +### 目标 +将“调用线程主动 poll SPDK”改为“专用 worker 线程 poll + 请求队列”。 +建立线程安全基础,不追求本阶段 hook 覆盖齐全。 + +### 改动范围 +- `zvfs/zvfs.h` +- `zvfs/zvfs.c` +- `zvfs/zvfs_hook.c`(仅初始化、提交流程相关) + +### 任务清单 +1. 新增 `zvfs_worker_t`: + - `pthread_t tid` + - 请求队列(先用 mutex + list + cond,后续可换 MPSC) + - `running/stopping` 状态 +2. 新增生命周期函数: + - `zvfs_worker_start()` + - `zvfs_worker_stop()` + - 在 mount/init 时启动,在 atexit/unmount 时停止 +3. 新增提交流程: + - `zvfs_submit_and_wait(req)`:调用线程阻塞等待完成 + - worker 线程执行 SPDK 异步链并回填 req +4. 替换 `waiter()` 主路径: + - 保留 waiter 仅作为 fallback(编译开关控制) +5. 锁与并发保护(最小闭环): + - 全局状态锁:`g_fs/g_mounted/fd_map` + - 文件级锁:`offset` 更新、close 与 io 竞争 + +### 完成定义(DoD) +- `db_bench --threads=4` 不崩溃、不死锁(先小规模)。 +- 旧单线程 case 仍通过。 +- 调用线程不再直接 `spdk_thread_poll`。 + +### 重入点 +- worker 生命周期已接管主流程(可在日志中看到 worker 启动/停止)。 +- 若中断,优先确保 stop 路径可达,避免进程退出挂死。 + +--- + +## Phase 3: 偏移型 IO 完整化(pread/pwrite 语义) + +### 目标 +补齐 RocksDB 高频调用语义:`pread/pwrite` 不改文件偏移。 +把 `read/write` 变成 `pread/pwrite + offset 管理` 的包装。 + +### 改动范围 +- `zvfs/zvfs.h` +- `zvfs/zvfs.c` +- `zvfs/zvfs_hook.c` + +### 任务清单 +1. 新增核心接口: + - `zvfs_pread(file, buf, count, offset)` + - `zvfs_pwrite(file, buf, count, offset)` +2. 改造 read/write: + - `read`:在 file lock 下读取并推进 `current_offset` + - `write`:在 file lock 下决定 offset(含 O_APPEND) +3. 明确并发语义: + - 同一 fd 并发 pread/pwrite 允许并行(不同 offset) + - 同一 fd 的 read/write 偏移操作受 file lock 串行 + +### 完成定义(DoD) +- `pread/pwrite` 回归用例通过。 +- 同一 fd 的并发 `pread` 不互相污染 offset。 + +### 重入点 +- hook 层可区分 `read/write` 与 `pread/pwrite` 路径。 +- 若中断,先保证 `read/write` 仍有正确 fallback。 + +--- + +## Phase 4: RocksDB 核心 hook 补齐(P0) + +### 目标 +先覆盖 RocksDB “能跑起来”最关键接口。 + +### 改动范围 +- `zvfs/zvfs_hook.c` +- 必要时 `zvfs/zvfs.h` 新增声明 + +### 任务清单(按优先级) +1. 打开类: + - `open/open64/openat/openat64/__open_2/__open64_2` +2. IO 类: + - `pread/pread64/pwrite/pwrite64` +3. 元数据类: + - `fstat/stat/lstat/fstatat/access` +4. 持久化类: + - `fsync/fdatasync` +5. 文件变更类: + - `rename/renameat/renameat2/unlink/unlinkat` + +### 实现要求 +- 非 `/zvfs` 路径必须透传 real libc。 +- 所有失败分支必须设置 errno。 +- 统一 dlsym 初始化:`pthread_once`,避免递归。 + +### 完成定义(DoD) +- RocksDB 基础 workload(单线程)可跑通: + - `fillseq`, `fillrandom`, `readrandom` +- `strace -f` 观察 `/zvfs` 关键 syscall 已被接管。 + +### 重入点 +- 每新增一组 hook 独立提交,失败时可只回退该组。 + +--- + +## Phase 5: 目录/锁/截断接口补齐(P1) + +### 目标 +补齐 RocksDB 稳定运行需要的辅助接口。 + +### 改动范围 +- `zvfs/zvfs_hook.c` +- `zvfs/zvfs.c`(必要后端支撑) + +### 任务清单 +1. 截断: + - `ftruncate/truncate` +2. 锁: + - `fcntl` 最少支持 `F_SETLK/F_SETLKW/F_UNLCK/F_GETLK` +3. 目录: + - `mkdir/rmdir/opendir/readdir/closedir` +4. 容量提示(可选但建议): + - `fallocate/posix_fallocate`(不支持时明确 `EOPNOTSUPP`) + +### 完成定义(DoD) +- RocksDB 多线程基础 workload 可稳定运行(`--threads=4/8`)。 +- LOCK 文件语义满足单进程多线程正确性。 + +### 重入点 +- `fcntl` 与目录接口可拆成两个子阶段提交。 + +--- + +## Phase 6: 元数据一致性与崩溃恢复增强 + +### 目标 +解决 `zvfs_meta.txt` 全量覆盖、崩溃窗口、恢复不确定问题。 + +### 改动范围 +- `zvfs/zvfs_hook.c`(meta load/save) +- `zvfs/zvfs.c`(关键操作强制 flush 时机) +- 文档与测试脚本 + +### 任务清单 +1. 元数据写入策略: + - 从“每次全量覆盖”改为“批量 + 关键点强制刷盘” +2. 元数据文件格式增强: + - 增加版本号 + CRC +3. 恢复路径: + - 加载失败时回退到最近完整版本(若实现双文件/快照) +4. 关键操作一致性点: + - `rename/unlink/truncate/fsync` 后策略明确 + +### 完成定义(DoD) +- 人工崩溃注入后(kill -9)可重新打开 DB。 +- 元数据损坏能被检测并报错,不默默产生脏状态。 + +### 重入点 +- 先做“格式版本化 + 校验”,再做“批量策略”。 + +--- + +## Phase 7: 性能增强与观测 + +### 目标 +在正确性稳定后提性能,确保收益可量化。 + +### 改动范围 +- `zvfs/zvfs.c` +- 性能脚本与统计输出 + +### 任务清单 +1. 请求对象池 / DMA buffer 复用 +2. 减少全局锁竞争(热点路径细化锁) +3. 元数据 debounce 与批量刷盘参数化 +4. 增加指标: + - op 次数、失败率、平均/尾延迟、队列深度 + +### 完成定义(DoD) +- 与旧版本对比,多线程 `db_bench` 吞吐显著提升(目标 >1.5x,按实测调整)。 +- 无新增数据一致性回归。 + +### 重入点 +- 每个优化项独立开关,可单独启停做 A/B。 + +--- + +## 4. 阶段间依赖关系(严格) + +1. 必须先完成 Phase 1 再做 Phase 2。 +2. 必须先完成 Phase 2 再做 Phase 3/4。 +3. Phase 4(核心 hook)完成后再做 Phase 5(辅助 hook)。 +4. Phase 6/7 必须基于 Phase 5 稳定分支。 + +--- + +## 5. 验收矩阵(执行时打勾) + +### Phase 1 +- [ ] 编译通过 +- [ ] `func_test` 通过 +- [ ] 请求对象已接管 read/write 内部主流程 + +### Phase 2 +- [ ] worker 模型接管 +- [ ] 多线程 smoke 测试通过 +- [ ] 无死锁/退出挂死 + +### Phase 3 +- [ ] `pread/pwrite` 语义正确 +- [ ] offset 并发污染问题消失 + +### Phase 4 +- [ ] P0 hook 集合已实现 +- [ ] RocksDB 单线程 workload 跑通 + +### Phase 5 +- [ ] P1 hook 集合已实现 +- [ ] RocksDB 多线程 workload 稳定 + +### Phase 6 +- [ ] 元数据校验与恢复策略生效 +- [ ] 崩溃恢复测试通过 + +### Phase 7 +- [ ] 关键指标可观测 +- [ ] 性能目标达成或给出瓶颈结论 + +--- + +## 6. 风险与应对(执行版) + +1. **接口补齐不完整** + - 应对:每阶段跑 `strace -f`,统计 `/zvfs` 相关未接管 syscall。 +2. **并发死锁** + - 应对:固定锁顺序;debug 模式打印锁获取日志。 +3. **行为回归** + - 应对:保留兼容开关(old path)直到该阶段验收通过。 +4. **性能退化** + - 应对:优化项独立开关,逐项 A/B,不一次性合并。 + +--- + +## 7. 给 Codex 的执行模板(每阶段复用) + +1. 阅读本阶段“目标/任务/DoD/重入点” +2. 只改本阶段文件与最小依赖 +3. 本地编译 + 对应测试 +4. 更新“阶段状态”勾选 +5. 输出: + - 改了什么 + - 测了什么 + - 还剩什么 +6. 等待用户确认再进入下一阶段 + diff --git a/test/Makefile b/test/Makefile new file mode 100755 index 0000000..06f7cee --- /dev/null +++ b/test/Makefile @@ -0,0 +1,42 @@ +CC ?= gcc +CFLAGS ?= -O2 -Wall -Wextra -std=gnu11 + +SRCS := $(sort $(wildcard test_*.c)) +BIN_DIR ?= bin +BIN_NAMES := $(SRCS:.c=) +BINS := $(addprefix $(BIN_DIR)/,$(BIN_NAMES)) +RUN_DIR ?= /tmp/zvfs-test +RUN_BINS ?= test_basic test_lseek test_dual_open_same_file test_two_files \ + test_single_file_perf test_single_file_random_perf \ + test_single_file_random_noaligned_perf test_write_file test_read_delete_file + +.PHONY: all clean list run-test + +all: $(BINS) + +$(BIN_DIR): + mkdir -p $@ + +$(BIN_DIR)/%: %.c test_utils.h | $(BIN_DIR) + $(CC) $(CFLAGS) -o $@ $< + +list: + @printf "%s\n" $(BINS) + +run-test: all + @mkdir -p $(RUN_DIR) + @pass=0; fail=0; \ + for t in $(RUN_BINS); do \ + printf "\n[RUN] %s\n" "$$t"; \ + if ./$(BIN_DIR)/$$t $(RUN_DIR); then \ + pass=$$((pass + 1)); \ + else \ + fail=$$((fail + 1)); \ + fi; \ + done; \ + printf "\n=== run-test summary: PASS=%d FAIL=%d ===\n" $$pass $$fail; \ + test $$fail -eq 0 + +clean: + $(RM) $(BINS) + -rmdir $(BIN_DIR) diff --git a/test/test_basic.c b/test/test_basic.c new file mode 100755 index 0000000..0436980 --- /dev/null +++ b/test/test_basic.c @@ -0,0 +1,51 @@ +#include "test_utils.h" + +static int test_basic(const char *path) +{ + printf("\n=== test_basic ===\n"); + + printf("open: %s\n", path); + int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd < 0) { perror("open"); return 1; } + + const char *msg = "ABCDEFGHIJKL"; + ssize_t w = write(fd, msg, strlen(msg)); + if (w < 0) { perror("write"); return 2; } + printf("write: %zd\n", w); + + const char *msg2 = "MNOPQRSTUVWXYZ"; + ssize_t w2 = write(fd, msg2, strlen(msg2)); + if (w2 < 0) { perror("write"); return 2; } + printf("write: %zd\n", w2); + + close(fd); + + fd = open(path, O_RDONLY); + if (fd < 0) { perror("open R"); return 3; } + + char buf[10]; + memset(buf, 0, sizeof(buf)); + ssize_t r = read(fd, buf, sizeof(buf)); + if (r < 0) { perror("read"); return 4; } + printf("read: %zd bytes: %.*s\n", r, (int)r, buf); + + char buf2[512]; + memset(buf2, 0, sizeof(buf2)); + ssize_t r2 = read(fd, buf2, sizeof(buf2)); + if (r2 < 0) { perror("read"); return 4; } + printf("read: %zd bytes: %.*s\n", r2, (int)r2, buf2); + + close(fd); + + if (unlink(path) != 0) { perror("unlink"); return 5; } + printf("unlink: ok\n"); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_basic(path); + return report_result("test_basic", rc); +} diff --git a/test/test_dual_open_same_file.c b/test/test_dual_open_same_file.c new file mode 100755 index 0000000..df6e9a7 --- /dev/null +++ b/test/test_dual_open_same_file.c @@ -0,0 +1,50 @@ +#include "test_utils.h" + +static int test_dual_open_same_file(const char *path) +{ + printf("\n=== test_dual_open_same_file ===\n"); + + int fd_init = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd_init < 0) { perror("open init"); return 1; } + const char *init = "0123456789"; + if (write(fd_init, init, 10) != 10) { perror("write init"); return 2; } + close(fd_init); + + int fd_w = open(path, O_WRONLY); + if (fd_w < 0) { perror("open W"); return 3; } + + int fd_r = open(path, O_RDONLY); + if (fd_r < 0) { perror("open R"); return 4; } + + printf("fd_w=%d fd_r=%d\n", fd_w, fd_r); + + if (write(fd_w, "HELLO", 5) != 5) { perror("write"); return 5; } + printf("write via fd_w: HELLO (overwrite first 5 bytes)\n"); + + char buf[32] = {0}; + lseek(fd_r, 0, SEEK_SET); + ssize_t r = read(fd_r, buf, sizeof(buf)); + printf("read via fd_r: %zd bytes: %.*s (expect: HELLO56789)\n", r, (int)r, buf); + + lseek(fd_w, 0, SEEK_END); + if (write(fd_w, "!!!", 3) != 3) { perror("write append"); return 6; } + printf("write append via fd_w: !!!\n"); + + lseek(fd_r, 10, SEEK_SET); + memset(buf, 0, sizeof(buf)); + r = read(fd_r, buf, sizeof(buf)); + printf("read appended via fd_r: %zd bytes: %.*s (expect: !!!)\n", r, (int)r, buf); + + close(fd_w); + close(fd_r); + unlink(path); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_dual_open_same_file(path); + return report_result("test_dual_open_same_file", rc); +} diff --git a/test/test_lseek.c b/test/test_lseek.c new file mode 100755 index 0000000..e1d1543 --- /dev/null +++ b/test/test_lseek.c @@ -0,0 +1,55 @@ +#include "test_utils.h" + +static int test_lseek(const char *path) +{ + printf("\n=== test_lseek ===\n"); + + int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd < 0) { perror("open"); return 1; } + + const char *alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + if (write(fd, alpha, 26) != 26) { perror("write"); return 2; } + printf("write 26 bytes: %s\n", alpha); + + off_t pos = lseek(fd, 0, SEEK_SET); + printf("lseek SEEK_SET 0 -> %ld\n", (long)pos); + char buf[32] = {0}; + ssize_t r = read(fd, buf, 5); + printf("read 5 bytes: %.*s (expect: ABCDE)\n", (int)r, buf); + + pos = lseek(fd, 3, SEEK_CUR); + printf("lseek SEEK_CUR +3 -> %ld\n", (long)pos); + memset(buf, 0, sizeof(buf)); + r = read(fd, buf, 5); + printf("read 5 bytes: %.*s (expect: IJKLM)\n", (int)r, buf); + + pos = lseek(fd, -5, SEEK_END); + printf("lseek SEEK_END -5 -> %ld\n", (long)pos); + memset(buf, 0, sizeof(buf)); + r = read(fd, buf, 10); + printf("read %zd bytes: %.*s (expect: VWXYZ)\n", r, (int)r, buf); + + pos = lseek(fd, 30, SEEK_SET); + printf("lseek SEEK_SET 30 -> %ld\n", (long)pos); + if (write(fd, "!", 1) != 1) { perror("write hole"); return 3; } + + lseek(fd, 26, SEEK_SET); + memset(buf, 0xAA, sizeof(buf)); + r = read(fd, buf, 5); + printf("read hole+1: %zd bytes, hole[0]=%02X hole[1]=%02X hole[2]=%02X " + "hole[3]=%02X last='%c' (expect: 00 00 00 00 '!')\n", + r, (unsigned char)buf[0], (unsigned char)buf[1], + (unsigned char)buf[2], (unsigned char)buf[3], buf[4]); + + close(fd); + unlink(path); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_lseek(path); + return report_result("test_lseek", rc); +} diff --git a/test/test_read_delete_file.c b/test/test_read_delete_file.c new file mode 100755 index 0000000..eaef2b7 --- /dev/null +++ b/test/test_read_delete_file.c @@ -0,0 +1,31 @@ +#include "test_utils.h" + +static int test_read_delete_file(const char *path) +{ + printf("\n=== test_read_delete_file ===\n"); + + int fd = open(path, O_RDONLY); + if (fd < 0) { perror("open"); return 1; } + printf("open: %s fd=%d\n", path, fd); + + char buf[256] = {0}; + ssize_t r = read(fd, buf, sizeof(buf)); + if (r < 0) { perror("read"); close(fd); return 2; } + printf("read: %zd bytes: %.*s\n", r, (int)r, buf); + + close(fd); + printf("close: ok\n"); + + if (unlink(path) != 0) { perror("unlink"); return 3; } + printf("unlink: ok\n"); + + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_read_delete_file(path); + return report_result("test_read_delete_file", rc); +} diff --git a/test/test_single_file_perf.c b/test/test_single_file_perf.c new file mode 100755 index 0000000..8258d62 --- /dev/null +++ b/test/test_single_file_perf.c @@ -0,0 +1,92 @@ +#include "test_utils.h" + +static int test_single_file_perf(const char *path) +{ + size_t io_size = 128 * 1024; + size_t max_size = 2ULL * 1024 * 1024 * 1024; + size_t max_count = max_size / io_size; + int test_sec = 10; + int direct = 0; + + printf("\n=== test_single_file_perf ===\n"); + printf("Path : %s\n", path); + printf("IO size : %zu KB\n", io_size / 1024); + printf("Max file: %zu MB\n", max_size / 1024 / 1024); + printf("Duration: %d sec\n", test_sec); + + unlink(path); + char *buf = aligned_alloc(4096, io_size); + if (!buf) { perror("aligned_alloc"); return 1; } + memset(buf, 'A', io_size); + + struct timespec t1, t2, now; + + int fd = open(path, O_CREAT | O_RDWR | direct, 0644); + if (fd < 0) { perror("open write"); free(buf); return 1; } + + clock_gettime(CLOCK_MONOTONIC, &t1); + size_t wcount = 0; + size_t wpos = 0; + do { + if (wpos >= max_count) { + lseek(fd, 0, SEEK_SET); + wpos = 0; + } + if (write(fd, buf, io_size) != (ssize_t)io_size) { + perror("write"); + close(fd); + free(buf); + return 2; + } + wcount++; + wpos++; + clock_gettime(CLOCK_MONOTONIC, &now); + } while (time_diff_sec(t1, now) < test_sec); + clock_gettime(CLOCK_MONOTONIC, &t2); + close(fd); + + double wsec = time_diff_sec(t1, t2); + double wmb = (double)(wcount * io_size) / (1024.0 * 1024.0); + printf("\nWRITE:\n"); + printf(" total : %.1f MB\n", wmb); + printf(" time : %.3f sec\n", wsec); + printf(" IOPS : %.0f ops/sec\n", wcount / wsec); + printf(" BW : %.2f MB/s\n", wmb / wsec); + + fd = open(path, O_RDONLY | direct); + if (fd < 0) { perror("open read"); free(buf); return 3; } + + clock_gettime(CLOCK_MONOTONIC, &t1); + size_t rcount = 0; + do { + ssize_t r = read(fd, buf, io_size); + if (r <= 0) { + lseek(fd, 0, SEEK_SET); + continue; + } + rcount++; + clock_gettime(CLOCK_MONOTONIC, &now); + } while (time_diff_sec(t1, now) < test_sec); + clock_gettime(CLOCK_MONOTONIC, &t2); + close(fd); + + double rsec = time_diff_sec(t1, t2); + double rmb = (double)(rcount * io_size) / (1024.0 * 1024.0); + printf("\nREAD:\n"); + printf(" total : %.1f MB\n", rmb); + printf(" time : %.3f sec\n", rsec); + printf(" IOPS : %.0f ops/sec\n", rcount / rsec); + printf(" BW : %.2f MB/s\n", rmb / rsec); + + unlink(path); + free(buf); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_single_file_perf(path); + return report_result("test_single_file_perf", rc); +} diff --git a/test/test_single_file_random_noaligned_perf.c b/test/test_single_file_random_noaligned_perf.c new file mode 100755 index 0000000..ed5d23a --- /dev/null +++ b/test/test_single_file_random_noaligned_perf.c @@ -0,0 +1,116 @@ +#include "test_utils.h" + +static int test_single_file_random_noaligned_perf(const char *path) +{ + size_t io_size = 128 * 1024; + size_t max_size = 2ULL * 1024 * 1024 * 1024; + int test_sec = 10; + int direct = 0; + + printf("\n=== test_single_file_random_noaligned_perf ===\n"); + printf("Path : %s\n", path); + printf("IO size : %zu KB\n", io_size / 1024); + printf("Range : %zu MB\n", max_size / 1024 / 1024); + printf("Duration: %d sec\n", test_sec); + + srand(0x1234); + + char *buf = aligned_alloc(4096, io_size); + if (!buf) { perror("aligned_alloc"); return 1; } + memset(buf, 'A', io_size); + + struct timespec t1, t2, now; + + unlink(path); + + int fd = open(path, O_CREAT | O_RDWR | direct, 0644); + if (fd < 0) { perror("open rand write"); free(buf); return 2; } + + clock_gettime(CLOCK_MONOTONIC, &t1); + + size_t wcount = 0; + do { + off_t offset = (off_t)(rand() % (max_size - io_size)); + + if (lseek(fd, offset, SEEK_SET) < 0) { + perror("lseek rand write"); + close(fd); + free(buf); + return 3; + } + + if (write(fd, buf, io_size) != (ssize_t)io_size) { + perror("rand write"); + close(fd); + free(buf); + return 4; + } + + wcount++; + clock_gettime(CLOCK_MONOTONIC, &now); + } while (time_diff_sec(t1, now) < test_sec); + + clock_gettime(CLOCK_MONOTONIC, &t2); + close(fd); + + double wsec = time_diff_sec(t1, t2); + double wmb = (double)wcount * io_size / 1024 / 1024; + + printf("\nRANDOM WRITE:\n"); + printf(" total : %.1f MB\n", wmb); + printf(" time : %.3f sec\n", wsec); + printf(" IOPS : %.0f ops/sec\n", wcount / wsec); + printf(" BW : %.2f MB/s\n", wmb / wsec); + + fd = open(path, O_RDONLY | direct); + if (fd < 0) { perror("open rand read"); free(buf); return 5; } + + clock_gettime(CLOCK_MONOTONIC, &t1); + + size_t rcount = 0; + do { + off_t offset = (off_t)(rand() % (max_size - io_size)); + + if (lseek(fd, offset, SEEK_SET) < 0) { + perror("lseek rand read"); + close(fd); + free(buf); + return 6; + } + + ssize_t r = read(fd, buf, io_size); + if (r < 0) { + perror("rand read"); + close(fd); + free(buf); + return 7; + } + + rcount++; + clock_gettime(CLOCK_MONOTONIC, &now); + } while (time_diff_sec(t1, now) < test_sec); + + clock_gettime(CLOCK_MONOTONIC, &t2); + close(fd); + + double rsec = time_diff_sec(t1, t2); + double rmb = (double)rcount * io_size / 1024 / 1024; + + printf("\nRANDOM READ:\n"); + printf(" total : %.1f MB\n", rmb); + printf(" time : %.3f sec\n", rsec); + printf(" IOPS : %.0f ops/sec\n", rcount / rsec); + printf(" BW : %.2f MB/s\n", rmb / rsec); + + unlink(path); + free(buf); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_single_file_random_noaligned_perf(path); + return report_result("test_single_file_random_noaligned_perf", rc); +} diff --git a/test/test_single_file_random_perf.c b/test/test_single_file_random_perf.c new file mode 100755 index 0000000..a4b0731 --- /dev/null +++ b/test/test_single_file_random_perf.c @@ -0,0 +1,119 @@ +#include "test_utils.h" + +static int test_single_file_random_perf(const char *path) +{ + size_t io_size = 128 * 1024; + size_t max_size = 2ULL * 1024 * 1024 * 1024; + size_t max_count = max_size / io_size; + int test_sec = 10; + int direct = 0; + + printf("\n=== test_single_file_random_perf ===\n"); + printf("Path : %s\n", path); + printf("IO size : %zu KB\n", io_size / 1024); + printf("Range : %zu MB\n", max_size / 1024 / 1024); + printf("Duration: %d sec\n", test_sec); + + srand(0x1234); + + char *buf = aligned_alloc(4096, io_size); + if (!buf) { perror("aligned_alloc"); return 1; } + memset(buf, 'A', io_size); + + struct timespec t1, t2, now; + + unlink(path); + + int fd = open(path, O_CREAT | O_RDWR | direct, 0644); + if (fd < 0) { perror("open rand write"); free(buf); return 2; } + + clock_gettime(CLOCK_MONOTONIC, &t1); + + size_t wcount = 0; + do { + size_t blk = rand() % max_count; + off_t offset = (off_t)blk * io_size; + + if (lseek(fd, offset, SEEK_SET) < 0) { + perror("lseek rand write"); + close(fd); + free(buf); + return 3; + } + + if (write(fd, buf, io_size) != (ssize_t)io_size) { + perror("rand write"); + close(fd); + free(buf); + return 4; + } + + wcount++; + clock_gettime(CLOCK_MONOTONIC, &now); + } while (time_diff_sec(t1, now) < test_sec); + + clock_gettime(CLOCK_MONOTONIC, &t2); + close(fd); + + double wsec = time_diff_sec(t1, t2); + double wmb = (double)wcount * io_size / 1024 / 1024; + + printf("\nRANDOM WRITE:\n"); + printf(" total : %.1f MB\n", wmb); + printf(" time : %.3f sec\n", wsec); + printf(" IOPS : %.0f ops/sec\n", wcount / wsec); + printf(" BW : %.2f MB/s\n", wmb / wsec); + + fd = open(path, O_RDONLY | direct); + if (fd < 0) { perror("open rand read"); free(buf); return 5; } + + clock_gettime(CLOCK_MONOTONIC, &t1); + + size_t rcount = 0; + do { + size_t blk = rand() % max_count; + off_t offset = (off_t)blk * io_size; + + if (lseek(fd, offset, SEEK_SET) < 0) { + perror("lseek rand read"); + close(fd); + free(buf); + return 6; + } + + ssize_t r = read(fd, buf, io_size); + if (r < 0) { + perror("rand read"); + close(fd); + free(buf); + return 7; + } + + rcount++; + clock_gettime(CLOCK_MONOTONIC, &now); + } while (time_diff_sec(t1, now) < test_sec); + + clock_gettime(CLOCK_MONOTONIC, &t2); + close(fd); + + double rsec = time_diff_sec(t1, t2); + double rmb = (double)rcount * io_size / 1024 / 1024; + + printf("\nRANDOM READ:\n"); + printf(" total : %.1f MB\n", rmb); + printf(" time : %.3f sec\n", rsec); + printf(" IOPS : %.0f ops/sec\n", rcount / rsec); + printf(" BW : %.2f MB/s\n", rmb / rsec); + + unlink(path); + free(buf); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_single_file_random_perf(path); + return report_result("test_single_file_random_perf", rc); +} diff --git a/test/test_two_files.c b/test/test_two_files.c new file mode 100755 index 0000000..45980d2 --- /dev/null +++ b/test/test_two_files.c @@ -0,0 +1,78 @@ +#include "test_utils.h" + +static int test_two_files(const char *path_a, const char *path_b) +{ + printf("\n=== test_two_files ===\n"); + + int fd_a = open(path_a, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd_a < 0) { perror("open A"); return 1; } + + int fd_b = open(path_b, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd_b < 0) { perror("open B"); return 2; } + + printf("fd_a=%d fd_b=%d\n", fd_a, fd_b); + + const char *data_a = "File-A: Hello World!"; + const char *data_b = "File-B: Goodbye World!"; + if (write(fd_a, data_a, strlen(data_a)) < 0) { perror("write A"); return 3; } + if (write(fd_b, data_b, strlen(data_b)) < 0) { perror("write B"); return 4; } + printf("write A: %s\n", data_a); + printf("write B: %s\n", data_b); + + lseek(fd_a, 0, SEEK_SET); + lseek(fd_b, 0, SEEK_SET); + + char buf_a[64] = {0}; + char buf_b[64] = {0}; + ssize_t r_a = read(fd_a, buf_a, sizeof(buf_a)); + ssize_t r_b = read(fd_b, buf_b, sizeof(buf_b)); + + printf("read A: %zd bytes: %.*s\n", r_a, (int)r_a, buf_a); + printf("read B: %zd bytes: %.*s\n", r_b, (int)r_b, buf_b); + + int ok = 1; + if (strncmp(buf_a, data_a, strlen(data_a)) != 0) { + printf("FAIL: A content mismatch!\n"); + ok = 0; + } + if (strncmp(buf_b, data_b, strlen(data_b)) != 0) { + printf("FAIL: B content mismatch!\n"); + ok = 0; + } + if (ok) { + printf("PASS: both files read back correctly\n"); + } + + lseek(fd_a, 0, SEEK_END); + if (write(fd_a, "[A-TAIL]", 8) != 8) { perror("append A"); return 5; } + + lseek(fd_b, 8, SEEK_SET); + if (write(fd_b, "Hi! ", 7) != 7) { perror("overwrite B"); return 6; } + + lseek(fd_a, 0, SEEK_SET); + lseek(fd_b, 0, SEEK_SET); + memset(buf_a, 0, sizeof(buf_a)); + memset(buf_b, 0, sizeof(buf_b)); + r_a = read(fd_a, buf_a, sizeof(buf_a)); + r_b = read(fd_b, buf_b, sizeof(buf_b)); + printf("after cross-write:\n"); + printf(" A: %.*s\n", (int)r_a, buf_a); + printf(" B: %.*s\n", (int)r_b, buf_b); + + close(fd_a); + close(fd_b); + unlink(path_a); + unlink(path_b); + return 0; +} + +int main(int argc, char **argv) +{ + char path_a[PATH_MAX]; + char path_b[PATH_MAX]; + const char *dir = argc >= 2 ? argv[1] : NULL; + make_path(path_a, sizeof(path_a), dir, "file_a.dat"); + make_path(path_b, sizeof(path_b), dir, "file_b.dat"); + int rc = test_two_files(path_a, path_b); + return report_result("test_two_files", rc); +} diff --git a/test/test_utils.h b/test/test_utils.h new file mode 100755 index 0000000..bd9363b --- /dev/null +++ b/test/test_utils.h @@ -0,0 +1,40 @@ +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +static inline double time_diff_sec(struct timespec a, struct timespec b) +{ + return (b.tv_sec - a.tv_sec) + (b.tv_nsec - a.tv_nsec) / 1000000000.0; +} + +static inline void make_path(char *out, size_t out_sz, const char *dir, const char *name) +{ + if (dir && dir[0] != 0) { + snprintf(out, out_sz, "%s/%s", dir, name); + } else { + snprintf(out, out_sz, "/tmp/%s", name); + } +} + +static inline int report_result(const char *name, int rc) +{ + printf("\n=== %s %s ===\n", name, rc == 0 ? "PASSED" : "FAILED"); + return rc; +} + +#endif diff --git a/test/test_write_file.c b/test/test_write_file.c new file mode 100755 index 0000000..ab67daf --- /dev/null +++ b/test/test_write_file.c @@ -0,0 +1,27 @@ +#include "test_utils.h" + +static int test_write_file(const char *path) +{ + printf("\n=== test_write_file ===\n"); + + int fd = open(path, O_CREAT | O_RDWR, 0644); + if (fd < 0) { perror("open"); return 1; } + printf("open: %s fd=%d\n", path, fd); + + const char *msg = "Hello, zvfs!"; + ssize_t w = write(fd, msg, strlen(msg)); + if (w < 0) { perror("write"); close(fd); return 2; } + printf("write: %zd bytes: %s\n", w, msg); + + close(fd); + printf("close: ok\n"); + return 0; +} + +int main(int argc, char **argv) +{ + char path[PATH_MAX]; + make_path(path, sizeof(path), argc >= 2 ? argv[1] : NULL, "file.dat"); + int rc = test_write_file(path); + return report_result("test_write_file", rc); +} diff --git a/zvfs/func_test.c b/zvfs/func_test.c deleted file mode 100644 index 102b3da..0000000 --- a/zvfs/func_test.c +++ /dev/null @@ -1,644 +0,0 @@ -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include - -static double time_diff_sec(struct timespec a, struct timespec b) -{ - return (b.tv_sec - a.tv_sec) + - (b.tv_nsec - a.tv_nsec) / 1000000000.0; -} - -static int test_single_file_perf(const char *path) -{ - size_t io_size = 128 * 1024; - // size_t io_size = 4096; - size_t max_size = 2ULL * 1024 * 1024 * 1024; /* 最大 2GB,循环覆写 */ - size_t max_count = max_size / io_size; - int test_sec = 10; - - int direct = 0; - - printf("\n=== test_single_file_perf ===\n"); - printf("Path : %s\n", path); - printf("IO size : %zu KB\n", io_size / 1024); - printf("Max file: %zu MB\n", max_size / 1024 / 1024); - printf("Duration: %d sec\n", test_sec); - - unlink(path); - char *buf = aligned_alloc(4096, io_size); - if (!buf) { perror("aligned_alloc"); return 1; } - memset(buf, 'A', io_size); - - struct timespec t1, t2, now; - - /* ================= WRITE ================= */ - int fd = open(path, O_CREAT | O_RDWR | direct, 0644); - if (fd < 0) { perror("open write"); free(buf); return 1; } - - clock_gettime(CLOCK_MONOTONIC, &t1); - size_t wcount = 0; - size_t wpos = 0; /* 当前写位置(以块为单位) */ - do { - /* 超过最大文件大小,seek 回头循环覆写 */ - if (wpos >= max_count) { - lseek(fd, 0, SEEK_SET); - wpos = 0; - } - if (write(fd, buf, io_size) != (ssize_t)io_size) { - perror("write"); - close(fd); - free(buf); - return 2; - } - wcount++; - wpos++; - clock_gettime(CLOCK_MONOTONIC, &now); - } while (time_diff_sec(t1, now) < test_sec); - clock_gettime(CLOCK_MONOTONIC, &t2); - close(fd); - - double wsec = time_diff_sec(t1, t2); - double wmb = (double)(wcount * io_size) / (1024.0 * 1024.0); - printf("\nWRITE:\n"); - printf(" total : %.1f MB\n", wmb); - printf(" time : %.3f sec\n", wsec); - printf(" IOPS : %.0f ops/sec\n", wcount / wsec); - printf(" BW : %.2f MB/s\n", wmb / wsec); - - /* ================= READ ================= */ - fd = open(path, O_RDONLY | direct); - if (fd < 0) { perror("open read"); free(buf); return 3; } - - clock_gettime(CLOCK_MONOTONIC, &t1); - size_t rcount = 0; - do { - ssize_t r = read(fd, buf, io_size); - if (r <= 0) { - lseek(fd, 0, SEEK_SET); - continue; - } - rcount++; - clock_gettime(CLOCK_MONOTONIC, &now); - } while (time_diff_sec(t1, now) < test_sec); - clock_gettime(CLOCK_MONOTONIC, &t2); - close(fd); - - double rsec = time_diff_sec(t1, t2); - double rmb = (double)(rcount * io_size) / (1024.0 * 1024.0); - printf("\nREAD:\n"); - printf(" total : %.1f MB\n", rmb); - printf(" time : %.3f sec\n", rsec); - printf(" IOPS : %.0f ops/sec\n", rcount / rsec); - printf(" BW : %.2f MB/s\n", rmb / rsec); - - unlink(path); - free(buf); - return 0; -} - -static int test_single_file_random_perf(const char *path) -{ - size_t io_size = 128 * 1024; - size_t max_size = 2ULL * 1024 * 1024 * 1024; - size_t max_count = max_size / io_size; - int test_sec = 10; - int direct = 0; - - printf("\n=== test_single_file_random_perf ===\n"); - printf("Path : %s\n", path); - printf("IO size : %zu KB\n", io_size / 1024); - printf("Range : %zu MB\n", max_size / 1024 / 1024); - printf("Duration: %d sec\n", test_sec); - - srand(0x1234); - - char *buf = aligned_alloc(4096, io_size); - if (!buf) { perror("aligned_alloc"); return 1; } - memset(buf, 'A', io_size); - - struct timespec t1, t2, now; - - unlink(path); - - /* ================= RANDOM WRITE ================= */ - int fd = open(path, O_CREAT | O_RDWR | direct, 0644); - if (fd < 0) { perror("open rand write"); free(buf); return 2; } - - clock_gettime(CLOCK_MONOTONIC, &t1); - - size_t wcount = 0; - - do { - size_t blk = rand() % max_count; - off_t offset = (off_t)blk * io_size; - - if (lseek(fd, offset, SEEK_SET) < 0) { - perror("lseek rand write"); - close(fd); - free(buf); - return 3; - } - - if (write(fd, buf, io_size) != (ssize_t)io_size) { - perror("rand write"); - close(fd); - free(buf); - return 4; - } - - wcount++; - clock_gettime(CLOCK_MONOTONIC, &now); - - } while (time_diff_sec(t1, now) < test_sec); - - clock_gettime(CLOCK_MONOTONIC, &t2); - close(fd); - - double wsec = time_diff_sec(t1, t2); - double wmb = (double)wcount * io_size / 1024 / 1024; - - printf("\nRANDOM WRITE:\n"); - printf(" total : %.1f MB\n", wmb); - printf(" time : %.3f sec\n", wsec); - printf(" IOPS : %.0f ops/sec\n", wcount / wsec); - printf(" BW : %.2f MB/s\n", wmb / wsec); - - - /* ================= RANDOM READ ================= */ - fd = open(path, O_RDONLY | direct); - if (fd < 0) { perror("open rand read"); free(buf); return 5; } - - clock_gettime(CLOCK_MONOTONIC, &t1); - - size_t rcount = 0; - - do { - size_t blk = rand() % max_count; - off_t offset = (off_t)blk * io_size; - - if (lseek(fd, offset, SEEK_SET) < 0) { - perror("lseek rand read"); - close(fd); - free(buf); - return 6; - } - - ssize_t r = read(fd, buf, io_size); - if (r < 0) { - perror("rand read"); - close(fd); - free(buf); - return 7; - } - - rcount++; - clock_gettime(CLOCK_MONOTONIC, &now); - - } while (time_diff_sec(t1, now) < test_sec); - - clock_gettime(CLOCK_MONOTONIC, &t2); - close(fd); - - double rsec = time_diff_sec(t1, t2); - double rmb = (double)rcount * io_size / 1024 / 1024; - - printf("\nRANDOM READ:\n"); - printf(" total : %.1f MB\n", rmb); - printf(" time : %.3f sec\n", rsec); - printf(" IOPS : %.0f ops/sec\n", rcount / rsec); - printf(" BW : %.2f MB/s\n", rmb / rsec); - - unlink(path); - free(buf); - return 0; -} - -static int test_single_file_random_noaligned_perf(const char *path) -{ - size_t io_size = 128 * 1024; - size_t max_size = 2ULL * 1024 * 1024 * 1024; - size_t max_count = max_size / io_size; - int test_sec = 10; - int direct = 0; - - printf("\n=== test_single_file_random_perf ===\n"); - printf("Path : %s\n", path); - printf("IO size : %zu KB\n", io_size / 1024); - printf("Range : %zu MB\n", max_size / 1024 / 1024); - printf("Duration: %d sec\n", test_sec); - - srand(0x1234); - - char *buf = aligned_alloc(4096, io_size); - if (!buf) { perror("aligned_alloc"); return 1; } - memset(buf, 'A', io_size); - - struct timespec t1, t2, now; - - unlink(path); - - /* ================= RANDOM WRITE ================= */ - int fd = open(path, O_CREAT | O_RDWR | direct, 0644); - if (fd < 0) { perror("open rand write"); free(buf); return 2; } - - clock_gettime(CLOCK_MONOTONIC, &t1); - - size_t wcount = 0; - - do { - off_t offset = (off_t)(rand() % (max_size - io_size)); - - if (lseek(fd, offset, SEEK_SET) < 0) { - perror("lseek rand write"); - close(fd); - free(buf); - return 3; - } - - if (write(fd, buf, io_size) != (ssize_t)io_size) { - perror("rand write"); - close(fd); - free(buf); - return 4; - } - - wcount++; - clock_gettime(CLOCK_MONOTONIC, &now); - - } while (time_diff_sec(t1, now) < test_sec); - - clock_gettime(CLOCK_MONOTONIC, &t2); - close(fd); - - double wsec = time_diff_sec(t1, t2); - double wmb = (double)wcount * io_size / 1024 / 1024; - - printf("\nRANDOM WRITE:\n"); - printf(" total : %.1f MB\n", wmb); - printf(" time : %.3f sec\n", wsec); - printf(" IOPS : %.0f ops/sec\n", wcount / wsec); - printf(" BW : %.2f MB/s\n", wmb / wsec); - - - /* ================= RANDOM READ ================= */ - fd = open(path, O_RDONLY | direct); - if (fd < 0) { perror("open rand read"); free(buf); return 5; } - - clock_gettime(CLOCK_MONOTONIC, &t1); - - size_t rcount = 0; - - do { - off_t offset = (off_t)(rand() % (max_size - io_size)); - - if (lseek(fd, offset, SEEK_SET) < 0) { - perror("lseek rand read"); - close(fd); - free(buf); - return 6; - } - - ssize_t r = read(fd, buf, io_size); - if (r < 0) { - perror("rand read"); - close(fd); - free(buf); - return 7; - } - - rcount++; - clock_gettime(CLOCK_MONOTONIC, &now); - - } while (time_diff_sec(t1, now) < test_sec); - - clock_gettime(CLOCK_MONOTONIC, &t2); - close(fd); - - double rsec = time_diff_sec(t1, t2); - double rmb = (double)rcount * io_size / 1024 / 1024; - - printf("\nRANDOM READ:\n"); - printf(" total : %.1f MB\n", rmb); - printf(" time : %.3f sec\n", rsec); - printf(" IOPS : %.0f ops/sec\n", rcount / rsec); - printf(" BW : %.2f MB/s\n", rmb / rsec); - - unlink(path); - free(buf); - return 0; -} - -/* ------------------------------------------------------------------ */ -/* Test 1: 原有基础测试 */ -/* ------------------------------------------------------------------ */ -static int test_basic(const char *path) -{ - printf("\n=== test_basic ===\n"); - - printf("open: %s\n", path); - int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); - if (fd < 0) { perror("open"); return 1; } - - const char *msg = "ABCDEFGHIJKL"; - ssize_t w = write(fd, msg, strlen(msg)); - if (w < 0) { perror("write"); return 2; } - printf("write: %zd\n", w); - - const char *msg2 = "MNOPQRSTUVWXYZ"; - ssize_t w2 = write(fd, msg2, strlen(msg2)); - if (w2 < 0) { perror("write"); return 2; } - printf("write: %zd\n", w2); - - close(fd); - - fd = open(path, O_RDONLY); - if (fd < 0) { perror("open R"); return 3; } - - char buf[10]; - memset(buf, 0, sizeof(buf)); - ssize_t r = read(fd, buf, sizeof(buf)); - if (r < 0) { perror("read"); return 4; } - printf("read: %zd bytes: %.*s\n", r, (int)r, buf); - - char buf2[512]; - memset(buf2, 0, sizeof(buf2)); - ssize_t r2 = read(fd, buf2, sizeof(buf2)); - if (r2 < 0) { perror("read"); return 4; } - printf("read: %zd bytes: %.*s\n", r2, (int)r2, buf2); - - close(fd); - - if (unlink(path) != 0) { perror("unlink"); return 5; } - printf("unlink: ok\n"); - return 0; -} - - -/* ------------------------------------------------------------------ */ -/* Test 2: lseek */ -/* ------------------------------------------------------------------ */ -static int test_lseek(const char *path) -{ - printf("\n=== test_lseek ===\n"); - - int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); - if (fd < 0) { perror("open"); return 1; } - - /* 写入 26 个字母 */ - const char *alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - if (write(fd, alpha, 26) != 26) { perror("write"); return 2; } - printf("write 26 bytes: %s\n", alpha); - - /* SEEK_SET: 跳到第 0 字节,读 5 个 */ - off_t pos = lseek(fd, 0, SEEK_SET); - printf("lseek SEEK_SET 0 -> %ld\n", (long)pos); - char buf[32] = {0}; - ssize_t r = read(fd, buf, 5); - printf("read 5 bytes: %.*s (expect: ABCDE)\n", (int)r, buf); - - /* SEEK_CUR: 从当前位置(5)再向前跳 3 个,应读到第 8 字节 'I' */ - pos = lseek(fd, 3, SEEK_CUR); - printf("lseek SEEK_CUR +3 -> %ld\n", (long)pos); - memset(buf, 0, sizeof(buf)); - r = read(fd, buf, 5); - printf("read 5 bytes: %.*s (expect: IJKLM)\n", (int)r, buf); - - /* SEEK_END: 从文件尾往回 5 字节,读到最后 5 个字母 */ - pos = lseek(fd, -5, SEEK_END); - printf("lseek SEEK_END -5 -> %ld\n", (long)pos); - memset(buf, 0, sizeof(buf)); - r = read(fd, buf, 10); /* 最多只剩 5 字节 */ - printf("read %zd bytes: %.*s (expect: VWXYZ)\n", r, (int)r, buf); - - /* SEEK_SET 超过文件末尾:写一个字节,制造"空洞" */ - pos = lseek(fd, 30, SEEK_SET); - printf("lseek SEEK_SET 30 -> %ld\n", (long)pos); - if (write(fd, "!", 1) != 1) { perror("write hole"); return 3; } - - /* 重新读回空洞区域,[26,29] 应为 0x00 */ - lseek(fd, 26, SEEK_SET); - memset(buf, 0xAA, sizeof(buf)); - r = read(fd, buf, 5); - printf("read hole+1: %zd bytes, hole[0]=%02X hole[1]=%02X hole[2]=%02X " - "hole[3]=%02X last='%c' (expect: 00 00 00 00 '!')\n", - r, (unsigned char)buf[0], (unsigned char)buf[1], - (unsigned char)buf[2], (unsigned char)buf[3], buf[4]); - - close(fd); - unlink(path); - return 0; -} - -/* ------------------------------------------------------------------ */ -/* Test 3: 同一文件被两个 fd 同时打开(一读一写) */ -/* ------------------------------------------------------------------ */ -static int test_dual_open_same_file(const char *path) -{ - printf("\n=== test_dual_open_same_file ===\n"); - - /* 先创建并写入初始内容 */ - int fd_init = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644); - if (fd_init < 0) { perror("open init"); return 1; } - const char *init = "0123456789"; - if (write(fd_init, init, 10) != 10) { perror("write init"); return 2; } - close(fd_init); - - /* 用写句柄和读句柄分别打开同一文件 */ - int fd_w = open(path, O_WRONLY); - if (fd_w < 0) { perror("open W"); return 3; } - - int fd_r = open(path, O_RDONLY); - if (fd_r < 0) { perror("open R"); return 4; } - - printf("fd_w=%d fd_r=%d\n", fd_w, fd_r); - - /* 通过写句柄覆写前 5 字节 */ - if (write(fd_w, "HELLO", 5) != 5) { perror("write"); return 5; } - printf("write via fd_w: HELLO (overwrite first 5 bytes)\n"); - - /* 通过读句柄从头读回全部内容 */ - char buf[32] = {0}; - lseek(fd_r, 0, SEEK_SET); - ssize_t r = read(fd_r, buf, sizeof(buf)); - printf("read via fd_r: %zd bytes: %.*s (expect: HELLO56789)\n", - r, (int)r, buf); - - /* 继续用写句柄追加 */ - lseek(fd_w, 0, SEEK_END); - if (write(fd_w, "!!!", 3) != 3) { perror("write append"); return 6; } - printf("write append via fd_w: !!!\n"); - - /* 读句柄读追加部分 */ - lseek(fd_r, 10, SEEK_SET); - memset(buf, 0, sizeof(buf)); - r = read(fd_r, buf, sizeof(buf)); - printf("read appended via fd_r: %zd bytes: %.*s (expect: !!!)\n", - r, (int)r, buf); - - close(fd_w); - close(fd_r); - unlink(path); - return 0; -} - - -/* ------------------------------------------------------------------ */ -/* Test 4: 两个不同文件同时打开,分别读写 */ -/* ------------------------------------------------------------------ */ -static int test_two_files(const char *path_a, const char *path_b) -{ - printf("\n=== test_two_files ===\n"); - - int fd_a = open(path_a, O_CREAT | O_RDWR | O_TRUNC, 0644); - if (fd_a < 0) { perror("open A"); return 1; } - - int fd_b = open(path_b, O_CREAT | O_RDWR | O_TRUNC, 0644); - if (fd_b < 0) { perror("open B"); return 2; } - - printf("fd_a=%d fd_b=%d\n", fd_a, fd_b); - - /* 分别写入不同内容 */ - const char *data_a = "File-A: Hello World!"; - const char *data_b = "File-B: Goodbye World!"; - if (write(fd_a, data_a, strlen(data_a)) < 0) { perror("write A"); return 3; } - if (write(fd_b, data_b, strlen(data_b)) < 0) { perror("write B"); return 4; } - printf("write A: %s\n", data_a); - printf("write B: %s\n", data_b); - - /* 各自 seek 回头读取,验证内容互不干扰 */ - lseek(fd_a, 0, SEEK_SET); - lseek(fd_b, 0, SEEK_SET); - - char buf_a[64] = {0}; - char buf_b[64] = {0}; - ssize_t r_a = read(fd_a, buf_a, sizeof(buf_a)); - ssize_t r_b = read(fd_b, buf_b, sizeof(buf_b)); - - printf("read A: %zd bytes: %.*s\n", r_a, (int)r_a, buf_a); - printf("read B: %zd bytes: %.*s\n", r_b, (int)r_b, buf_b); - - int ok = 1; - if (strncmp(buf_a, data_a, strlen(data_a)) != 0) { - printf("FAIL: A content mismatch!\n"); ok = 0; - } - if (strncmp(buf_b, data_b, strlen(data_b)) != 0) { - printf("FAIL: B content mismatch!\n"); ok = 0; - } - if (ok) printf("PASS: both files read back correctly\n"); - - /* 交叉写:向 A 追加,向 B 中段覆写,再各自读回验证 */ - lseek(fd_a, 0, SEEK_END); - write(fd_a, "[A-TAIL]", 8); - - lseek(fd_b, 8, SEEK_SET); /* "File-B: " 之后 */ - write(fd_b, "Hi! ", 7); /* 覆写 "Goodbye" */ - - lseek(fd_a, 0, SEEK_SET); - lseek(fd_b, 0, SEEK_SET); - memset(buf_a, 0, sizeof(buf_a)); - memset(buf_b, 0, sizeof(buf_b)); - r_a = read(fd_a, buf_a, sizeof(buf_a)); - r_b = read(fd_b, buf_b, sizeof(buf_b)); - printf("after cross-write:\n"); - printf(" A: %.*s\n", (int)r_a, buf_a); - printf(" B: %.*s\n", (int)r_b, buf_b); - - close(fd_a); - close(fd_b); - unlink(path_a); - unlink(path_b); - return 0; -} - -static int test_write_file(const char *path) -{ - printf("\n=== test_write_file ===\n"); - - int fd = open(path, O_CREAT | O_RDWR, 0644); - if (fd < 0) { perror("open"); return 1; } - printf("open: %s fd=%d\n", path, fd); - - const char *msg = "Hello, zvfs!"; - ssize_t w = write(fd, msg, strlen(msg)); - if (w < 0) { perror("write"); close(fd); return 2; } - printf("write: %zd bytes: %s\n", w, msg); - - close(fd); - printf("close: ok\n"); - return 0; -} - -static int test_read_delete_file(const char *path) -{ - printf("\n=== test_read_delete_file ===\n"); - - int fd = open(path, O_RDONLY); - if (fd < 0) { perror("open"); return 1; } - printf("open: %s fd=%d\n", path, fd); - - char buf[256] = {0}; - ssize_t r = read(fd, buf, sizeof(buf)); - if (r < 0) { perror("read"); close(fd); return 2; } - printf("read: %zd bytes: %.*s\n", r, (int)r, buf); - - close(fd); - printf("close: ok\n"); - - if (unlink(path) != 0) { perror("unlink"); return 3; } - printf("unlink: ok\n"); - - return 0; -} - -/* ------------------------------------------------------------------ */ -/* main */ -/* ------------------------------------------------------------------ */ -int main(int argc, char **argv) -{ - int rc = 0; - - - char path[256]; - char path_a[256]; - char path_b[256]; - if(argc >= 2){ - sprintf(path, "%s/file.dat", argv[1]); - sprintf(path_a, "%s/file_a.dat", argv[1]); - sprintf(path_b, "%s/file_a.dat", argv[1]); - }else { - sprintf(path, "/tmp/test.dat"); - } - - if(argc == 3){ - int choose = atoi(argv[2]); - if(choose == 0){ - rc = test_write_file(path); - }else if(choose == 1){ - rc = test_read_delete_file(path); - } - return rc; - } - - // printf("argv[0]: %s\n", argv[0]); - // printf("argv[1]: %s\n", argv[1]); - // printf("path_a: %s\n", path_a); - // printf("path_b: %s\n", path_b); - - // rc |= test_basic(path); - // rc |= test_lseek(path); - // rc |= test_dual_open_same_file(path); - // rc |= test_two_files(path_a, path_b); - // rc |= test_single_file_perf(path); - // rc |= test_single_file_random_perf(path); - rc |= test_single_file_random_noaligned_perf(path); - - - printf("\n=== all tests %s ===\n", rc == 0 ? "PASSED" : "FAILED"); - return rc; -} \ No newline at end of file diff --git a/zvfs/zvfs.c b/zvfs/zvfs.c index b34c09b..ff34ac6 100755 --- a/zvfs/zvfs.c +++ b/zvfs/zvfs.c @@ -1,8 +1,9 @@ - -#include "zvfs.h" -#undef SPDK_DEBUGLOG -#define SPDK_DEBUGLOG(...) do {} while(0) + +#include "zvfs.h" +#include +#undef SPDK_DEBUGLOG +#define SPDK_DEBUGLOG(...) do {} while(0) #define ZVFS_BDEV "Nvme0n1" #ifndef ZVFS_BDEV @@ -38,11 +39,12 @@ void zvfs_do_write_io(zvfs_file_t *file); void zvfs_spdk_blob_write_preread_cb(void *arg, int bserrno); void zvfs_spdk_blob_write_resize_cb(void *arg, int bserrno); void zvfs_spdk_blob_write_sync_cb(void *arg, int bserrno); -void zvfs_spdk_blob_write_cb(void *arg, int bserrno); - -// close -void zvfs_do_close(void *arg); -void zvfs_spdk_blob_close_cb(void *arg, int bserrno); +void zvfs_spdk_blob_write_cb(void *arg, int bserrno); + +// close +void zvfs_do_close(void *arg); +void zvfs_spdk_blob_close_cb(void *arg, int bserrno); +void zvfs_spdk_blob_open_fail_close_cb(void *arg, int bserrno); // delete void zvfs_do_delete(void *arg); @@ -81,8 +83,8 @@ static void calc_lba_range(zvfs_file_t *file, } /* ---------- 确保 dma_buf 足够大 ---------- */ -static int ensure_dma_buf(zvfs_file_t *file, uint64_t need_bytes) -{ +static int ensure_dma_buf(zvfs_file_t *file, uint64_t need_bytes) +{ if (file->dma_buf && file->dma_buf_size >= need_bytes) return 0; if (file->dma_buf) spdk_free(file->dma_buf); @@ -91,14 +93,22 @@ static int ensure_dma_buf(zvfs_file_t *file, uint64_t need_bytes) SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); if (!file->dma_buf) { file->dma_buf_size = 0; return -1; } - file->dma_buf_size = need_bytes; - return 0; -} - -// waiter -bool waiter(struct spdk_thread *thread, spdk_msg_fn start_fn, void *ctx, bool *finished) { - - spdk_thread_send_msg(thread, start_fn, ctx); + file->dma_buf_size = need_bytes; + return 0; +} + +static inline int zvfs_err_from_bserrno(int bserrno) +{ + return bserrno != 0 ? bserrno : -EIO; +} + +// waiter +bool waiter(struct spdk_thread *thread, spdk_msg_fn start_fn, void *ctx, bool *finished) { + if (thread == NULL || start_fn == NULL || finished == NULL) { + return false; + } + + spdk_thread_send_msg(thread, start_fn, ctx); int waiter_count = 0; @@ -117,9 +127,13 @@ bool waiter(struct spdk_thread *thread, spdk_msg_fn start_fn, void *ctx, bool *f /* ================================================================== */ /* MOUNT */ /* ================================================================== */ -void zvfs_do_mount(void *arg) { - zvfs_t *fs = (zvfs_t*)arg; - struct spdk_bs_dev *bs_dev = NULL; +void zvfs_do_mount(void *arg) { + zvfs_t *fs = (zvfs_t*)arg; + struct spdk_bs_dev *bs_dev = NULL; + if (fs == NULL) { + return; + } + fs->op_errno = 0; // SPDK_DEBUGLOG("=== Listing ALL bdevs after JSON load ===\n"); // struct spdk_bdev *bdev = spdk_bdev_first(); @@ -132,36 +146,49 @@ void zvfs_do_mount(void *arg) { // SPDK_DEBUGLOG("---------------------------------\n"); // SPDK_DEBUGLOG("Trying to open: %s\n", ZVFS_BDEV); - int rc = spdk_bdev_create_bs_dev_ext(ZVFS_BDEV, zvfs_spdk_bdev_event_cb, NULL, &bs_dev); - if (rc != 0) { - SPDK_ERRLOG("=== bdev_open FAILED rc=%d (probably still not registered) ===\n", rc); - fs->finished = true; - spdk_app_stop(-1); - return; - } + int rc = spdk_bdev_create_bs_dev_ext(ZVFS_BDEV, zvfs_spdk_bdev_event_cb, NULL, &bs_dev); + if (rc != 0) { + SPDK_ERRLOG("=== bdev_open FAILED rc=%d (probably still not registered) ===\n", rc); + fs->op_errno = rc; + fs->finished = true; + return; + } + + fs->bs_dev = bs_dev; + fs->bs_dev_owned = true; + spdk_bs_load(bs_dev, NULL, zvfs_spdk_bs_load_cb, fs); + fs->bs_dev_owned = false; +} - fs->bs_dev = bs_dev; - spdk_bs_load(bs_dev, NULL, zvfs_spdk_bs_load_cb, fs); -} - -void zvfs_spdk_bs_load_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { - zvfs_t *fs = (zvfs_t*)arg; - - if (bserrno != 0) { - SPDK_DEBUGLOG("load failed, new device, re-create bs_dev and init\n"); +void zvfs_spdk_bs_load_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { + zvfs_t *fs = (zvfs_t*)arg; + if (fs == NULL) { + return; + } + if (bserrno == 0 && bs == NULL) { + fs->op_errno = -EIO; + fs->finished = true; + return; + } + + if (bserrno != 0) { + SPDK_DEBUGLOG("load failed, new device, re-create bs_dev and init\n"); struct spdk_bs_dev *bs_dev = NULL; - int rc = spdk_bdev_create_bs_dev_ext(ZVFS_BDEV, zvfs_spdk_bdev_event_cb, NULL, &bs_dev); - if (rc != 0) { - SPDK_ERRLOG("re-create bs_dev failed\n"); - spdk_app_stop(-1); - return; - } - fs->bs_dev = bs_dev; - - spdk_bs_init(fs->bs_dev, NULL, zvfs_spdk_bs_init_cb, fs); - return; - } + int rc = spdk_bdev_create_bs_dev_ext(ZVFS_BDEV, zvfs_spdk_bdev_event_cb, NULL, &bs_dev); + if (rc != 0) { + SPDK_ERRLOG("re-create bs_dev failed\n"); + fs->op_errno = rc; + fs->finished = true; + return; + } + fs->bs_dev = bs_dev; + fs->bs_dev_owned = true; + + spdk_bs_init(fs->bs_dev, NULL, zvfs_spdk_bs_init_cb, fs); + fs->bs_dev_owned = false; + return; + } uint64_t io_unit_size = spdk_bs_get_io_unit_size(bs); SPDK_DEBUGLOG("io_unit_size : %"PRIu64"\n", io_unit_size); @@ -169,17 +196,27 @@ void zvfs_spdk_bs_load_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { fs->io_unit_size = io_unit_size; fs->bs = bs; - fs->channel = spdk_bs_alloc_io_channel(fs->bs); - if (fs->channel == NULL) { - return ; - } + fs->channel = spdk_bs_alloc_io_channel(fs->bs); + if (fs->channel == NULL) { + fs->op_errno = -ENOMEM; + fs->finished = true; + return ; + } fs->finished = true; } -void zvfs_spdk_bs_init_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { - zvfs_t *fs = (zvfs_t*)arg; +void zvfs_spdk_bs_init_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { + zvfs_t *fs = (zvfs_t*)arg; + if (fs == NULL) { + return; + } + if (bserrno != 0 || bs == NULL) { + fs->op_errno = zvfs_err_from_bserrno(bserrno); + fs->finished = true; + return; + } uint64_t io_unit_size = spdk_bs_get_io_unit_size(bs); SPDK_DEBUGLOG("io_unit_size : %"PRIu64"\n", io_unit_size); @@ -187,10 +224,12 @@ void zvfs_spdk_bs_init_cb(void *arg, struct spdk_blob_store *bs, int bserrno) { fs->io_unit_size = io_unit_size; fs->bs = bs; - fs->channel = spdk_bs_alloc_io_channel(fs->bs); - if (fs->channel == NULL) { - return ; - } + fs->channel = spdk_bs_alloc_io_channel(fs->bs); + if (fs->channel == NULL) { + fs->op_errno = -ENOMEM; + fs->finished = true; + return ; + } fs->finished = true; } @@ -202,45 +241,76 @@ void zvfs_spdk_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *b /* ================================================================== */ /* CREATE */ /* ================================================================== */ -void zvfs_do_create(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - spdk_bs_create_blob(file->fs->bs, zvfs_spdk_bs_create_blob_cb, file); -} - -void zvfs_spdk_bs_create_blob_cb(void *arg, spdk_blob_id blobid, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - file->blob_id = blobid; - SPDK_DEBUGLOG("create blobid : %"PRIu64"\n", blobid); +void zvfs_do_create(void *arg) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL || file->fs == NULL || file->fs->bs == NULL) { + if (file != NULL) { + file->op_errno = -EINVAL; + file->finished = true; + } + return; + } + file->op_errno = 0; + + spdk_bs_create_blob(file->fs->bs, zvfs_spdk_bs_create_blob_cb, file); +} + +void zvfs_spdk_bs_create_blob_cb(void *arg, spdk_blob_id blobid, int bserrno) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL) { + return; + } + if (bserrno != 0) { + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } + + file->blob_id = blobid; + SPDK_DEBUGLOG("create blobid : %"PRIu64"\n", blobid); spdk_bs_open_blob(file->fs->bs, blobid, zvfs_spdk_bs_open_blob_cb, file); } -void zvfs_spdk_bs_open_blob_cb(void *arg, struct spdk_blob *blb, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("load blob error: %d\n", bserrno); - file->finished = true; - return; - } +void zvfs_spdk_bs_open_blob_cb(void *arg, struct spdk_blob *blb, int bserrno) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (bserrno) { + SPDK_ERRLOG("load blob error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } + if (blb == NULL) { + file->op_errno = -EIO; + file->finished = true; + return; + } file->blob = blb; - uint64_t free_cluster = spdk_bs_free_cluster_count(file->fs->bs); - if(free_cluster == 0){ - SPDK_ERRLOG("no free cluster: %d\n", bserrno); - file->finished = true; - return ; - } + uint64_t free_cluster = spdk_bs_free_cluster_count(file->fs->bs); + if(free_cluster == 0){ + SPDK_ERRLOG("no free cluster: %d\n", bserrno); + file->op_errno = -ENOSPC; + file->finished = true; + return ; + } spdk_blob_resize(blb, 1, zvfs_spdk_blob_resize_cb, file); } -void zvfs_spdk_blob_resize_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - uint64_t total = spdk_blob_get_num_clusters(file->blob); +void zvfs_spdk_blob_resize_cb(void *arg, int bserrno) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL) { + return; + } + if (bserrno != 0) { + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } + + uint64_t total = spdk_blob_get_num_clusters(file->blob); SPDK_DEBUGLOG("resize blob :%"PRIu64"\n", total); if (file->dirent) { @@ -250,15 +320,24 @@ void zvfs_spdk_blob_resize_cb(void *arg, int bserrno) { spdk_blob_sync_md(file->blob, zvfs_spdk_blob_sync_cb, file); } -void zvfs_spdk_blob_sync_cb(void *arg, int bserrno) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - file->dma_buf = spdk_malloc(BUFFER_SIZE, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); - if (file->dma_buf == NULL) { - SPDK_ERRLOG("spdk_malloc failed\n"); - file->finished = true; - return ; - } +void zvfs_spdk_blob_sync_cb(void *arg, int bserrno) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL) { + return; + } + if (bserrno != 0) { + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } + + file->dma_buf = spdk_malloc(BUFFER_SIZE, 0x1000, NULL, SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + if (file->dma_buf == NULL) { + SPDK_ERRLOG("spdk_malloc failed\n"); + file->op_errno = -ENOMEM; + spdk_blob_close(file->blob, zvfs_spdk_blob_open_fail_close_cb, file); + return ; + } file->dma_buf_size = BUFFER_SIZE; file->finished = true; @@ -267,29 +346,44 @@ void zvfs_spdk_blob_sync_cb(void *arg, int bserrno) { /* ================================================================== */ /* OPEN */ /* ================================================================== */ -void zvfs_do_open(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - spdk_bs_open_blob(file->fs->bs, file->blob_id, zvfs_spdk_bs_open_blob_cb2, file); -} +void zvfs_do_open(void *arg) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL || file->fs == NULL || file->fs->bs == NULL) { + if (file != NULL) { + file->op_errno = -EINVAL; + file->finished = true; + } + return; + } + file->op_errno = 0; + spdk_bs_open_blob(file->fs->bs, file->blob_id, zvfs_spdk_bs_open_blob_cb2, file); +} -void zvfs_spdk_bs_open_blob_cb2(void *arg, struct spdk_blob *blb, int bserrno) { +void zvfs_spdk_bs_open_blob_cb2(void *arg, struct spdk_blob *blb, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("load blob error: %d\n", bserrno); - file->finished = true; - return; - } + if (bserrno) { + SPDK_ERRLOG("load blob error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } + if (blb == NULL) { + file->op_errno = -EIO; + file->finished = true; + return; + } file->blob = blb; - file->dma_buf = spdk_malloc(BUFFER_SIZE, 0x1000, NULL, - SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); - if (!file->dma_buf) { - SPDK_ERRLOG("spdk_malloc failed\n"); - file->finished = true; - return; - } + file->dma_buf = spdk_malloc(BUFFER_SIZE, 0x1000, NULL, + SPDK_ENV_LCORE_ID_ANY, SPDK_MALLOC_DMA); + if (!file->dma_buf) { + SPDK_ERRLOG("spdk_malloc failed\n"); + file->op_errno = -ENOMEM; + spdk_blob_close(file->blob, zvfs_spdk_blob_open_fail_close_cb, file); + return; + } file->dma_buf_size = BUFFER_SIZE; file->finished = true; @@ -298,11 +392,27 @@ void zvfs_spdk_bs_open_blob_cb2(void *arg, struct spdk_blob *blb, int bserrno) { /* ================================================================== */ /* READ */ /* ================================================================== */ -void zvfs_do_read(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - - uint64_t io_unit = file->fs->io_unit_size; - uint64_t offset = file->current_offset; +void zvfs_do_read(void *arg) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL || file->fs == NULL || file->blob == NULL || file->fs->channel == NULL) { + if (file != NULL) { + file->op_errno = -EINVAL; + file->io_count = 0; + file->actual_io_count = 0; + file->finished = true; + } + return; + } + file->op_errno = 0; + + uint64_t io_unit = file->fs->io_unit_size; + if (io_unit == 0) { + file->op_errno = -EIO; + file->actual_io_count = 0; + file->finished = true; + return; + } + uint64_t offset = file->current_offset; uint64_t file_sz = file->dirent ? file->dirent->file_size : 0; /* EOF 检查 */ @@ -325,28 +435,31 @@ void zvfs_do_read(void *arg) { calc_lba_range(file, &lba, &page_off, &lba_count); uint64_t buf_need = lba_count * io_unit; - if (ensure_dma_buf(file, buf_need) != 0) { - SPDK_ERRLOG("ensure_dma_buf failed\n"); - file->actual_io_count = 0; - file->finished = true; - return; - } - - spdk_blob_io_read(file->blob, file->fs->channel, - file->dma_buf, - lba, lba_count, - zvfs_spdk_blob_read_cb, file); -} + if (ensure_dma_buf(file, buf_need) != 0) { + SPDK_ERRLOG("ensure_dma_buf failed\n"); + file->op_errno = -ENOMEM; + file->actual_io_count = 0; + file->finished = true; + return; + } + + spdk_blob_io_read(file->blob, file->fs->channel, + file->dma_buf, + lba, lba_count, + zvfs_spdk_blob_read_cb, file); +} void zvfs_spdk_blob_read_cb(void *arg, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("blob_read error: %d\n", bserrno); - file->io_count = 0; - file->finished = true; - return; - } + if (bserrno) { + SPDK_ERRLOG("blob_read error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->io_count = 0; + file->actual_io_count = 0; + file->finished = true; + return; + } file->current_offset += file->io_count; SPDK_DEBUGLOG("read complete, new offset=%" PRIu64 "\n", file->current_offset); @@ -373,19 +486,42 @@ void zvfs_spdk_blob_read_cb(void *arg, int bserrno) { */ /* Step 1 : 进入 write,先把覆盖范围内的扇区读出来(read-modify-write) */ -void zvfs_do_write(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; +void zvfs_do_write(void *arg) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL || file->fs == NULL || file->blob == NULL || file->fs->channel == NULL) { + if (file != NULL) { + file->op_errno = -EINVAL; + file->finished = true; + } + return; + } + if (file->write_staging_buf == NULL && file->io_count != 0) { + file->op_errno = -EINVAL; + file->finished = true; + return; + } + if (file->io_count == 0) { + file->finished = true; + return; + } + file->op_errno = 0; - uint64_t io_unit = file->fs->io_unit_size; - uint64_t lba, page_off, lba_count; + uint64_t io_unit = file->fs->io_unit_size; + if (io_unit == 0) { + file->op_errno = -EIO; + file->finished = true; + return; + } + uint64_t lba, page_off, lba_count; calc_lba_range(file, &lba, &page_off, &lba_count); uint64_t buf_need = lba_count * io_unit; - if (ensure_dma_buf(file, buf_need) != 0) { - SPDK_ERRLOG("ensure_dma_buf failed\n"); - file->finished = true; - return; - } + if (ensure_dma_buf(file, buf_need) != 0) { + SPDK_ERRLOG("ensure_dma_buf failed\n"); + file->op_errno = -ENOMEM; + file->finished = true; + return; + } file->aligned = (file->current_offset % io_unit == 0) && (file->io_count % io_unit == 0); @@ -414,20 +550,23 @@ void zvfs_do_write(void *arg) { * 这里我们把用户数据已经由上层调用者拷贝到了 write_staging_buf, */ /* 不管是否需要扩容,先 preread */ - spdk_blob_io_read(file->blob, file->fs->channel, - file->dma_buf, - lba, lba_count, - zvfs_spdk_blob_write_preread_cb, file); - } -} + spdk_blob_io_read(file->blob, file->fs->channel, + file->dma_buf, + lba, lba_count, + zvfs_spdk_blob_write_preread_cb, file); + } +} /* Step 2 : preread 完成,patch dma_buf,然后决定是否扩容 */ -void zvfs_spdk_blob_write_preread_cb(void *arg, int bserrno){ - zvfs_file_t *file = (zvfs_file_t *)arg; - - /* preread 失败也没关系——如果是新分配区域全零即可, - 这里仍然继续(SPDK 对未写过的区域返回全零)。*/ - if (bserrno) { +void zvfs_spdk_blob_write_preread_cb(void *arg, int bserrno){ + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL) { + return; + } + + /* preread 失败也没关系——如果是新分配区域全零即可, + 这里仍然继续(SPDK 对未写过的区域返回全零)。*/ + if (bserrno) { SPDK_DEBUGLOG("preread error %d (may be uninitialized, continue)\n", bserrno); } @@ -449,11 +588,12 @@ void zvfs_spdk_blob_write_preread_cb(void *arg, int bserrno){ if (need_clusters > cur_clusters) { uint64_t free_clusters = spdk_bs_free_cluster_count(file->fs->bs); - if (need_clusters - cur_clusters > free_clusters) { - SPDK_ERRLOG("no free clusters\n"); - file->finished = true; - return; - } + if (need_clusters - cur_clusters > free_clusters) { + SPDK_ERRLOG("no free clusters\n"); + file->op_errno = -ENOSPC; + file->finished = true; + return; + } spdk_blob_resize(file->blob, need_clusters, zvfs_spdk_blob_write_resize_cb, file); } else { @@ -465,11 +605,12 @@ void zvfs_spdk_blob_write_preread_cb(void *arg, int bserrno){ void zvfs_spdk_blob_write_resize_cb(void *arg, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("write resize error: %d\n", bserrno); - file->finished = true; - return; - } + if (bserrno) { + SPDK_ERRLOG("write resize error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } spdk_blob_sync_md(file->blob, zvfs_spdk_blob_write_sync_cb, file); } @@ -478,11 +619,12 @@ void zvfs_spdk_blob_write_resize_cb(void *arg, int bserrno) { void zvfs_spdk_blob_write_sync_cb(void *arg, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("write sync error: %d\n", bserrno); - file->finished = true; - return; - } + if (bserrno) { + SPDK_ERRLOG("write sync error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } if (file->dirent) { file->dirent->allocated_clusters = @@ -493,27 +635,33 @@ void zvfs_spdk_blob_write_sync_cb(void *arg, int bserrno) { } /* Step 4 : 实际写入(dma_buf 已经是 patch 后的整扇区数据) */ -void zvfs_do_write_io(zvfs_file_t *file) { - uint64_t io_unit_size = file->fs->io_unit_size; - uint64_t lba = file->current_offset / io_unit_size; +void zvfs_do_write_io(zvfs_file_t *file) { + uint64_t io_unit_size = file->fs->io_unit_size; + if (io_unit_size == 0) { + file->op_errno = -EIO; + file->finished = true; + return; + } + uint64_t lba = file->current_offset / io_unit_size; uint64_t page_off = file->current_offset % io_unit_size; uint64_t lba_count = (page_off + file->io_count + io_unit_size - 1) / io_unit_size; - spdk_blob_io_write(file->blob, file->fs->channel, - file->dma_buf, - lba, lba_count, - zvfs_spdk_blob_write_cb, file); -} + spdk_blob_io_write(file->blob, file->fs->channel, + file->dma_buf, + lba, lba_count, + zvfs_spdk_blob_write_cb, file); +} /* Step 5 : 写完成 */ void zvfs_spdk_blob_write_cb(void *arg, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("blob_write error: %d\n", bserrno); - file->finished = true; - return; - } + if (bserrno) { + SPDK_ERRLOG("blob_write error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + file->finished = true; + return; + } uint64_t new_end = file->current_offset + file->io_count; if (file->dirent && new_end > file->dirent->file_size) { @@ -529,20 +677,48 @@ void zvfs_spdk_blob_write_cb(void *arg, int bserrno) { /* ================================================================== */ /* CLOSE */ /* ================================================================== */ -void zvfs_do_close(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - spdk_blob_close(file->blob, zvfs_spdk_blob_close_cb, file); -} +void zvfs_do_close(void *arg) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL) { + return; + } + file->op_errno = 0; + if (file->blob == NULL) { + file->finished = true; + return; + } + spdk_blob_close(file->blob, zvfs_spdk_blob_close_cb, file); +} + +void zvfs_spdk_blob_open_fail_close_cb(void *arg, int bserrno) +{ + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL) { + return; + } + + if (bserrno) { + SPDK_ERRLOG("blob_close after open/create failure error: %d\n", bserrno); + if (file->op_errno == 0) { + file->op_errno = zvfs_err_from_bserrno(bserrno); + } + } + + file->blob = NULL; + file->finished = true; +} void zvfs_spdk_blob_close_cb(void *arg, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("blob_close error: %d\n", bserrno); - } - spdk_free(file->dma_buf); - file->dma_buf = NULL; - file->current_offset = 0; + if (bserrno) { + SPDK_ERRLOG("blob_close error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + } + spdk_free(file->dma_buf); + file->dma_buf = NULL; + file->blob = NULL; + file->current_offset = 0; file->finished = true; } @@ -550,17 +726,26 @@ void zvfs_spdk_blob_close_cb(void *arg, int bserrno) { /* ================================================================== */ /* DELETE */ /* ================================================================== */ -void zvfs_do_delete(void *arg) { - zvfs_file_t *file = (zvfs_file_t *)arg; - spdk_bs_delete_blob(file->fs->bs, file->blob_id, zvfs_spdk_blob_delete_cb, file); -} +void zvfs_do_delete(void *arg) { + zvfs_file_t *file = (zvfs_file_t *)arg; + if (file == NULL || file->fs == NULL || file->fs->bs == NULL) { + if (file != NULL) { + file->op_errno = -EINVAL; + file->finished = true; + } + return; + } + file->op_errno = 0; + spdk_bs_delete_blob(file->fs->bs, file->blob_id, zvfs_spdk_blob_delete_cb, file); +} void zvfs_spdk_blob_delete_cb(void *arg, int bserrno) { zvfs_file_t *file = (zvfs_file_t *)arg; - if (bserrno) { - SPDK_ERRLOG("blob_delete error: %d\n", bserrno); - } + if (bserrno) { + SPDK_ERRLOG("blob_delete error: %d\n", bserrno); + file->op_errno = zvfs_err_from_bserrno(bserrno); + } file->finished = true; } @@ -568,24 +753,44 @@ void zvfs_spdk_blob_delete_cb(void *arg, int bserrno) { /* ================================================================== */ /* UNMOUNT */ /* ================================================================== */ -void zvfs_do_umount(void *arg) { +void zvfs_do_umount(void *arg) { + + zvfs_t *fs = (zvfs_t *)arg; + if (fs == NULL) { + return; + } + fs->op_errno = 0; + + if (fs->bs) { + if (fs->channel) { + spdk_bs_free_io_channel(fs->channel); + fs->channel = NULL; + } + spdk_bs_unload(fs->bs, zvfs_spdk_bs_unload_cb, fs); + return; + } + if (fs->bs_dev && fs->bs_dev_owned) { + fs->bs_dev->destroy(fs->bs_dev); + fs->bs_dev = NULL; + fs->bs_dev_owned = false; + } + fs->finished = true; +} - zvfs_t *fs = (zvfs_t *)arg; - - if (fs->bs) { - if (fs->channel) { - spdk_bs_free_io_channel(fs->channel); - } - spdk_bs_unload(fs->bs, zvfs_spdk_bs_unload_cb, fs); - } - -} - -void zvfs_spdk_bs_unload_cb(void *arg, int bserrno) { - - zvfs_t *fs = (zvfs_t *)arg; - fs->finished = true; -} +void zvfs_spdk_bs_unload_cb(void *arg, int bserrno) { + + zvfs_t *fs = (zvfs_t *)arg; + if (fs == NULL) { + return; + } + if (bserrno != 0) { + fs->op_errno = zvfs_err_from_bserrno(bserrno); + } + fs->bs = NULL; + fs->bs_dev = NULL; + fs->bs_dev_owned = false; + fs->finished = true; +} // setup // zvfs.json @@ -609,11 +814,18 @@ int zvfs_env_setup(void) { return -1; } - global_thread = spdk_thread_create("global", NULL); - spdk_set_thread(global_thread); - - bool done = false; - waiter(global_thread, zvfs_json_load_fn, &done, &done); + global_thread = spdk_thread_create("global", NULL); + if (global_thread == NULL) { + SPDK_ERRLOG("spdk_thread_create failed\n"); + return -1; + } + spdk_set_thread(global_thread); + + bool done = false; + if (!waiter(global_thread, zvfs_json_load_fn, &done, &done) || !done) { + SPDK_ERRLOG("json load waiter timeout\n"); + return -1; + } int retry = 0; while (retry < 200) { // 最多等 20 秒 @@ -657,43 +869,67 @@ void json_app_load_done(int rc, void *ctx) { // filesystem // load -int zvfs_mount(struct zvfs_s *fs) { - fs->finished = false; - bool ok = waiter(global_thread, zvfs_do_mount, fs, &fs->finished); - if(!ok) SPDK_ERRLOG("mount result: ok=%d\n", ok); - return ok; -} -// unload -int zvfs_umount(struct zvfs_s *fs) { - fs->finished = false; - bool ok = waiter(global_thread, zvfs_do_umount, fs, &fs->finished); - if(!ok) SPDK_ERRLOG("umount result: ok=%d\n", ok); - return ok; -} -// file -// create -int zvfs_create(struct zvfs_file_s *file) { - file->finished = false; - bool ok = waiter(global_thread, zvfs_do_create, file, &file->finished); - if(!ok) SPDK_ERRLOG("create result: ok=%d\n", ok); - return ok; -} -// open -int zvfs_open(struct zvfs_file_s *file) { - file->finished = false; - bool ok = waiter(global_thread, zvfs_do_open, file, &file->finished); - if(!ok) SPDK_ERRLOG("open result: ok=%d\n", ok); - return ok; -} -// read -int zvfs_read(struct zvfs_file_s *file, uint8_t *buffer, size_t count) { - file->io_count = count; - file->actual_io_count = 0; - file->finished = false; - - bool ok = waiter(global_thread, zvfs_do_read, file, &file->finished); - if(!ok) SPDK_ERRLOG("read result: ok=%d\n", ok); - if (!ok || file->actual_io_count == 0) return -1; +int zvfs_mount(struct zvfs_s *fs) { + if (fs == NULL || global_thread == NULL) { + return 0; + } + fs->op_errno = 0; + fs->finished = false; + bool ok = waiter(global_thread, zvfs_do_mount, fs, &fs->finished); + if(!ok) SPDK_ERRLOG("mount result: ok=%d\n", ok); + return ok && fs->op_errno == 0; +} +// unload +int zvfs_umount(struct zvfs_s *fs) { + if (fs == NULL || global_thread == NULL) { + return 0; + } + fs->op_errno = 0; + fs->finished = false; + bool ok = waiter(global_thread, zvfs_do_umount, fs, &fs->finished); + if(!ok) SPDK_ERRLOG("umount result: ok=%d\n", ok); + return ok && fs->op_errno == 0; +} +// file +// create +int zvfs_create(struct zvfs_file_s *file) { + if (file == NULL || global_thread == NULL) { + return 0; + } + file->op_errno = 0; + file->finished = false; + bool ok = waiter(global_thread, zvfs_do_create, file, &file->finished); + if(!ok) SPDK_ERRLOG("create result: ok=%d\n", ok); + return ok && file->op_errno == 0; +} +// open +int zvfs_open(struct zvfs_file_s *file) { + if (file == NULL || global_thread == NULL) { + return 0; + } + file->op_errno = 0; + file->finished = false; + bool ok = waiter(global_thread, zvfs_do_open, file, &file->finished); + if(!ok) SPDK_ERRLOG("open result: ok=%d\n", ok); + return ok && file->op_errno == 0; +} +// read +int zvfs_read(struct zvfs_file_s *file, uint8_t *buffer, size_t count) { + if (file == NULL || buffer == NULL || global_thread == NULL) { + return -1; + } + if (count == 0) { + return 0; + } + file->op_errno = 0; + file->io_count = count; + file->actual_io_count = 0; + file->finished = false; + + bool ok = waiter(global_thread, zvfs_do_read, file, &file->finished); + if(!ok) SPDK_ERRLOG("read result: ok=%d\n", ok); + if (!ok || file->op_errno != 0) return -1; + if (file->actual_io_count == 0) return 0; /* * dma_buf 里存的是从 LBA 边界开始的整扇区数据, @@ -713,29 +949,41 @@ int zvfs_read(struct zvfs_file_s *file, uint8_t *buffer, size_t count) { return (int)file->actual_io_count; } // write -int zvfs_write(struct zvfs_file_s *file, const uint8_t *buffer, size_t count) { - file->io_count = count; - file->write_staging_buf = buffer; - file->finished = false; - - bool ok = waiter(global_thread, zvfs_do_write, file, &file->finished); - if(!ok) SPDK_ERRLOG("write result: ok=%d\n", ok); - return ok ? (int)count : -1; -} -// close -int zvfs_close(struct zvfs_file_s *file) { - file->finished = false; - bool ok = waiter(global_thread, zvfs_do_close, file, &file->finished); - if(!ok) SPDK_ERRLOG("close result: ok=%d\n", ok); - return ok; -} -// delete -int zvfs_delete(struct zvfs_file_s *file) { - file->finished = false; - bool ok = waiter(global_thread, zvfs_do_delete, file, &file->finished); - if(!ok) SPDK_ERRLOG("delete result: ok=%d\n", ok); - return ok; -} +int zvfs_write(struct zvfs_file_s *file, const uint8_t *buffer, size_t count) { + if (file == NULL || global_thread == NULL) { + return -1; + } + file->op_errno = 0; + file->io_count = count; + file->write_staging_buf = buffer; + file->finished = false; + + bool ok = waiter(global_thread, zvfs_do_write, file, &file->finished); + if(!ok) SPDK_ERRLOG("write result: ok=%d\n", ok); + return (ok && file->op_errno == 0) ? (int)count : -1; +} +// close +int zvfs_close(struct zvfs_file_s *file) { + if (file == NULL || global_thread == NULL) { + return 0; + } + file->op_errno = 0; + file->finished = false; + bool ok = waiter(global_thread, zvfs_do_close, file, &file->finished); + if(!ok) SPDK_ERRLOG("close result: ok=%d\n", ok); + return ok && file->op_errno == 0; +} +// delete +int zvfs_delete(struct zvfs_file_s *file) { + if (file == NULL || global_thread == NULL) { + return 0; + } + file->op_errno = 0; + file->finished = false; + bool ok = waiter(global_thread, zvfs_do_delete, file, &file->finished); + if(!ok) SPDK_ERRLOG("delete result: ok=%d\n", ok); + return ok && file->op_errno == 0; +} int main(int argc, char *argv[]) { diff --git a/zvfs/zvfs.h b/zvfs/zvfs.h old mode 100644 new mode 100755 index d805180..f244240 --- a/zvfs/zvfs.h +++ b/zvfs/zvfs.h @@ -48,6 +48,8 @@ typedef struct zvfs_s { uint32_t magic; // 0x5A563146 (ZV1F) uint32_t version; // 1 + bool bs_dev_owned; + int op_errno; bool finished; } zvfs_t; @@ -72,6 +74,7 @@ typedef struct zvfs_file_s { int aligned; size_t io_count; + int op_errno; bool finished; } zvfs_file_t; @@ -95,4 +98,4 @@ int close(int fd); int unlink(const char *name); off_t lseek(int fd, off_t offset, int whence); -#endif \ No newline at end of file +#endif diff --git a/zvfs/zvfs.json b/zvfs/zvfs.json old mode 100644 new mode 100755 diff --git a/zvfs/zvfs.old/zvfs.json b/zvfs/zvfs.old/zvfs.json old mode 100644 new mode 100755 diff --git a/zvfs/zvfs.old/zvfs.old.c b/zvfs/zvfs.old/zvfs.old.c old mode 100644 new mode 100755 diff --git a/zvfs/zvfs_hook.c b/zvfs/zvfs_hook.c old mode 100644 new mode 100755