@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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
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-
|
|
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
|
-
|
|
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 @
|
|
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.
|
|
250
|
+
return `<MetadataBlock @ ${hex(this.byteOffset)}>`;
|
|
253
251
|
let text = [
|
|
254
|
-
`---- Metadata block at ${this.
|
|
255
|
-
`Checksum:
|
|
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:
|
|
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\
|
|
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.
|
|
280
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
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
|
-
|
|
297
|
-
Atomics.store(this, i, 1);
|
|
293
|
+
Atomics.store(this, MetadataBlock.lockIndex, 1);
|
|
298
294
|
const release = () => {
|
|
299
|
-
Atomics.store(this,
|
|
300
|
-
Atomics.notify(this,
|
|
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
|
|
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
|
-
|
|
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,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
|
});
|