@componentor/fs 3.0.3 → 3.0.5

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.
@@ -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(`Invalid VFS: bad magic 0x${magic.toString(16)}`);
200
- }
201
- this.inodeCount = v.getUint32(SUPERBLOCK.INODE_COUNT, true);
202
- this.blockSize = v.getUint32(SUPERBLOCK.BLOCK_SIZE, true);
203
- this.totalBlocks = v.getUint32(SUPERBLOCK.TOTAL_BLOCKS, true);
204
- this.freeBlocks = v.getUint32(SUPERBLOCK.FREE_BLOCKS, true);
205
- this.inodeTableOffset = v.getFloat64(SUPERBLOCK.INODE_OFFSET, true);
206
- this.pathTableOffset = v.getFloat64(SUPERBLOCK.PATH_OFFSET, true);
207
- this.dataOffset = v.getFloat64(SUPERBLOCK.DATA_OFFSET, true);
208
- this.bitmapOffset = v.getFloat64(SUPERBLOCK.BITMAP_OFFSET, true);
209
- this.pathTableUsed = v.getUint32(SUPERBLOCK.PATH_USED, true);
210
- this.pathTableSize = this.bitmapOffset - this.pathTableOffset;
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
  }