@componentor/fs 3.0.3 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -3
- package/dist/index.js +1547 -5
- package/dist/index.js.map +1 -1
- package/dist/workers/server.worker.js +88 -13
- package/dist/workers/server.worker.js.map +1 -1
- package/dist/workers/sync-relay.worker.js +103 -20
- package/dist/workers/sync-relay.worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -168,6 +168,13 @@ var VFSEngine = class {
|
|
|
168
168
|
this.mount();
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
+
/** Release the sync access handle (call on fatal error or shutdown) */
|
|
172
|
+
closeHandle() {
|
|
173
|
+
try {
|
|
174
|
+
this.handle?.close();
|
|
175
|
+
} catch (_) {
|
|
176
|
+
}
|
|
177
|
+
}
|
|
171
178
|
/** Format a fresh VFS */
|
|
172
179
|
format() {
|
|
173
180
|
const layout = calculateLayout(DEFAULT_INODE_COUNT, DEFAULT_BLOCK_SIZE, INITIAL_DATA_BLOCKS);
|
|
@@ -190,28 +197,78 @@ var VFSEngine = class {
|
|
|
190
197
|
this.createInode("/", INODE_TYPE.DIRECTORY, DEFAULT_DIR_MODE, 0);
|
|
191
198
|
this.handle.flush();
|
|
192
199
|
}
|
|
193
|
-
/** Mount an existing VFS from disk */
|
|
200
|
+
/** Mount an existing VFS from disk — validates superblock integrity */
|
|
194
201
|
mount() {
|
|
202
|
+
const fileSize = this.handle.getSize();
|
|
203
|
+
if (fileSize < SUPERBLOCK.SIZE) {
|
|
204
|
+
throw new Error(`Corrupt VFS: file too small (${fileSize} bytes, need at least ${SUPERBLOCK.SIZE})`);
|
|
205
|
+
}
|
|
195
206
|
this.handle.read(this.superblockBuf, { at: 0 });
|
|
196
207
|
const v = this.superblockView;
|
|
197
208
|
const magic = v.getUint32(SUPERBLOCK.MAGIC, true);
|
|
198
209
|
if (magic !== VFS_MAGIC) {
|
|
199
|
-
throw new Error(`
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
throw new Error(`Corrupt VFS: bad magic 0x${magic.toString(16)} (expected 0x${VFS_MAGIC.toString(16)})`);
|
|
211
|
+
}
|
|
212
|
+
const version = v.getUint32(SUPERBLOCK.VERSION, true);
|
|
213
|
+
if (version !== VFS_VERSION) {
|
|
214
|
+
throw new Error(`Corrupt VFS: unsupported version ${version} (expected ${VFS_VERSION})`);
|
|
215
|
+
}
|
|
216
|
+
const inodeCount = v.getUint32(SUPERBLOCK.INODE_COUNT, true);
|
|
217
|
+
const blockSize = v.getUint32(SUPERBLOCK.BLOCK_SIZE, true);
|
|
218
|
+
const totalBlocks = v.getUint32(SUPERBLOCK.TOTAL_BLOCKS, true);
|
|
219
|
+
const freeBlocks = v.getUint32(SUPERBLOCK.FREE_BLOCKS, true);
|
|
220
|
+
const inodeTableOffset = v.getFloat64(SUPERBLOCK.INODE_OFFSET, true);
|
|
221
|
+
const pathTableOffset = v.getFloat64(SUPERBLOCK.PATH_OFFSET, true);
|
|
222
|
+
const dataOffset = v.getFloat64(SUPERBLOCK.DATA_OFFSET, true);
|
|
223
|
+
const bitmapOffset = v.getFloat64(SUPERBLOCK.BITMAP_OFFSET, true);
|
|
224
|
+
const pathUsed = v.getUint32(SUPERBLOCK.PATH_USED, true);
|
|
225
|
+
if (blockSize === 0 || (blockSize & blockSize - 1) !== 0) {
|
|
226
|
+
throw new Error(`Corrupt VFS: invalid block size ${blockSize} (must be power of 2)`);
|
|
227
|
+
}
|
|
228
|
+
if (inodeCount === 0) {
|
|
229
|
+
throw new Error("Corrupt VFS: inode count is 0");
|
|
230
|
+
}
|
|
231
|
+
if (freeBlocks > totalBlocks) {
|
|
232
|
+
throw new Error(`Corrupt VFS: free blocks (${freeBlocks}) exceeds total blocks (${totalBlocks})`);
|
|
233
|
+
}
|
|
234
|
+
if (inodeTableOffset !== SUPERBLOCK.SIZE) {
|
|
235
|
+
throw new Error(`Corrupt VFS: inode table offset ${inodeTableOffset} (expected ${SUPERBLOCK.SIZE})`);
|
|
236
|
+
}
|
|
237
|
+
const expectedPathOffset = inodeTableOffset + inodeCount * INODE_SIZE;
|
|
238
|
+
if (pathTableOffset !== expectedPathOffset) {
|
|
239
|
+
throw new Error(`Corrupt VFS: path table offset ${pathTableOffset} (expected ${expectedPathOffset})`);
|
|
240
|
+
}
|
|
241
|
+
if (bitmapOffset <= pathTableOffset) {
|
|
242
|
+
throw new Error(`Corrupt VFS: bitmap offset ${bitmapOffset} must be after path table ${pathTableOffset}`);
|
|
243
|
+
}
|
|
244
|
+
if (dataOffset <= bitmapOffset) {
|
|
245
|
+
throw new Error(`Corrupt VFS: data offset ${dataOffset} must be after bitmap ${bitmapOffset}`);
|
|
246
|
+
}
|
|
247
|
+
const pathTableSize = bitmapOffset - pathTableOffset;
|
|
248
|
+
if (pathUsed > pathTableSize) {
|
|
249
|
+
throw new Error(`Corrupt VFS: path used (${pathUsed}) exceeds path table size (${pathTableSize})`);
|
|
250
|
+
}
|
|
251
|
+
const expectedMinSize = dataOffset + totalBlocks * blockSize;
|
|
252
|
+
if (fileSize < expectedMinSize) {
|
|
253
|
+
throw new Error(`Corrupt VFS: file size ${fileSize} too small for layout (need ${expectedMinSize})`);
|
|
254
|
+
}
|
|
255
|
+
this.inodeCount = inodeCount;
|
|
256
|
+
this.blockSize = blockSize;
|
|
257
|
+
this.totalBlocks = totalBlocks;
|
|
258
|
+
this.freeBlocks = freeBlocks;
|
|
259
|
+
this.inodeTableOffset = inodeTableOffset;
|
|
260
|
+
this.pathTableOffset = pathTableOffset;
|
|
261
|
+
this.dataOffset = dataOffset;
|
|
262
|
+
this.bitmapOffset = bitmapOffset;
|
|
263
|
+
this.pathTableUsed = pathUsed;
|
|
264
|
+
this.pathTableSize = pathTableSize;
|
|
211
265
|
const bitmapSize = Math.ceil(this.totalBlocks / 8);
|
|
212
266
|
this.bitmap = new Uint8Array(bitmapSize);
|
|
213
267
|
this.handle.read(this.bitmap, { at: this.bitmapOffset });
|
|
214
268
|
this.rebuildIndex();
|
|
269
|
+
if (!this.pathIndex.has("/")) {
|
|
270
|
+
throw new Error('Corrupt VFS: root directory "/" not found in inode table');
|
|
271
|
+
}
|
|
215
272
|
}
|
|
216
273
|
writeSuperblock() {
|
|
217
274
|
const v = this.superblockView;
|
|
@@ -1222,6 +1279,24 @@ var VFSEngine = class {
|
|
|
1222
1279
|
const data = inode.size > 0 ? this.readData(inode.firstBlock, inode.blockCount, inode.size) : new Uint8Array(0);
|
|
1223
1280
|
return { type: inode.type, data, mtime: inode.mtime };
|
|
1224
1281
|
}
|
|
1282
|
+
/** Export all files/dirs/symlinks from the VFS */
|
|
1283
|
+
exportAll() {
|
|
1284
|
+
const result = [];
|
|
1285
|
+
for (const [path, idx] of this.pathIndex) {
|
|
1286
|
+
const inode = this.readInode(idx);
|
|
1287
|
+
let data = null;
|
|
1288
|
+
if (inode.type === INODE_TYPE.FILE || inode.type === INODE_TYPE.SYMLINK) {
|
|
1289
|
+
data = inode.size > 0 ? this.readData(inode.firstBlock, inode.blockCount, inode.size) : new Uint8Array(0);
|
|
1290
|
+
}
|
|
1291
|
+
result.push({ path, type: inode.type, data, mode: inode.mode, mtime: inode.mtime });
|
|
1292
|
+
}
|
|
1293
|
+
result.sort((a, b) => {
|
|
1294
|
+
if (a.type === INODE_TYPE.DIRECTORY && b.type !== INODE_TYPE.DIRECTORY) return -1;
|
|
1295
|
+
if (a.type !== INODE_TYPE.DIRECTORY && b.type === INODE_TYPE.DIRECTORY) return 1;
|
|
1296
|
+
return a.path.localeCompare(b.path);
|
|
1297
|
+
});
|
|
1298
|
+
return result;
|
|
1299
|
+
}
|
|
1225
1300
|
flush() {
|
|
1226
1301
|
this.handle.flush();
|
|
1227
1302
|
}
|