@zenfs/core 2.4.2 → 2.4.3

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/COPYING.md CHANGED
@@ -1,18 +1,24 @@
1
1
  _This document is supplemental to the license._
2
2
 
3
- **This is a very easy requirement, please respect it.**
4
- Feel free to reach out if you have any concerns regarding licensing.
3
+ Feel free to reach out if you have any concerns regarding licensing. You can create a new discussion [here](https://github.com/orgs/zen-fs/discussions/new?category=q-a).
4
+
5
+ Regarding section 4(b) of version 3 of the LGPL, you are permitted to link to a copy of the LGPL and this document rather than including the full text of both.
6
+
7
+ #### Exception for web applications
5
8
 
6
- If you convey copies of ZenFS (including in bundles), you must meet section 4 and 5 of the license. This includes at a minimum:
9
+ You are permitted to convey the Combined Work without meeting the requirements of section 4(d) of version 3 of the LGPL if
7
10
 
8
- - disclosure that ZenFS is in use
9
- - linking to the GitHub or npm pages
10
- - referencing the license and copyright.
11
+ - a) the Combined Work is only accessed by users from other computers over a network (i.e. a web application or website), and
12
+ - b) you also provide the [Corresponding Source](https://www.gnu.org/licenses/gpl-3.0.html#:~:text=“Corresponding%20Source”) of The Library or an offer thereof.
13
+
14
+ This exception does not excuse or waive any other requirement of the LGPL.
15
+
16
+ The practical goal of this exception is to allow ZenFS to be used in modern web apps. For the avoidance of doubt, you are permitted to bundle and minify ZenFS in a web application. Linking to ZenFS' GitHub or npm page is sufficient to satisfy the above section (b) provided you do not modify ZenFS.
11
17
 
12
18
  For example, in a list of libraries you could have:
13
19
 
14
- > - [ZenFS](https://github.com/zen-fs/core), LGPL-3.0-or-later, Copyright © James Prevett and other ZenFS contributors
20
+ > - [ZenFS](https://github.com/zen-fs/core), Licensed under the [LGPL 3.0 or later](https://www.gnu.org/licenses/lgpl-3.0.html) and [COPYING.md](https://github.com/zen-fs/core/blob/main/COPYING.md), Copyright © James Prevett and other ZenFS contributors
15
21
 
16
- <br />
22
+ **This is a very easy requirement, please respect it.**
17
23
 
18
24
  Before v2.4.0, ZenFS was licensed under the MIT. This still means you need to include the copyright notice and license.
@@ -9,7 +9,7 @@ type OptionType = 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undef
9
9
  * @category Backends and Configuration
10
10
  */
11
11
  export type OptionsConfig<T> = {
12
- [K in keyof T]: {
12
+ [K in Exclude<keyof T, keyof SharedConfig>]: {
13
13
  /**
14
14
  * The type of the option. Can be a:
15
15
  * - string given by `typeof`
@@ -26,6 +26,7 @@ declare const MetadataBlock_base: import("memium/decorators").StructFromTypedArr
26
26
  */
27
27
  export declare class MetadataBlock extends MetadataBlock_base<ArrayBufferLike> {
28
28
  readonly ['constructor']: typeof MetadataBlock;
29
+ private static readonly lockIndex;
29
30
  /**
30
31
  * The crc32c checksum for the metadata block.
31
32
  * @privateRemarks Keep this first!
@@ -37,7 +38,6 @@ export declare class MetadataBlock extends MetadataBlock_base<ArrayBufferLike> {
37
38
  accessor previous_offset: number;
38
39
  protected _previous?: MetadataBlock;
39
40
  get previous(): MetadataBlock | undefined;
40
- protected get offsetHex(): string;
41
41
  /** Metadata entries. */
42
42
  accessor items: ArrayOf<MetadataEntry>;
43
43
  toString(long?: boolean): string;
@@ -86,7 +86,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
86
86
  });
87
87
  // SPDX-License-Identifier: LGPL-3.0-or-later
88
88
  import { withErrno } from 'kerium';
89
- import { alert, crit, err, warn } from 'kerium/log';
89
+ import { alert, crit, debug, err, warn } from 'kerium/log';
90
90
  import { array, offsetof, sizeof } from 'memium';
91
91
  import { $from, field, struct, types as t } from 'memium/decorators';
92
92
  import { BufferView } from 'utilium/buffer.js';
@@ -95,6 +95,7 @@ import { decodeUUID, encodeUUID } from 'utilium/string.js';
95
95
  import { _inode_version, Inode } from '../internal/inode.js';
96
96
  import { StoreFS } from './store/fs.js';
97
97
  import { SyncMapTransaction } from './store/map.js';
98
+ const hex = (value) => '0x' + value.toString(16).padStart(8, '0');
98
99
  // eslint-disable-next-line @typescript-eslint/unbound-method
99
100
  const { format } = new Intl.NumberFormat('en-US', {
100
101
  notation: 'compact',
@@ -155,7 +156,7 @@ let MetadataEntry = (() => {
155
156
  get size() { return this.#size_accessor_storage; }
156
157
  set size(value) { this.#size_accessor_storage = value; }
157
158
  toString() {
158
- return `<MetadataEntry @ 0x${this.byteOffset.toString(16).padStart(8, '0')}>`;
159
+ return `<MetadataEntry @ ${hex(this.byteOffset)}>`;
159
160
  }
160
161
  constructor() {
161
162
  super(...arguments);
@@ -216,8 +217,8 @@ let MetadataBlock = (() => {
216
217
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
217
218
  MetadataBlock = _classThis = _classDescriptor.value;
218
219
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
219
- __runInitializers(_classThis, _classExtraInitializers);
220
220
  }
221
+ static lockIndex = offsetof(MetadataBlock, 'locked') / Int32Array.BYTES_PER_ELEMENT;
221
222
  #checksum_accessor_storage = __runInitializers(this, _checksum_initializers, void 0);
222
223
  /**
223
224
  * The crc32c checksum for the metadata block.
@@ -240,27 +241,24 @@ let MetadataBlock = (() => {
240
241
  this._previous ??= new MetadataBlock(this.buffer, this.previous_offset);
241
242
  return this._previous;
242
243
  }
243
- get offsetHex() {
244
- return '0x' + this.byteOffset.toString(16).padStart(8, '0');
245
- }
246
244
  #items_accessor_storage = __runInitializers(this, _items_initializers, void 0);
247
245
  /** Metadata entries. */
248
246
  get items() { return this.#items_accessor_storage; }
249
247
  set items(value) { this.#items_accessor_storage = value; }
250
248
  toString(long = false) {
251
249
  if (!long)
252
- return `<MetadataBlock @ ${this.offsetHex}>`;
250
+ return `<MetadataBlock @ ${hex(this.byteOffset)}>`;
253
251
  let text = [
254
- `---- Metadata block at ${this.offsetHex} ----`,
255
- `Checksum: 0x${this.checksum.toString(16).padStart(8, '0')}`,
252
+ `---- Metadata block at ${hex(this.byteOffset)} ----`,
253
+ `Checksum: ${hex(this.checksum)}`,
256
254
  `Last updated: ${new Date(Number(this.timestamp)).toLocaleString()}`,
257
- `Previous block: 0x${this.previous_offset.toString(16).padStart(8, '0')}`,
255
+ `Previous block: ${hex(this.previous_offset)}`,
258
256
  'Entries:',
259
257
  ].join('\n');
260
258
  for (const entry of this.items) {
261
259
  if (!entry.offset)
262
260
  continue;
263
- text += `\n\t0x${entry.id.toString(16).padStart(8, '0')}: ${format(entry.size).padStart(5)} at 0x${entry.offset.toString(16).padStart(8, '0')}`;
261
+ text += `\n\t${hex(entry.id)}: ${format(entry.size).padStart(5)} at ${hex(entry.offset)}`;
264
262
  }
265
263
  return text;
266
264
  }
@@ -276,28 +274,26 @@ let MetadataBlock = (() => {
276
274
  */
277
275
  waitUnlocked(depth = 0) {
278
276
  if (depth > max_lock_attempts)
279
- throw crit(withErrno('EBUSY', `sbfs: exceeded max attempts waiting for metadata block at ${this.offsetHex} to be unlocked`));
280
- const i = this.length - 1;
281
- if (!Atomics.load(this, i))
277
+ throw crit(withErrno('EBUSY', `sbfs: exceeded max attempts waiting for metadata block at ${hex(this.byteOffset)} to be unlocked`));
278
+ if (!Atomics.load(this, MetadataBlock.lockIndex))
282
279
  return;
283
- switch (Atomics.wait(this, i, 1)) {
280
+ switch (Atomics.wait(this, MetadataBlock.lockIndex, 1)) {
284
281
  case 'ok':
285
282
  break;
286
283
  case 'not-equal':
287
284
  depth++;
288
- err(`sbfs: waiting for metadata block at ${this.offsetHex} to be unlocked (${depth}/${max_lock_attempts})`);
285
+ err(`sbfs: waiting for metadata block at ${hex(this.byteOffset)} to be unlocked (${depth}/${max_lock_attempts})`);
289
286
  return this.waitUnlocked(depth);
290
287
  case 'timed-out':
291
- throw crit(withErrno('EBUSY', `sbfs: timed out waiting for metadata block at ${this.offsetHex} to be unlocked`));
288
+ throw crit(withErrno('EBUSY', `sbfs: timed out waiting for metadata block at ${hex(this.byteOffset)} to be unlocked`));
292
289
  }
293
290
  }
294
291
  lock() {
295
292
  this.waitUnlocked();
296
- const i = offsetof(this, 'locked');
297
- Atomics.store(this, i, 1);
293
+ Atomics.store(this, MetadataBlock.lockIndex, 1);
298
294
  const release = () => {
299
- Atomics.store(this, i, 0);
300
- Atomics.notify(this, i, 1);
295
+ Atomics.store(this, MetadataBlock.lockIndex, 0);
296
+ Atomics.notify(this, MetadataBlock.lockIndex, 1);
301
297
  };
302
298
  release[Symbol.dispose] = release;
303
299
  return release;
@@ -306,6 +302,9 @@ let MetadataBlock = (() => {
306
302
  super(...arguments);
307
303
  __runInitializers(this, _locked_extraInitializers);
308
304
  }
305
+ static {
306
+ __runInitializers(_classThis, _classExtraInitializers);
307
+ }
309
308
  };
310
309
  return MetadataBlock = _classThis;
311
310
  })();
@@ -425,7 +424,7 @@ let SuperBlock = (() => {
425
424
  throw crit(withErrno('EIO', 'sbfs: checksum mismatch for super block'));
426
425
  this.metadata = new MetadataBlock(this.buffer, this.metadata_offset);
427
426
  if (this.metadata.checksum !== checksum(this.metadata))
428
- throw crit(withErrno('EIO', `sbfs: checksum mismatch for metadata block (saved ${this.metadata.checksum.toString(16).padStart(8, '0')}, computed ${checksum(this.metadata).toString(16).padStart(8, '0')})`));
427
+ throw crit(withErrno('EIO', `sbfs: checksum mismatch for metadata block (saved ${hex(this.metadata.checksum)}, computed ${hex(checksum(this.metadata))})`));
429
428
  if (this.inode_format != _inode_version)
430
429
  throw crit(withErrno('EIO', 'sbfs: inode format mismatch'));
431
430
  if (this.metadata_block_size != sizeof(MetadataBlock))
@@ -506,6 +505,7 @@ let SuperBlock = (() => {
506
505
  this.metadata_offset = metadata.byteOffset;
507
506
  _update(metadata);
508
507
  _update(this);
508
+ debug(`sbfs: rotated metadata block at ${hex(metadata.previous_offset)} with new block at ${hex(offset)}`);
509
509
  return metadata;
510
510
  }
511
511
  /**
@@ -541,7 +541,10 @@ export { SuperBlock };
541
541
  * Note we don't include the checksum when computing a new one.
542
542
  */
543
543
  function checksum(value) {
544
- return crc32c(new Uint8Array(value.buffer, value.byteOffset + 4, sizeof(value) - 4));
544
+ let length = sizeof(value) - 4;
545
+ if (value instanceof MetadataBlock)
546
+ length -= Int32Array.BYTES_PER_ELEMENT;
547
+ return crc32c(new Uint8Array(value.buffer, value.byteOffset + 4, length));
545
548
  }
546
549
  /**
547
550
  * Update a block's checksum and timestamp.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenfs/core",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "A filesystem, anywhere",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -1,8 +1,9 @@
1
1
  // SPDX-License-Identifier: LGPL-3.0-or-later
2
2
  import assert from 'node:assert';
3
+ import { randomBytes } from 'node:crypto';
3
4
  import { suite, test } from 'node:test';
4
5
  import { Worker } from 'worker_threads';
5
- import { fs, mount, resolveMountConfig, SingleBuffer } from '@zenfs/core';
6
+ import { fs, mount, resolveMountConfig, SingleBuffer, vfs } from '@zenfs/core';
6
7
  import { setupLogs } from '../logs.js';
7
8
 
8
9
  setupLogs();
@@ -51,4 +52,50 @@ await suite('SingleBuffer', () => {
51
52
 
52
53
  assert(fs.existsSync('/shared/worker-file.ts'));
53
54
  });
55
+
56
+ test('reliability across varied file sizes', async () => {
57
+ const mountPoint = '/sbfs-reliability';
58
+ const verifyMountPoint = '/sbfs-verify';
59
+ const buffer = new ArrayBuffer(0x400000);
60
+ const writable = await resolveMountConfig({ backend: SingleBuffer, buffer, label: 'reliability' });
61
+ mount(mountPoint, writable);
62
+
63
+ const filePath = `${mountPoint}/payload.bin`;
64
+ const growthSizes = [0, 1, 17, 512, 8192, 65535, 262144, 524288];
65
+ const shrinkSizes = [262144, 4096, 128, 0];
66
+
67
+ const verifySnapshot = async (expected: Buffer, size: number) => {
68
+ mount(verifyMountPoint, writable);
69
+ try {
70
+ const reopened = fs.readFileSync(`${verifyMountPoint}/payload.bin`);
71
+ assert.strictEqual(reopened.byteLength, size, `snapshot size mismatch for ${size} bytes`);
72
+ assert.deepStrictEqual(reopened, expected, `snapshot content mismatch for ${size} bytes`);
73
+ } finally {
74
+ vfs.umount(verifyMountPoint);
75
+ }
76
+ };
77
+
78
+ try {
79
+ for (const size of growthSizes) {
80
+ const payload = size ? randomBytes(size) : Buffer.alloc(0);
81
+ fs.writeFileSync(filePath, payload);
82
+ const direct = fs.readFileSync(filePath);
83
+ assert.strictEqual(direct.byteLength, size, `direct size mismatch for ${size} bytes`);
84
+ assert.deepStrictEqual(direct, payload, `direct content mismatch for ${size} bytes`);
85
+ await verifySnapshot(direct, size);
86
+ }
87
+
88
+ for (const size of shrinkSizes) {
89
+ const payload = size ? randomBytes(size) : Buffer.alloc(0);
90
+ fs.writeFileSync(filePath, payload);
91
+ const direct = fs.readFileSync(filePath);
92
+ assert.strictEqual(direct.byteLength, size, `direct size mismatch after shrink to ${size} bytes`);
93
+ assert.deepStrictEqual(direct, payload, `direct content mismatch after shrink to ${size} bytes`);
94
+ await verifySnapshot(direct, size);
95
+ }
96
+ } finally {
97
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
98
+ vfs.umount(mountPoint);
99
+ }
100
+ });
54
101
  });