|
12 | 12 | */ |
13 | 13 |
|
14 | 14 | import { KernelError } from "../kernel/types.js"; |
15 | | -import type { VirtualDirEntry, VirtualFileSystem, VirtualStat } from "../kernel/vfs.js"; |
| 15 | +import type { VirtualDirEntry, VirtualDirStatEntry, VirtualFileSystem, VirtualStat } from "../kernel/vfs.js"; |
16 | 16 | import type { FsBlockStore, FsMetadataStore, InodeMeta } from "./types.js"; |
17 | 17 |
|
18 | 18 | // --------------------------------------------------------------------------- |
@@ -1202,5 +1202,78 @@ export function createChunkedVfs(options: ChunkedVfsOptions): VirtualFileSystem |
1202 | 1202 | }; |
1203 | 1203 | } |
1204 | 1204 |
|
| 1205 | + // Add copy method. |
| 1206 | + vfs.copy = async (srcPath: string, dstPath: string): Promise<void> => { |
| 1207 | + const srcIno = await resolveIno(srcPath); |
| 1208 | + const srcMeta = await requireInode(srcIno); |
| 1209 | + if (srcMeta.type === "directory") { |
| 1210 | + throw new KernelError("EISDIR", `illegal operation on a directory: '${srcPath}'`); |
| 1211 | + } |
| 1212 | + |
| 1213 | + const { parentIno, name } = await ensureParents(dstPath); |
| 1214 | + const existingDstIno = await metadata.lookup(parentIno, name); |
| 1215 | + if (existingDstIno !== null) { |
| 1216 | + throw new KernelError("EEXIST", `file already exists: '${dstPath}'`); |
| 1217 | + } |
| 1218 | + |
| 1219 | + await metadata.transaction(async () => { |
| 1220 | + const dstIno = await metadata.createInode({ |
| 1221 | + type: "file", |
| 1222 | + mode: srcMeta.mode, |
| 1223 | + uid: srcMeta.uid, |
| 1224 | + gid: srcMeta.gid, |
| 1225 | + }); |
| 1226 | + await metadata.createDentry(parentIno, name, dstIno, "file"); |
| 1227 | + |
| 1228 | + if (srcMeta.storageMode === "inline") { |
| 1229 | + const content = srcMeta.inlineContent |
| 1230 | + ? new Uint8Array(srcMeta.inlineContent) |
| 1231 | + : new Uint8Array(0); |
| 1232 | + await metadata.updateInode(dstIno, { |
| 1233 | + nlink: 1, |
| 1234 | + size: srcMeta.size, |
| 1235 | + storageMode: "inline", |
| 1236 | + inlineContent: content, |
| 1237 | + }); |
| 1238 | + } else { |
| 1239 | + // Chunked: copy each block. |
| 1240 | + const chunkEntries = await metadata.getAllChunkKeys(srcIno); |
| 1241 | + for (const entry of chunkEntries) { |
| 1242 | + const newKey = blockKey(dstIno, entry.chunkIndex); |
| 1243 | + if (blocks.copy) { |
| 1244 | + await blocks.copy(entry.key, newKey); |
| 1245 | + } else { |
| 1246 | + const data = await blocks.read(entry.key); |
| 1247 | + await blocks.write(newKey, data); |
| 1248 | + } |
| 1249 | + await metadata.setChunkKey(dstIno, entry.chunkIndex, newKey); |
| 1250 | + } |
| 1251 | + await metadata.updateInode(dstIno, { |
| 1252 | + nlink: 1, |
| 1253 | + size: srcMeta.size, |
| 1254 | + storageMode: "chunked", |
| 1255 | + inlineContent: null, |
| 1256 | + }); |
| 1257 | + } |
| 1258 | + }); |
| 1259 | + }; |
| 1260 | + |
| 1261 | + // Add readDirStat method. |
| 1262 | + vfs.readDirStat = async (path: string): Promise<VirtualDirStatEntry[]> => { |
| 1263 | + const ino = await resolveIno(path); |
| 1264 | + const meta = await requireInode(ino); |
| 1265 | + if (meta.type !== "directory") { |
| 1266 | + throw new KernelError("ENOTDIR", `not a directory: '${path}'`); |
| 1267 | + } |
| 1268 | + const entries = await metadata.listDirWithStats(ino); |
| 1269 | + return entries.map((e) => ({ |
| 1270 | + name: e.name, |
| 1271 | + isDirectory: e.type === "directory", |
| 1272 | + isSymbolicLink: e.type === "symlink", |
| 1273 | + ino: e.ino, |
| 1274 | + stat: inodeMetaToStat(e.stat), |
| 1275 | + })); |
| 1276 | + }; |
| 1277 | + |
1205 | 1278 | return vfs; |
1206 | 1279 | } |
0 commit comments