---
title: "Filesystem"
description: "Kasumi 文件系统插件的虚拟根、方法、权限与边界。"
---


> 理解 Filesystem 插件的虚拟根、命令、权限、容量限制和双端实现边界。

## 模块边界

Filesystem 插件只读写 Kasumi 虚拟文件系统。它不暴露宿主真实路径、`content://` URI 或
可绕过虚拟根的第三个文件 API。目录枚举和元数据读取也经过同一路径守卫。

| 层级 | 入口 | 责任 |
| --- | --- | --- |
| 前端 API | `frontend/src/api/fs.ts` | 参数守卫、Promise 和命令封装 |
| 协议清单 | `protocol/plugins.json` | `fs` 插件和七个命令归属 |
| Windows 插件 | `windows/src/plugins/ksm_fs_plugin.cpp` | 路由到 `KSMFileSystem` |
| Android 插件 | `KSMFileSystemPlugin.kt` | 路由到 `KSMFileSystem` |

## 平台与权限

| 命令 | Windows | Android | 权限 | 返回 |
| --- | --- | --- | --- | --- |
| `fs.readText` | 支持 | 支持 | `fs.read` | `string` 或 `{ file }` |
| `fs.writeText` | 支持 | 支持 | `fs.write` | `{ written: true }` |
| `fs.exists` | 支持 | 支持 | `fs.read` | `{ exists }` |
| `fs.remove` | 支持 | 支持 | `fs.remove` | `{ removed }` |
| `fs.readDir` | 支持 | 支持 | `fs.read` | `{ entries }` |
| `fs.mkdir` | 支持 | 支持 | `fs.write` | `{ created }` |
| `fs.stat` | 支持 | 支持 | `fs.read` | `{ kind, size, modifiedAt }` |

## 虚拟根

| 路径 | 用途 | 读取 | 写入 / 删除 |
| --- | --- | --- | --- |
| `app://data/**` | 应用私有数据 | 是 | 是 |
| `app://cache/**` | 可清理缓存 | 是 | 是 |
| `app://tmp/**` | 临时文件 | 是 | 是 |
| `app://picked/**` | 用户选择的文件句柄 | 是 | 是 |
| `app://res/**` | 打包资源 | 是 | 否 |
| `app://assets/**` | 前端构建资源 | 是 | 否 |

`app://picked/<id>` 只接受文件选择器返回的单个 token，不能继续带子路径。业务代码不得自行
拼接 token，也不能把 picked 句柄当成普通目录。

## 方法与配置

宿主持久根由 `KSMFileSystem(data, cache, tmp)` 配置。`app://res` 与 `app://assets` 由宿主
资源提供器映射，不写入真实路径。

| 方法 | 参数 | 说明 |
| --- | --- | --- |
| `readText({ path })` | `path` | 读取 UTF-8 文本 |
| `writeText({ path, content })` | `path`、`content` | 写入或覆盖 UTF-8 文本 |
| `exists({ path })` | `path` | 查询文件或目录是否存在 |
| `remove({ path })` | `path` | 删除文件或空目录，不递归删除非空目录 |
| `readDir({ path })` | `path` | 按名称排序返回直接子项 |
| `mkdir({ path, recursive? })` | `path`、`recursive` | 创建目录，已存在时返回 `created: false` |
| `stat({ path })` | `path` | 返回文件类型、字节数和修改时间 |

`modifiedAt` 是 Unix epoch 毫秒；无法取得时返回 `null`。`readDir` 的每一项包含
`name`、`path`、`kind`、`size` 和 `modifiedAt`。

## 安全与资源限制

- 前端拒绝空字节、`..`、`~`、反斜杠、盘符样式片段和只读根写入。
- `app://picked` 的删除和读取只能通过宿主登记的 token。
- 单条桥接消息上限是 64 KiB；超过时 `readText` 返回临时文件句柄。
- 单文件读写上限是 16 MiB。超过上限返回 `IO_ERROR`。
- 符号链接不作为可读写虚拟文件返回。
- 前端守卫抛普通 `TypeError`；绕过封装直接调用时由宿主返回标准错误码。

## 示例

```ts
await KSM.fs.mkdir({
  path: 'app://data/notes',
  recursive: true
});

await KSM.fs.writeText({
  path: 'app://data/notes/entry.txt',
  content: 'hello'
});

const entries = await KSM.fs.readDir({
  path: 'app://data/notes'
});

const metadata = await KSM.fs.stat({
  path: 'app://data/notes/entry.txt'
});

const text = await KSM.fs.readText({
  path: metadata.kind === 'file' ? 'app://data/notes/entry.txt' : 'app://data'
});
console.info(entries.entries, text);
```

## 平台差异

Windows 基于 `std::filesystem`，Android 基于 `java.io.File` 与 `Os.lstat`。两端共享同一
虚拟根、路径规范化、64 KiB 内联阈值和 16 MiB 文件上限。Android 需要额外拒绝符号链接，
避免跨越应用沙箱目录。

## 错误行为

| 错误码 | 场景 |
| --- | --- |
| `INVALID_ARGS` | 路径、目录参数或 `recursive` 类型非法 |
| `PERMISSION_DENIED` | 路径越界、只读根写入或 picked token 无效 |
| `NOT_FOUND` | `stat` 目标不存在 |
| `IO_ERROR` | 文件过大、目录非空、磁盘失败或资源读取失败 |
| `NOT_SUPPORTED` | 宿主未注册文件系统服务 |
| `TIMEOUT` / `CANCELLED` | 请求超时或页面重载取消 |

## 排查路径

1. 确认路径从 `app://data`、`app://cache`、`app://tmp`、`app://res` 或 `app://assets` 开始。
2. 确认写入目标不在 `res`、`assets` 或 picked 目录的子路径中。
3. 检查路径是否包含 `..`、`~`、反斜杠或空字节。
4. 读取结果不是字符串时，按 `{ file }` 大文本句柄处理。
5. `remove` 删除非空目录失败时，先枚举并逐个删除子项。
6. 页面重载后不要复用旧请求和旧 picked token。
