@zenfs/core 2.4.1 → 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 +24 -0
- package/{readme.md → README.md} +16 -74
- package/dist/backends/backend.d.ts +1 -1
- package/dist/backends/fetch.js +1 -1
- package/dist/backends/memory.js +1 -1
- package/dist/backends/passthrough.d.ts +1 -2
- package/dist/backends/single_buffer.d.ts +1 -1
- package/dist/backends/single_buffer.js +27 -24
- package/dist/backends/store/fs.js +4 -4
- package/dist/config.js +15 -15
- package/dist/context.js +3 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +9 -4
- package/dist/internal/contexts.d.ts +5 -4
- package/dist/internal/devices.js +1 -1
- package/dist/internal/error.d.ts +11 -2
- package/dist/internal/error.js +38 -2
- package/dist/internal/file_index.js +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.js +2 -1
- package/dist/internal/index_fs.js +1 -1
- package/dist/internal/inode.d.ts +51 -2
- package/dist/internal/inode.js +18 -2
- package/dist/mixins/shared.js +1 -1
- package/dist/node/async.d.ts +278 -0
- package/dist/node/async.js +518 -0
- package/dist/node/compat.d.ts +4 -0
- package/dist/node/compat.js +6 -0
- package/dist/node/dir.d.ts +78 -0
- package/dist/node/dir.js +150 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +8 -0
- package/dist/{vfs → node}/promises.d.ts +10 -66
- package/dist/{vfs → node}/promises.js +141 -478
- package/dist/{vfs → node}/stats.d.ts +0 -4
- package/dist/{vfs → node}/stats.js +1 -16
- package/dist/{vfs → node}/streams.js +2 -2
- package/dist/node/sync.d.ts +252 -0
- package/dist/node/sync.js +682 -0
- package/dist/node/types.d.ts +21 -0
- package/dist/utils.d.ts +1 -7
- package/dist/utils.js +0 -6
- package/dist/vfs/acl.js +1 -1
- package/dist/vfs/async.d.ts +22 -278
- package/dist/vfs/async.js +212 -501
- package/dist/vfs/dir.d.ts +5 -82
- package/dist/vfs/dir.js +5 -233
- package/dist/vfs/file.d.ts +52 -13
- package/dist/vfs/file.js +167 -25
- package/dist/vfs/flags.js +1 -1
- package/dist/vfs/index.d.ts +2 -5
- package/dist/vfs/index.js +2 -5
- package/dist/vfs/shared.d.ts +25 -1
- package/dist/vfs/shared.js +6 -4
- package/dist/vfs/sync.d.ts +17 -245
- package/dist/vfs/sync.js +129 -773
- package/dist/vfs/watchers.d.ts +1 -1
- package/dist/vfs/watchers.js +2 -2
- package/dist/vfs/xattr.js +1 -1
- package/eslint.shared.js +1 -0
- package/package.json +7 -5
- package/scripts/make-index.js +5 -29
- package/scripts/test.js +59 -51
- package/tests/backend/fetch.test.ts +2 -2
- package/tests/backend/port.test.ts +2 -3
- package/tests/backend/single-buffer.test.ts +48 -1
- package/tests/common/casefold.test.ts +1 -1
- package/tests/common/context.test.ts +11 -4
- package/tests/common/devices.test.ts +3 -3
- package/tests/common/handle.test.ts +4 -3
- package/tests/common/inode.test.ts +2 -2
- package/tests/common/mounts.test.ts +1 -3
- package/tests/common/mutex.test.ts +1 -3
- package/tests/common/path.test.ts +2 -2
- package/tests/common/readline.test.ts +1 -1
- package/tests/common.ts +5 -4
- package/tests/fetch/fetch.ts +1 -1
- package/tests/fs/dir.test.ts +3 -43
- package/tests/fs/directory.test.ts +4 -4
- package/tests/fs/errors.test.ts +2 -2
- package/tests/fs/links.test.ts +1 -1
- package/tests/fs/permissions.test.ts +3 -3
- package/tests/fs/read.test.ts +1 -1
- package/tests/fs/scaling.test.ts +1 -1
- package/tests/fs/stat.test.ts +1 -2
- package/tests/fs/times.test.ts +1 -1
- package/tests/fs/watch.test.ts +3 -2
- package/tests/setup/context.ts +1 -2
- package/tests/setup/cow.ts +1 -1
- package/tests/setup/index.ts +2 -2
- package/tests/setup/port.ts +1 -1
- package/tests/setup/single-buffer.ts +1 -1
- package/tests/setup.ts +4 -3
- package/dist/vfs/types.d.ts +0 -24
- package/tests/assignment.ts +0 -21
- /package/dist/{vfs/constants.d.ts → constants.d.ts} +0 -0
- /package/dist/{vfs/constants.js → constants.js} +0 -0
- /package/dist/{readline.d.ts → node/readline.d.ts} +0 -0
- /package/dist/{readline.js → node/readline.js} +0 -0
- /package/dist/{vfs → node}/streams.d.ts +0 -0
- /package/dist/{vfs → node}/types.js +0 -0
package/COPYING.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
_This document is supplemental to the license._
|
|
2
|
+
|
|
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
|
|
8
|
+
|
|
9
|
+
You are permitted to convey the Combined Work without meeting the requirements of section 4(d) of version 3 of the LGPL if
|
|
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.
|
|
17
|
+
|
|
18
|
+
For example, in a list of libraries you could have:
|
|
19
|
+
|
|
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
|
|
21
|
+
|
|
22
|
+
**This is a very easy requirement, please respect it.**
|
|
23
|
+
|
|
24
|
+
Before v2.4.0, ZenFS was licensed under the MIT. This still means you need to include the copyright notice and license.
|
package/{readme.md → README.md}
RENAMED
|
@@ -19,7 +19,12 @@ ZenFS supports a number of other backends.
|
|
|
19
19
|
Many are provided as separate packages under `@zenfs`.
|
|
20
20
|
More backends can be defined by separate libraries by extending the `FileSystem` class and providing a `Backend` object.
|
|
21
21
|
|
|
22
|
-
You can find all of the packages available over on [NPM](https://www.npmjs.com/org/zenfs).
|
|
22
|
+
You can find all of the packages available over on [NPM](https://www.npmjs.com/org/zenfs). Below is a list of the backends included with some of them:
|
|
23
|
+
|
|
24
|
+
- @zenfs/archives: `Zip`, `Iso`
|
|
25
|
+
- @zenfs/cloud: `Dropbox`, `GoogleDrive`, `S3Bucket`
|
|
26
|
+
- @zenfs/dom: `WebAccess` (Web [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API)/OPFS), `IndexedDB`, `WebStorage` (`localStorage`/`sessionStorage`), `XML` (DOM elements)
|
|
27
|
+
- @zenfs/emscripten: `Emscripten` and a plugin for Emscripten's file system API
|
|
23
28
|
|
|
24
29
|
As an added bonus, all ZenFS backends support synchronous operations.
|
|
25
30
|
Additionally, all of the backends included with the core are cross-platform.
|
|
@@ -34,7 +39,7 @@ npm install @zenfs/core
|
|
|
34
39
|
|
|
35
40
|
If you're using ZenFS, especially for big projects, please consider supporting the project.
|
|
36
41
|
Thousands of hours have been dedicated to its development.
|
|
37
|
-
Your
|
|
42
|
+
Your financial support would go a long way toward improving ZenFS and its community.
|
|
38
43
|
|
|
39
44
|
## Usage
|
|
40
45
|
|
|
@@ -69,8 +74,8 @@ await configure({
|
|
|
69
74
|
'/mnt/zip': { backend: Zip, data: await res.arrayBuffer() },
|
|
70
75
|
'/tmp': InMemory,
|
|
71
76
|
'/home': IndexedDB,
|
|
72
|
-
}
|
|
73
|
-
};
|
|
77
|
+
},
|
|
78
|
+
});
|
|
74
79
|
```
|
|
75
80
|
|
|
76
81
|
Note that while you aren't required to use absolute paths for the keys of `mounts`, it is a good practice to do so.
|
|
@@ -140,7 +145,7 @@ await configure({
|
|
|
140
145
|
},
|
|
141
146
|
});
|
|
142
147
|
|
|
143
|
-
fs.mkdirSync('/mnt');
|
|
148
|
+
fs.mkdirSync('/mnt/zip', { recursive: true });
|
|
144
149
|
|
|
145
150
|
const res = await fetch('mydata.zip');
|
|
146
151
|
const zipfs = await resolveMountConfig({ backend: Zip, data: await res.arrayBuffer() });
|
|
@@ -156,82 +161,19 @@ fs.umount('/mnt/zip'); // finished using the zip
|
|
|
156
161
|
|
|
157
162
|
### Devices and device files
|
|
158
163
|
|
|
159
|
-
ZenFS includes support for device files. These are designed to follow Linux's device file behavior, for consistency and ease of use.
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
await configure({
|
|
163
|
-
mounts: {
|
|
164
|
-
/* ... */
|
|
165
|
-
},
|
|
166
|
-
addDevices: true,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
fs.writeFileSync('/dev/null', 'Some data to be discarded');
|
|
170
|
-
|
|
171
|
-
const randomData = new Uint8Array(100);
|
|
172
|
-
|
|
173
|
-
const random = fs.openSync('/dev/random', 'r');
|
|
174
|
-
fs.readSync(random, randomData);
|
|
175
|
-
fs.closeSync(random);
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
You can create your own devices by implementing a `DeviceDriver`. For example, the null device looks similar to this:
|
|
179
|
-
|
|
180
|
-
```ts
|
|
181
|
-
const customNullDevice = {
|
|
182
|
-
name: 'custom_null',
|
|
183
|
-
// only 1 can exist per DeviceFS
|
|
184
|
-
singleton: true,
|
|
185
|
-
// optional if false
|
|
186
|
-
isBuffered: false,
|
|
187
|
-
read() {
|
|
188
|
-
return 0;
|
|
189
|
-
},
|
|
190
|
-
write() {
|
|
191
|
-
return 0;
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Note the actual implementation's write is slightly more complicated since it adds to the file position. You can find more information on the docs.
|
|
197
|
-
|
|
198
|
-
Finally, if you'd like to use your custom device with the file system:
|
|
164
|
+
ZenFS includes support for device files. These are designed to follow Linux's device file behavior, for consistency and ease of use. Check out the [Devices and Device Drivers](https://zenfs.dev/core/documents/Devices_and_Device_Drivers) documentation for more information.
|
|
199
165
|
|
|
200
|
-
|
|
201
|
-
import { addDevice, fs } from '@zenfs/core';
|
|
202
|
-
|
|
203
|
-
addDevice(customNullDevice);
|
|
204
|
-
|
|
205
|
-
fs.writeFileSync('/dev/custom', 'This gets discarded.');
|
|
206
|
-
```
|
|
166
|
+
## Bundling
|
|
207
167
|
|
|
208
|
-
|
|
168
|
+
ZenFS exports a drop-in for Node's `fs` module, so you can use it for your bundler of preference using the default export.
|
|
209
169
|
|
|
210
|
-
|
|
170
|
+
> [!IMPORTANT]
|
|
171
|
+
> See [COPYING.md](./COPYING.md)
|
|
211
172
|
|
|
212
173
|
## Sponsors
|
|
213
174
|
|
|
214
|
-
A huge thank you to [
|
|
215
|
-
|
|
216
|
-
## Building
|
|
217
|
-
|
|
218
|
-
- Make sure you have Node and NPM installed. You must have Node v22 or newer.
|
|
219
|
-
- Install dependencies with `npm install`
|
|
220
|
-
- Build using `npx tsc` or `npm run build`
|
|
221
|
-
- You can find the built code in `dist`.
|
|
175
|
+
A huge thank you to [deco.cx](https://github.com/deco-cx) for sponsoring ZenFS and helping to make this possible.
|
|
222
176
|
|
|
223
177
|
## Contact and Support
|
|
224
178
|
|
|
225
179
|
You can reach out [on Discord](https://zenfs.dev/discord) or by emailing jp@zenfs.dev
|
|
226
|
-
|
|
227
|
-
### Testing
|
|
228
|
-
|
|
229
|
-
Run unit tests with:
|
|
230
|
-
|
|
231
|
-
- `npm test` to run all tests using the default configuration
|
|
232
|
-
- `npx zenfs-test -abc` to run the common tests and run the full FS suite against all included backends
|
|
233
|
-
- You can also run this command to test your own backends, the `--auto` (`-a`) flag will automatically detect any setup scripts matching `tests/setup/*` or `tests/setup-*.ts`. If you do, you'll need to include the `c8` dependency for coverage.
|
|
234
|
-
|
|
235
|
-
### BrowserFS Fork
|
|
236
|
-
|
|
237
|
-
ZenFS is a fork of [BrowserFS](https://github.com/jvilk/BrowserFS). If you are using ZenFS in a research paper, you may want to [cite BrowserFS](https://github.com/jvilk/BrowserFS#citing).
|
|
@@ -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`
|
package/dist/backends/fetch.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as requests from 'utilium/requests.js';
|
|
|
6
6
|
import { Index } from '../internal/file_index.js';
|
|
7
7
|
import { IndexFS } from '../internal/index_fs.js';
|
|
8
8
|
import { normalizePath } from '../utils.js';
|
|
9
|
-
import { S_IFREG } from '../
|
|
9
|
+
import { S_IFREG } from '../constants.js';
|
|
10
10
|
/** Parse and throw */
|
|
11
11
|
function parseError(error) {
|
|
12
12
|
if (!('tag' in error))
|
package/dist/backends/memory.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type * as fs from 'node:fs';
|
|
2
1
|
import type { CreationOptions, UsageInfo } from '../internal/filesystem.js';
|
|
3
2
|
import { FileSystem } from '../internal/filesystem.js';
|
|
4
3
|
import { type InodeLike } from '../internal/inode.js';
|
|
5
|
-
|
|
4
|
+
import type { NodeFS } from '../node/types.js';
|
|
6
5
|
/**
|
|
7
6
|
* Passthrough backend options
|
|
8
7
|
* @category Backends and Configuration
|
|
@@ -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,8 +86,8 @@ 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';
|
|
90
|
-
import { array, offsetof,
|
|
89
|
+
import { alert, crit, debug, err, warn } from 'kerium/log';
|
|
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';
|
|
93
93
|
import { crc32c } from 'utilium/checksum.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.
|
|
@@ -58,10 +58,10 @@ import { _throw, canary, encodeUTF8 } from 'utilium';
|
|
|
58
58
|
import { extendBuffer } from 'utilium/buffer.js';
|
|
59
59
|
import { Index } from '../../internal/file_index.js';
|
|
60
60
|
import { FileSystem } from '../../internal/filesystem.js';
|
|
61
|
-
import { Inode, isDirectory, rootIno } from '../../internal/inode.js';
|
|
61
|
+
import { Inode, isDirectory, isFile, rootIno } from '../../internal/inode.js';
|
|
62
62
|
import { basename, dirname, join, parse, relative } from '../../path.js';
|
|
63
63
|
import { decodeDirListing, encodeDirListing } from '../../utils.js';
|
|
64
|
-
import { S_IFDIR, S_IFREG, size_max } from '../../
|
|
64
|
+
import { S_IFDIR, S_IFREG, size_max } from '../../constants.js';
|
|
65
65
|
import { WrappedTransaction } from './store.js';
|
|
66
66
|
/**
|
|
67
67
|
* A file system which uses a `Store`
|
|
@@ -304,7 +304,7 @@ export class StoreFS extends FileSystem {
|
|
|
304
304
|
: decodeDirListing((await tx.get(newDirNode.data)) ?? _throw(withErrno('ENODATA')));
|
|
305
305
|
if (newDirList[_new.base]) {
|
|
306
306
|
const existing = new Inode((await tx.get(newDirList[_new.base])) ?? _throw(withErrno('ENOENT')));
|
|
307
|
-
if (!
|
|
307
|
+
if (!isFile(existing))
|
|
308
308
|
throw withErrno('EISDIR');
|
|
309
309
|
await tx.remove(existing.data);
|
|
310
310
|
await tx.remove(newDirList[_new.base]);
|
|
@@ -353,7 +353,7 @@ export class StoreFS extends FileSystem {
|
|
|
353
353
|
const newDirList = sameParent ? oldDirList : decodeDirListing(tx.getSync(newDirNode.data) ?? _throw(withErrno('ENODATA')));
|
|
354
354
|
if (newDirList[_new.base]) {
|
|
355
355
|
const existing = new Inode(tx.getSync(newDirList[_new.base]) ?? _throw(withErrno('ENOENT')));
|
|
356
|
-
if (!
|
|
356
|
+
if (!isFile(existing))
|
|
357
357
|
throw withErrno('EISDIR');
|
|
358
358
|
tx.removeSync(existing.data);
|
|
359
359
|
tx.removeSync(newDirList[_new.base]);
|
package/dist/config.js
CHANGED
|
@@ -4,9 +4,9 @@ import { defaultContext } from './internal/contexts.js';
|
|
|
4
4
|
import { createCredentials } from './internal/credentials.js';
|
|
5
5
|
import { DeviceFS } from './internal/devices.js';
|
|
6
6
|
import { FileSystem } from './internal/filesystem.js';
|
|
7
|
+
import { exists, mkdir, stat } from './node/promises.js';
|
|
7
8
|
import { _setAccessChecks } from './vfs/config.js';
|
|
8
|
-
import
|
|
9
|
-
import { mounts } from './vfs/shared.js';
|
|
9
|
+
import { mount, mounts, umount } from './vfs/shared.js';
|
|
10
10
|
/**
|
|
11
11
|
* Update the configuration of a file system.
|
|
12
12
|
* @category Backends and Configuration
|
|
@@ -69,8 +69,8 @@ export async function configureSingle(configuration) {
|
|
|
69
69
|
throw new TypeError('Invalid single mount point configuration');
|
|
70
70
|
}
|
|
71
71
|
const resolved = await resolveMountConfig(configuration);
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
umount('/');
|
|
73
|
+
mount('/', resolved);
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
76
|
* Like `fs.mount`, but it also creates missing directories.
|
|
@@ -78,19 +78,19 @@ export async function configureSingle(configuration) {
|
|
|
78
78
|
* This is implemented as a separate function to avoid a circular dependency between vfs/shared.ts and other vfs layer files.
|
|
79
79
|
* @internal
|
|
80
80
|
*/
|
|
81
|
-
async function
|
|
81
|
+
async function mountWithMkdir(path, fs) {
|
|
82
82
|
if (path == '/') {
|
|
83
|
-
|
|
83
|
+
mount(path, fs);
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
-
const stats = await
|
|
86
|
+
const stats = await stat(path).catch(() => null);
|
|
87
87
|
if (!stats) {
|
|
88
|
-
await
|
|
88
|
+
await mkdir(path, { recursive: true });
|
|
89
89
|
}
|
|
90
90
|
else if (!stats.isDirectory()) {
|
|
91
91
|
throw withErrno('ENOTDIR', 'Missing directory at mount point: ' + path);
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
mount(path, fs);
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
96
|
* @category Backends and Configuration
|
|
@@ -124,8 +124,8 @@ export async function configure(configuration) {
|
|
|
124
124
|
mountConfig.caseFold ??= configuration.caseFold;
|
|
125
125
|
}
|
|
126
126
|
if (point == '/')
|
|
127
|
-
|
|
128
|
-
await
|
|
127
|
+
umount('/');
|
|
128
|
+
await mountWithMkdir(point, await resolveMountConfig(mountConfig));
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
for (const fs of mounts.values()) {
|
|
@@ -135,17 +135,17 @@ export async function configure(configuration) {
|
|
|
135
135
|
const devfs = new DeviceFS();
|
|
136
136
|
devfs.addDefaults();
|
|
137
137
|
await devfs.ready();
|
|
138
|
-
await
|
|
138
|
+
await mountWithMkdir('/dev', devfs);
|
|
139
139
|
}
|
|
140
140
|
if (configuration.defaultDirectories) {
|
|
141
141
|
for (const dir of _defaultDirectories) {
|
|
142
|
-
if (await
|
|
143
|
-
const stats = await
|
|
142
|
+
if (await exists(dir)) {
|
|
143
|
+
const stats = await stat(dir);
|
|
144
144
|
if (!stats.isDirectory())
|
|
145
145
|
log.warn('Default directory exists but is not a directory: ' + dir);
|
|
146
146
|
}
|
|
147
147
|
else
|
|
148
|
-
await
|
|
148
|
+
await mkdir(dir);
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
}
|
package/dist/context.js
CHANGED
|
@@ -3,7 +3,8 @@ import { bindFunctions } from 'utilium';
|
|
|
3
3
|
import { defaultContext } from './internal/contexts.js';
|
|
4
4
|
import { createCredentials } from './internal/credentials.js';
|
|
5
5
|
import * as path from './path.js';
|
|
6
|
-
import * as fs from './
|
|
6
|
+
import * as fs from './node/index.js';
|
|
7
|
+
import * as xattr from './vfs/xattr.js';
|
|
7
8
|
// 0 is reserved for the global/default context
|
|
8
9
|
let _nextId = 1;
|
|
9
10
|
/**
|
|
@@ -33,7 +34,7 @@ export function bindContext({ root = this?.root || '/', pwd = this?.pwd || '/',
|
|
|
33
34
|
fs: {
|
|
34
35
|
...bindFunctions(fs, ctx),
|
|
35
36
|
promises: bindFunctions(fs.promises, ctx),
|
|
36
|
-
xattr: bindFunctions(
|
|
37
|
+
xattr: bindFunctions(xattr, ctx),
|
|
37
38
|
},
|
|
38
39
|
path: bindFunctions(path, ctx),
|
|
39
40
|
bind: (init) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* @zenfs/core — https://npmjs.com/package/@zenfs/core
|
|
3
|
+
* Copyright © James Prevett and other ZenFS contributors.
|
|
4
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
5
|
+
*/
|
|
1
6
|
export * from './backends/index.js';
|
|
2
7
|
export * from './config.js';
|
|
3
8
|
export * from './context.js';
|
|
4
9
|
export * from './internal/index.js';
|
|
5
10
|
export * from './mixins/index.js';
|
|
6
|
-
export * from './vfs/stats.js';
|
|
7
11
|
export * from './utils.js';
|
|
8
12
|
export { mounts } from './vfs/shared.js';
|
|
9
|
-
|
|
13
|
+
import * as fs from './node/compat.js';
|
|
14
|
+
/** @primaryExport */
|
|
10
15
|
export { fs };
|
|
11
|
-
import * as fs from './vfs/index.js';
|
|
12
16
|
export default fs;
|
|
17
|
+
export * from './node/compat.js';
|
|
18
|
+
export * as vfs from './vfs/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/*!
|
|
2
|
+
* @zenfs/core — https://npmjs.com/package/@zenfs/core
|
|
3
|
+
* Copyright © James Prevett and other ZenFS contributors.
|
|
4
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
5
|
+
*/
|
|
2
6
|
export * from './backends/index.js';
|
|
3
7
|
export * from './config.js';
|
|
4
8
|
export * from './context.js';
|
|
5
9
|
export * from './internal/index.js';
|
|
6
10
|
export * from './mixins/index.js';
|
|
7
|
-
export * from './vfs/stats.js';
|
|
8
11
|
export * from './utils.js';
|
|
9
12
|
export { mounts } from './vfs/shared.js';
|
|
10
|
-
|
|
13
|
+
import * as fs from './node/compat.js';
|
|
14
|
+
/** @primaryExport */
|
|
11
15
|
export { fs };
|
|
12
|
-
import * as fs from './vfs/index.js';
|
|
13
16
|
export default fs;
|
|
17
|
+
export * from './node/compat.js';
|
|
18
|
+
export * as vfs from './vfs/index.js';
|
|
14
19
|
import $pkg from '../package.json' with { type: 'json' };
|
|
15
20
|
globalThis.__zenfs__ = Object.assign(Object.create(fs), { _version: $pkg.version });
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Bound } from 'utilium';
|
|
2
|
+
import type * as fs from '../node/index.js';
|
|
2
3
|
import type * as path from '../path.js';
|
|
3
|
-
import type {
|
|
4
|
-
import type * as
|
|
4
|
+
import type { Handle } from '../vfs/file.js';
|
|
5
|
+
import type * as xattr from '../vfs/xattr.js';
|
|
5
6
|
import type { Credentials, CredentialsInit } from './credentials.js';
|
|
6
7
|
/**
|
|
7
8
|
* A context used for FS operations
|
|
@@ -21,7 +22,7 @@ export interface FSContext {
|
|
|
21
22
|
/** The credentials of the context, used for access checks */
|
|
22
23
|
readonly credentials: Credentials;
|
|
23
24
|
/** A map of open file descriptors to their handles */
|
|
24
|
-
descriptors: Map<number,
|
|
25
|
+
descriptors: Map<number, Handle>;
|
|
25
26
|
/** The parent context, if any. */
|
|
26
27
|
parent: V_Context;
|
|
27
28
|
/** The child contexts */
|
|
@@ -38,7 +39,7 @@ export type V_Context = void | null | (Partial<FSContext> & object);
|
|
|
38
39
|
export interface BoundContext extends FSContext {
|
|
39
40
|
fs: Bound<typeof fs, FSContext> & {
|
|
40
41
|
promises: Bound<typeof fs.promises, FSContext>;
|
|
41
|
-
xattr: Bound<typeof
|
|
42
|
+
xattr: Bound<typeof xattr, FSContext>;
|
|
42
43
|
};
|
|
43
44
|
/** Path functions, bound to the context */
|
|
44
45
|
path: Bound<typeof path, FSContext>;
|
package/dist/internal/devices.js
CHANGED
|
@@ -8,7 +8,7 @@ import { decodeUTF8, omit } from 'utilium';
|
|
|
8
8
|
import { InMemoryStore } from '../backends/memory.js';
|
|
9
9
|
import { StoreFS } from '../backends/store/fs.js';
|
|
10
10
|
import { basename, dirname } from '../path.js';
|
|
11
|
-
import { S_IFCHR } from '../
|
|
11
|
+
import { S_IFCHR } from '../constants.js';
|
|
12
12
|
import { Inode } from './inode.js';
|
|
13
13
|
/**
|
|
14
14
|
* A temporary file system that manages and interfaces with devices
|
package/dist/internal/error.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Exception, type ExceptionJSON } from 'kerium';
|
|
1
|
+
import { Exception, type ExceptionExtra, type ExceptionJSON } from 'kerium';
|
|
2
|
+
import type { FileSystem } from './filesystem.js';
|
|
2
3
|
/**
|
|
3
4
|
* @deprecated Use {@link ExceptionJSON} instead
|
|
4
5
|
* @category Internals
|
|
@@ -15,4 +16,12 @@ export declare const ErrnoError: typeof Exception;
|
|
|
15
16
|
*/
|
|
16
17
|
export type ErrnoError = Exception;
|
|
17
18
|
export declare function withPath<E extends Exception>(e: E, path: string): E;
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @internal @hidden
|
|
21
|
+
*/
|
|
22
|
+
export declare function wrap<const FS, const Prop extends keyof FS & string>(fs: FS, prop: Prop, path: string | ExceptionExtra, dest?: string): FS[Prop];
|
|
23
|
+
/**
|
|
24
|
+
* @internal
|
|
25
|
+
* Wraps an `fs` so that thrown errors aren't empty
|
|
26
|
+
*/
|
|
27
|
+
export declare function withExceptionContext<const FS extends FileSystem>(fs: FS, context: ExceptionExtra): FS;
|