@zenfs/core 1.2.6 → 1.2.8
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/dist/backends/backend.d.ts +2 -1
- package/dist/backends/fetch.d.ts +10 -3
- package/dist/backends/fetch.js +9 -15
- package/dist/backends/memory.d.ts +0 -1
- package/dist/backends/memory.js +1 -5
- package/dist/backends/port/fs.d.ts +0 -2
- package/dist/backends/port/fs.js +0 -2
- package/dist/config.js +2 -2
- package/dist/devices.d.ts +46 -13
- package/dist/devices.js +23 -6
- package/dist/emulation/config.d.ts +4 -4
- package/dist/emulation/config.js +4 -4
- package/dist/emulation/shared.d.ts +1 -1
- package/dist/emulation/shared.js +2 -3
- package/dist/file.js +15 -13
- package/package.json +1 -1
- package/readme.md +4 -0
- package/src/backends/backend.ts +2 -1
- package/src/backends/fetch.ts +18 -18
- package/src/backends/memory.ts +1 -5
- package/src/backends/port/fs.ts +0 -2
- package/src/config.ts +2 -2
- package/src/devices.ts +65 -12
- package/src/emulation/config.ts +4 -4
- package/src/emulation/shared.ts +2 -3
- package/src/file.ts +19 -13
|
@@ -12,8 +12,9 @@ export type OptionsConfig<T> = {
|
|
|
12
12
|
type: OptionType | readonly OptionType[];
|
|
13
13
|
/**
|
|
14
14
|
* Description of the option. Used in error messages and documentation.
|
|
15
|
+
* @deprecated
|
|
15
16
|
*/
|
|
16
|
-
description
|
|
17
|
+
description?: string;
|
|
17
18
|
/**
|
|
18
19
|
* Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
|
|
19
20
|
*/
|
package/dist/backends/fetch.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ import type { IndexData } from './file_index.js';
|
|
|
6
6
|
* Configuration options for FetchFS.
|
|
7
7
|
*/
|
|
8
8
|
export interface FetchOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Options to pass through to fetch calls
|
|
11
|
+
*/
|
|
12
|
+
requestInit?: RequestInit;
|
|
9
13
|
/**
|
|
10
14
|
* URL to a file index as a JSON file or the file index object itself.
|
|
11
15
|
* Defaults to `index.json`.
|
|
@@ -37,8 +41,9 @@ export interface FetchOptions {
|
|
|
37
41
|
*/
|
|
38
42
|
export declare class FetchFS extends IndexFS {
|
|
39
43
|
readonly baseUrl: string;
|
|
44
|
+
readonly requestInit?: RequestInit;
|
|
40
45
|
ready(): Promise<void>;
|
|
41
|
-
constructor({ index, baseUrl }: FetchOptions);
|
|
46
|
+
constructor({ index, baseUrl, requestInit }: FetchOptions);
|
|
42
47
|
metadata(): FileSystemMetadata;
|
|
43
48
|
/**
|
|
44
49
|
* Preload the `path` into the index.
|
|
@@ -56,12 +61,14 @@ declare const _Fetch: {
|
|
|
56
61
|
readonly index: {
|
|
57
62
|
readonly type: readonly ["string", "object"];
|
|
58
63
|
readonly required: false;
|
|
59
|
-
readonly description: "URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.";
|
|
60
64
|
};
|
|
61
65
|
readonly baseUrl: {
|
|
62
66
|
readonly type: "string";
|
|
63
67
|
readonly required: false;
|
|
64
|
-
|
|
68
|
+
};
|
|
69
|
+
readonly requestInit: {
|
|
70
|
+
readonly type: "object";
|
|
71
|
+
readonly required: false;
|
|
65
72
|
};
|
|
66
73
|
};
|
|
67
74
|
readonly isAvailable: () => boolean;
|
package/dist/backends/fetch.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Errno, ErrnoError } from '../error.js';
|
|
2
2
|
import { IndexFS } from './file_index.js';
|
|
3
|
-
async function fetchFile(path, type) {
|
|
4
|
-
const response = await fetch(path).catch((e) => {
|
|
3
|
+
async function fetchFile(path, type, init) {
|
|
4
|
+
const response = await fetch(path, init).catch((e) => {
|
|
5
5
|
throw new ErrnoError(Errno.EIO, e.message, path);
|
|
6
6
|
});
|
|
7
7
|
if (!response.ok) {
|
|
@@ -57,13 +57,14 @@ export class FetchFS extends IndexFS {
|
|
|
57
57
|
await this.getData(path, stats);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
constructor({ index = 'index.json', baseUrl = '' }) {
|
|
60
|
+
constructor({ index = 'index.json', baseUrl = '', requestInit }) {
|
|
61
61
|
// prefix url must end in a directory separator.
|
|
62
62
|
if (baseUrl.at(-1) != '/') {
|
|
63
63
|
baseUrl += '/';
|
|
64
64
|
}
|
|
65
|
-
super(typeof index != 'string' ? index : fetchFile(baseUrl + index, 'json'));
|
|
65
|
+
super(typeof index != 'string' ? index : fetchFile(baseUrl + index, 'json', requestInit));
|
|
66
66
|
this.baseUrl = baseUrl;
|
|
67
|
+
this.requestInit = requestInit;
|
|
67
68
|
}
|
|
68
69
|
metadata() {
|
|
69
70
|
return {
|
|
@@ -93,7 +94,7 @@ export class FetchFS extends IndexFS {
|
|
|
93
94
|
if (stats.fileData) {
|
|
94
95
|
return stats.fileData;
|
|
95
96
|
}
|
|
96
|
-
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
97
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer', this.requestInit);
|
|
97
98
|
stats.fileData = data;
|
|
98
99
|
return data;
|
|
99
100
|
}
|
|
@@ -107,16 +108,9 @@ export class FetchFS extends IndexFS {
|
|
|
107
108
|
const _Fetch = {
|
|
108
109
|
name: 'Fetch',
|
|
109
110
|
options: {
|
|
110
|
-
index: {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.',
|
|
114
|
-
},
|
|
115
|
-
baseUrl: {
|
|
116
|
-
type: 'string',
|
|
117
|
-
required: false,
|
|
118
|
-
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
119
|
-
},
|
|
111
|
+
index: { type: ['string', 'object'], required: false },
|
|
112
|
+
baseUrl: { type: 'string', required: false },
|
|
113
|
+
requestInit: { type: 'object', required: false },
|
|
120
114
|
},
|
|
121
115
|
isAvailable() {
|
|
122
116
|
return typeof globalThis.fetch == 'function';
|
package/dist/backends/memory.js
CHANGED
|
@@ -26,11 +26,7 @@ const _InMemory = {
|
|
|
26
26
|
return true;
|
|
27
27
|
},
|
|
28
28
|
options: {
|
|
29
|
-
name: {
|
|
30
|
-
type: 'string',
|
|
31
|
-
required: false,
|
|
32
|
-
description: 'The name of the store',
|
|
33
|
-
},
|
|
29
|
+
name: { type: 'string', required: false },
|
|
34
30
|
},
|
|
35
31
|
create({ name }) {
|
|
36
32
|
const fs = new StoreFS(new InMemoryStore(name));
|
|
@@ -110,13 +110,11 @@ declare const _Port: {
|
|
|
110
110
|
port: {
|
|
111
111
|
type: "object";
|
|
112
112
|
required: true;
|
|
113
|
-
description: string;
|
|
114
113
|
validator(port: RPC.Port): void;
|
|
115
114
|
};
|
|
116
115
|
timeout: {
|
|
117
116
|
type: "number";
|
|
118
117
|
required: false;
|
|
119
|
-
description: string;
|
|
120
118
|
};
|
|
121
119
|
};
|
|
122
120
|
isAvailable(): boolean;
|
package/dist/backends/port/fs.js
CHANGED
|
@@ -214,7 +214,6 @@ const _Port = {
|
|
|
214
214
|
port: {
|
|
215
215
|
type: 'object',
|
|
216
216
|
required: true,
|
|
217
|
-
description: 'The target port that you want to connect to',
|
|
218
217
|
validator(port) {
|
|
219
218
|
// Check for a `postMessage` function.
|
|
220
219
|
if (typeof port?.postMessage != 'function') {
|
|
@@ -225,7 +224,6 @@ const _Port = {
|
|
|
225
224
|
timeout: {
|
|
226
225
|
type: 'number',
|
|
227
226
|
required: false,
|
|
228
|
-
description: 'How long to wait before the request times out',
|
|
229
227
|
},
|
|
230
228
|
},
|
|
231
229
|
isAvailable() {
|
package/dist/config.js
CHANGED
|
@@ -70,8 +70,8 @@ export async function configure(configuration) {
|
|
|
70
70
|
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
|
|
71
71
|
cache.setEnabled(configuration.cacheStats ?? false);
|
|
72
72
|
config.checkAccess = !configuration.disableAccessChecks;
|
|
73
|
-
config.
|
|
74
|
-
config.
|
|
73
|
+
config.updateOnRead = !configuration.disableUpdateOnRead;
|
|
74
|
+
config.syncImmediately = !configuration.onlySyncOnClose;
|
|
75
75
|
if (configuration.addDevices) {
|
|
76
76
|
const devfs = new DeviceFS();
|
|
77
77
|
devfs.createDevice('/null', nullDevice);
|
package/dist/devices.d.ts
CHANGED
|
@@ -8,9 +8,10 @@ import type { Ino } from './inode.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* A device
|
|
10
10
|
* @todo Maybe add major/minor number or some other device information, like a UUID?
|
|
11
|
-
* @
|
|
11
|
+
* @privateRemarks
|
|
12
|
+
* UUIDs were considered, however they don't make sense without an easy mechanism for persistance
|
|
12
13
|
*/
|
|
13
|
-
export interface Device {
|
|
14
|
+
export interface Device<TData = any> {
|
|
14
15
|
/**
|
|
15
16
|
* The device's driver
|
|
16
17
|
*/
|
|
@@ -19,34 +20,64 @@ export interface Device {
|
|
|
19
20
|
* Which inode the device is assigned
|
|
20
21
|
*/
|
|
21
22
|
ino: Ino;
|
|
23
|
+
/**
|
|
24
|
+
* Data associated with a device.
|
|
25
|
+
* This is meant to be used by device drivers.
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
data: TData;
|
|
29
|
+
/**
|
|
30
|
+
* Major device number
|
|
31
|
+
* @experimental
|
|
32
|
+
*/
|
|
33
|
+
major: number;
|
|
34
|
+
/**
|
|
35
|
+
* Minor device number
|
|
36
|
+
* @experimental
|
|
37
|
+
*/
|
|
38
|
+
minor: number;
|
|
22
39
|
}
|
|
23
40
|
/**
|
|
24
41
|
* A device driver
|
|
25
|
-
* @experimental
|
|
26
42
|
*/
|
|
27
|
-
export interface DeviceDriver {
|
|
43
|
+
export interface DeviceDriver<TData = any> {
|
|
28
44
|
/**
|
|
29
45
|
* The name of the device driver
|
|
30
46
|
*/
|
|
31
47
|
name: string;
|
|
32
48
|
/**
|
|
33
49
|
* Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
isBuffered?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Initializes a new device.
|
|
55
|
+
* @returns `Device.data`
|
|
56
|
+
* @experimental
|
|
34
57
|
*/
|
|
35
|
-
|
|
58
|
+
init?(ino: Ino): {
|
|
59
|
+
data?: TData;
|
|
60
|
+
minor?: number;
|
|
61
|
+
major?: number;
|
|
62
|
+
};
|
|
36
63
|
/**
|
|
37
64
|
* Synchronously read from the device
|
|
65
|
+
* @group File operations
|
|
38
66
|
*/
|
|
39
67
|
read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
40
68
|
/**
|
|
41
69
|
* Synchronously write to the device
|
|
70
|
+
* @group File operations
|
|
42
71
|
*/
|
|
43
72
|
write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
|
|
44
73
|
/**
|
|
45
74
|
* Sync the device
|
|
75
|
+
* @group File operations
|
|
46
76
|
*/
|
|
47
77
|
sync?(file: DeviceFile): void;
|
|
48
78
|
/**
|
|
49
79
|
* Close the device
|
|
80
|
+
* @group File operations
|
|
50
81
|
*/
|
|
51
82
|
close?(file: DeviceFile): void;
|
|
52
83
|
}
|
|
@@ -55,7 +86,6 @@ export interface DeviceDriver {
|
|
|
55
86
|
* This class only does some simple things:
|
|
56
87
|
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
57
88
|
* It is up to device drivers to implement the rest of the functionality.
|
|
58
|
-
* @experimental
|
|
59
89
|
*/
|
|
60
90
|
export declare class DeviceFile extends File {
|
|
61
91
|
fs: DeviceFS;
|
|
@@ -86,11 +116,14 @@ export declare class DeviceFile extends File {
|
|
|
86
116
|
_setTypeSync(): void;
|
|
87
117
|
}
|
|
88
118
|
/**
|
|
89
|
-
*
|
|
119
|
+
* A temporary file system that manages and interfaces with devices
|
|
90
120
|
*/
|
|
91
121
|
export declare class DeviceFS extends StoreFS<InMemoryStore> {
|
|
92
|
-
protected readonly devices: Map<string, Device
|
|
93
|
-
|
|
122
|
+
protected readonly devices: Map<string, Device<any>>;
|
|
123
|
+
/**
|
|
124
|
+
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
125
|
+
*/
|
|
126
|
+
createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>>;
|
|
94
127
|
constructor();
|
|
95
128
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
96
129
|
renameSync(oldPath: string, newPath: string): void;
|
|
@@ -150,9 +183,9 @@ export declare const randomDevice: DeviceDriver;
|
|
|
150
183
|
* @experimental
|
|
151
184
|
*/
|
|
152
185
|
declare const _default: {
|
|
153
|
-
null: DeviceDriver
|
|
154
|
-
zero: DeviceDriver
|
|
155
|
-
full: DeviceDriver
|
|
156
|
-
random: DeviceDriver
|
|
186
|
+
null: DeviceDriver<any>;
|
|
187
|
+
zero: DeviceDriver<any>;
|
|
188
|
+
full: DeviceDriver<any>;
|
|
189
|
+
random: DeviceDriver<any>;
|
|
157
190
|
};
|
|
158
191
|
export default _default;
|
package/dist/devices.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is a great resource: https://www.kernel.org/doc/html/latest/admin-guide/devices.html
|
|
3
|
+
*/
|
|
1
4
|
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
5
|
if (value !== null && value !== void 0) {
|
|
3
6
|
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
@@ -57,7 +60,6 @@ import { basename, dirname } from './emulation/path.js';
|
|
|
57
60
|
* This class only does some simple things:
|
|
58
61
|
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
59
62
|
* It is up to device drivers to implement the rest of the functionality.
|
|
60
|
-
* @experimental
|
|
61
63
|
*/
|
|
62
64
|
export class DeviceFile extends File {
|
|
63
65
|
constructor(fs, path, device) {
|
|
@@ -142,9 +144,12 @@ export class DeviceFile extends File {
|
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
145
|
-
*
|
|
147
|
+
* A temporary file system that manages and interfaces with devices
|
|
146
148
|
*/
|
|
147
149
|
export class DeviceFS extends StoreFS {
|
|
150
|
+
/**
|
|
151
|
+
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
152
|
+
*/
|
|
148
153
|
createDevice(path, driver) {
|
|
149
154
|
if (this.existsSync(path)) {
|
|
150
155
|
throw ErrnoError.With('EEXIST', path, 'mknod');
|
|
@@ -155,6 +160,10 @@ export class DeviceFS extends StoreFS {
|
|
|
155
160
|
const dev = {
|
|
156
161
|
driver,
|
|
157
162
|
ino,
|
|
163
|
+
data: {},
|
|
164
|
+
minor: 0,
|
|
165
|
+
major: 0,
|
|
166
|
+
...driver.init?.(ino),
|
|
158
167
|
};
|
|
159
168
|
this.devices.set(path, dev);
|
|
160
169
|
return dev;
|
|
@@ -332,7 +341,9 @@ function defaultWrite(file, buffer, offset, length) {
|
|
|
332
341
|
*/
|
|
333
342
|
export const nullDevice = {
|
|
334
343
|
name: 'null',
|
|
335
|
-
|
|
344
|
+
init() {
|
|
345
|
+
return { major: 1, minor: 3 };
|
|
346
|
+
},
|
|
336
347
|
read() {
|
|
337
348
|
return 0;
|
|
338
349
|
},
|
|
@@ -350,7 +361,9 @@ export const nullDevice = {
|
|
|
350
361
|
*/
|
|
351
362
|
export const zeroDevice = {
|
|
352
363
|
name: 'zero',
|
|
353
|
-
|
|
364
|
+
init() {
|
|
365
|
+
return { major: 1, minor: 5 };
|
|
366
|
+
},
|
|
354
367
|
read(file, buffer, offset = 0, length = buffer.byteLength) {
|
|
355
368
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
356
369
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -369,7 +382,9 @@ export const zeroDevice = {
|
|
|
369
382
|
*/
|
|
370
383
|
export const fullDevice = {
|
|
371
384
|
name: 'full',
|
|
372
|
-
|
|
385
|
+
init() {
|
|
386
|
+
return { major: 1, minor: 7 };
|
|
387
|
+
},
|
|
373
388
|
read(file, buffer, offset = 0, length = buffer.byteLength) {
|
|
374
389
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
375
390
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -390,7 +405,9 @@ export const fullDevice = {
|
|
|
390
405
|
*/
|
|
391
406
|
export const randomDevice = {
|
|
392
407
|
name: 'random',
|
|
393
|
-
|
|
408
|
+
init() {
|
|
409
|
+
return { major: 1, minor: 8 };
|
|
410
|
+
},
|
|
394
411
|
read(file, buffer, offset = 0, length = buffer.byteLength) {
|
|
395
412
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
396
413
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -4,13 +4,13 @@ export declare const config: {
|
|
|
4
4
|
*/
|
|
5
5
|
checkAccess: boolean;
|
|
6
6
|
/**
|
|
7
|
-
* Whether to
|
|
7
|
+
* Whether to mark a file as dirty after updating its `atime` when read from
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
updateOnRead: boolean;
|
|
10
10
|
/**
|
|
11
|
-
* Whether to immediately sync when files are
|
|
11
|
+
* Whether to immediately sync when files are changed
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
syncImmediately: boolean;
|
|
14
14
|
/**
|
|
15
15
|
* If a file's buffer is not large enough to store content when writing and the buffer can't be resized, reuse the buffer passed to write()
|
|
16
16
|
*/
|
package/dist/emulation/config.js
CHANGED
|
@@ -4,13 +4,13 @@ export const config = {
|
|
|
4
4
|
*/
|
|
5
5
|
checkAccess: true,
|
|
6
6
|
/**
|
|
7
|
-
* Whether to
|
|
7
|
+
* Whether to mark a file as dirty after updating its `atime` when read from
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
updateOnRead: true,
|
|
10
10
|
/**
|
|
11
|
-
* Whether to immediately sync when files are
|
|
11
|
+
* Whether to immediately sync when files are changed
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
syncImmediately: true,
|
|
14
14
|
/**
|
|
15
15
|
* If a file's buffer is not large enough to store content when writing and the buffer can't be resized, reuse the buffer passed to write()
|
|
16
16
|
*/
|
|
@@ -21,7 +21,7 @@ export declare function mount(mountPoint: string, fs: FileSystem): void;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function umount(mountPoint: string): void;
|
|
23
23
|
/**
|
|
24
|
-
* Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
|
|
24
|
+
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
|
25
25
|
*/
|
|
26
26
|
export declare function resolveMount(path: string): {
|
|
27
27
|
fs: FileSystem;
|
package/dist/emulation/shared.js
CHANGED
|
@@ -43,7 +43,7 @@ export function mount(mountPoint, fs) {
|
|
|
43
43
|
*/
|
|
44
44
|
export function umount(mountPoint) {
|
|
45
45
|
if (mountPoint[0] !== '/') {
|
|
46
|
-
mountPoint =
|
|
46
|
+
mountPoint = '/' + mountPoint;
|
|
47
47
|
}
|
|
48
48
|
mountPoint = resolve(mountPoint);
|
|
49
49
|
if (!mounts.has(mountPoint)) {
|
|
@@ -52,11 +52,10 @@ export function umount(mountPoint) {
|
|
|
52
52
|
mounts.delete(mountPoint);
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
|
|
55
|
+
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
|
56
56
|
*/
|
|
57
57
|
export function resolveMount(path) {
|
|
58
58
|
path = normalizePath(path);
|
|
59
|
-
// Maybe do something for devices here
|
|
60
59
|
const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
|
|
61
60
|
for (const [mountPoint, fs] of sortedMounts) {
|
|
62
61
|
// We know path is normalized, so it would be a substring of the mount point.
|
package/dist/file.js
CHANGED
|
@@ -289,12 +289,12 @@ export class PreloadFile extends File {
|
|
|
289
289
|
}
|
|
290
290
|
async truncate(length) {
|
|
291
291
|
this._truncate(length);
|
|
292
|
-
if (config.
|
|
292
|
+
if (config.syncImmediately)
|
|
293
293
|
await this.sync();
|
|
294
294
|
}
|
|
295
295
|
truncateSync(length) {
|
|
296
296
|
this._truncate(length);
|
|
297
|
-
if (config.
|
|
297
|
+
if (config.syncImmediately)
|
|
298
298
|
this.syncSync();
|
|
299
299
|
}
|
|
300
300
|
_write(buffer, offset = 0, length = this.stats.size, position = this.position) {
|
|
@@ -339,7 +339,7 @@ export class PreloadFile extends File {
|
|
|
339
339
|
*/
|
|
340
340
|
async write(buffer, offset, length, position) {
|
|
341
341
|
const bytesWritten = this._write(buffer, offset, length, position);
|
|
342
|
-
if (config.
|
|
342
|
+
if (config.syncImmediately)
|
|
343
343
|
await this.sync();
|
|
344
344
|
return bytesWritten;
|
|
345
345
|
}
|
|
@@ -354,7 +354,7 @@ export class PreloadFile extends File {
|
|
|
354
354
|
*/
|
|
355
355
|
writeSync(buffer, offset = 0, length = this.stats.size, position = this.position) {
|
|
356
356
|
const bytesWritten = this._write(buffer, offset, length, position);
|
|
357
|
-
if (config.
|
|
357
|
+
if (config.syncImmediately)
|
|
358
358
|
this.syncSync();
|
|
359
359
|
return bytesWritten;
|
|
360
360
|
}
|
|
@@ -365,7 +365,9 @@ export class PreloadFile extends File {
|
|
|
365
365
|
if (!isReadable(this.flag)) {
|
|
366
366
|
throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
|
|
367
367
|
}
|
|
368
|
-
|
|
368
|
+
if (config.updateOnRead) {
|
|
369
|
+
this.dirty = true;
|
|
370
|
+
}
|
|
369
371
|
this.stats.atimeMs = Date.now();
|
|
370
372
|
position ?? (position = this.position);
|
|
371
373
|
let end = position + length;
|
|
@@ -391,7 +393,7 @@ export class PreloadFile extends File {
|
|
|
391
393
|
*/
|
|
392
394
|
async read(buffer, offset, length, position) {
|
|
393
395
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
394
|
-
if (config.
|
|
396
|
+
if (config.syncImmediately)
|
|
395
397
|
await this.sync();
|
|
396
398
|
return { bytesRead, buffer };
|
|
397
399
|
}
|
|
@@ -406,7 +408,7 @@ export class PreloadFile extends File {
|
|
|
406
408
|
*/
|
|
407
409
|
readSync(buffer, offset, length, position) {
|
|
408
410
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
409
|
-
if (config.
|
|
411
|
+
if (config.syncImmediately)
|
|
410
412
|
this.syncSync();
|
|
411
413
|
return bytesRead;
|
|
412
414
|
}
|
|
@@ -416,7 +418,7 @@ export class PreloadFile extends File {
|
|
|
416
418
|
}
|
|
417
419
|
this.dirty = true;
|
|
418
420
|
this.stats.chmod(mode);
|
|
419
|
-
if (config.
|
|
421
|
+
if (config.syncImmediately)
|
|
420
422
|
await this.sync();
|
|
421
423
|
}
|
|
422
424
|
chmodSync(mode) {
|
|
@@ -425,7 +427,7 @@ export class PreloadFile extends File {
|
|
|
425
427
|
}
|
|
426
428
|
this.dirty = true;
|
|
427
429
|
this.stats.chmod(mode);
|
|
428
|
-
if (config.
|
|
430
|
+
if (config.syncImmediately)
|
|
429
431
|
this.syncSync();
|
|
430
432
|
}
|
|
431
433
|
async chown(uid, gid) {
|
|
@@ -434,7 +436,7 @@ export class PreloadFile extends File {
|
|
|
434
436
|
}
|
|
435
437
|
this.dirty = true;
|
|
436
438
|
this.stats.chown(uid, gid);
|
|
437
|
-
if (config.
|
|
439
|
+
if (config.syncImmediately)
|
|
438
440
|
await this.sync();
|
|
439
441
|
}
|
|
440
442
|
chownSync(uid, gid) {
|
|
@@ -443,7 +445,7 @@ export class PreloadFile extends File {
|
|
|
443
445
|
}
|
|
444
446
|
this.dirty = true;
|
|
445
447
|
this.stats.chown(uid, gid);
|
|
446
|
-
if (config.
|
|
448
|
+
if (config.syncImmediately)
|
|
447
449
|
this.syncSync();
|
|
448
450
|
}
|
|
449
451
|
async utimes(atime, mtime) {
|
|
@@ -453,7 +455,7 @@ export class PreloadFile extends File {
|
|
|
453
455
|
this.dirty = true;
|
|
454
456
|
this.stats.atime = atime;
|
|
455
457
|
this.stats.mtime = mtime;
|
|
456
|
-
if (config.
|
|
458
|
+
if (config.syncImmediately)
|
|
457
459
|
await this.sync();
|
|
458
460
|
}
|
|
459
461
|
utimesSync(atime, mtime) {
|
|
@@ -463,7 +465,7 @@ export class PreloadFile extends File {
|
|
|
463
465
|
this.dirty = true;
|
|
464
466
|
this.stats.atime = atime;
|
|
465
467
|
this.stats.mtime = mtime;
|
|
466
|
-
if (config.
|
|
468
|
+
if (config.syncImmediately)
|
|
467
469
|
this.syncSync();
|
|
468
470
|
}
|
|
469
471
|
async _setType(type) {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -201,6 +201,10 @@ If you would like to see a more intuitive way adding custom devices (e.g. `fs.mk
|
|
|
201
201
|
|
|
202
202
|
ZenFS exports a drop-in for Node's `fs` module (up to the version of `@types/node` in package.json), so you can use it for your bundler of preference using the default export.
|
|
203
203
|
|
|
204
|
+
## Sponsors
|
|
205
|
+
|
|
206
|
+
A huge thank you to [ Deco.cx](https://github.com/deco-cx) for sponsoring ZenFS and helping to make this possible.
|
|
207
|
+
|
|
204
208
|
## Building
|
|
205
209
|
|
|
206
210
|
- Make sure you have Node and NPM installed. You must have Node v18 or newer.
|
package/src/backends/backend.ts
CHANGED
|
@@ -17,8 +17,9 @@ export type OptionsConfig<T> = {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Description of the option. Used in error messages and documentation.
|
|
20
|
+
* @deprecated
|
|
20
21
|
*/
|
|
21
|
-
description
|
|
22
|
+
description?: string;
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Whether or not the option is required (optional can be set to null or undefined). Defaults to false.
|
package/src/backends/fetch.ts
CHANGED
|
@@ -12,11 +12,11 @@ import type { IndexData } from './file_index.js';
|
|
|
12
12
|
* constants.
|
|
13
13
|
* @hidden
|
|
14
14
|
*/
|
|
15
|
-
async function fetchFile(path: string, type: 'buffer'): Promise<Uint8Array>;
|
|
16
|
-
async function fetchFile<T extends object>(path: string, type: 'json'): Promise<T>;
|
|
17
|
-
async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json'): Promise<T | Uint8Array>;
|
|
18
|
-
async function fetchFile<T extends object>(path: string, type: string): Promise<T | Uint8Array> {
|
|
19
|
-
const response = await fetch(path).catch((e: Error) => {
|
|
15
|
+
async function fetchFile(path: string, type: 'buffer', init?: RequestInit): Promise<Uint8Array>;
|
|
16
|
+
async function fetchFile<T extends object>(path: string, type: 'json', init?: RequestInit): Promise<T>;
|
|
17
|
+
async function fetchFile<T extends object>(path: string, type: 'buffer' | 'json', init?: RequestInit): Promise<T | Uint8Array>;
|
|
18
|
+
async function fetchFile<T extends object>(path: string, type: string, init?: RequestInit): Promise<T | Uint8Array> {
|
|
19
|
+
const response = await fetch(path, init).catch((e: Error) => {
|
|
20
20
|
throw new ErrnoError(Errno.EIO, e.message, path);
|
|
21
21
|
});
|
|
22
22
|
if (!response.ok) {
|
|
@@ -42,6 +42,11 @@ async function fetchFile<T extends object>(path: string, type: string): Promise<
|
|
|
42
42
|
* Configuration options for FetchFS.
|
|
43
43
|
*/
|
|
44
44
|
export interface FetchOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Options to pass through to fetch calls
|
|
47
|
+
*/
|
|
48
|
+
requestInit?: RequestInit;
|
|
49
|
+
|
|
45
50
|
/**
|
|
46
51
|
* URL to a file index as a JSON file or the file index object itself.
|
|
47
52
|
* Defaults to `index.json`.
|
|
@@ -75,6 +80,7 @@ export interface FetchOptions {
|
|
|
75
80
|
*/
|
|
76
81
|
export class FetchFS extends IndexFS {
|
|
77
82
|
public readonly baseUrl: string;
|
|
83
|
+
public readonly requestInit?: RequestInit;
|
|
78
84
|
|
|
79
85
|
public async ready(): Promise<void> {
|
|
80
86
|
if (this._isInitialized) {
|
|
@@ -94,15 +100,16 @@ export class FetchFS extends IndexFS {
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
public constructor({ index = 'index.json', baseUrl = '' }: FetchOptions) {
|
|
103
|
+
public constructor({ index = 'index.json', baseUrl = '', requestInit }: FetchOptions) {
|
|
98
104
|
// prefix url must end in a directory separator.
|
|
99
105
|
if (baseUrl.at(-1) != '/') {
|
|
100
106
|
baseUrl += '/';
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
super(typeof index != 'string' ? index : fetchFile<IndexData>(baseUrl + index, 'json'));
|
|
109
|
+
super(typeof index != 'string' ? index : fetchFile<IndexData>(baseUrl + index, 'json', requestInit));
|
|
104
110
|
|
|
105
111
|
this.baseUrl = baseUrl;
|
|
112
|
+
this.requestInit = requestInit;
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
public metadata(): FileSystemMetadata {
|
|
@@ -136,7 +143,7 @@ export class FetchFS extends IndexFS {
|
|
|
136
143
|
return stats.fileData;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer');
|
|
146
|
+
const data = await fetchFile(this.baseUrl + (path.startsWith('/') ? path.slice(1) : path), 'buffer', this.requestInit);
|
|
140
147
|
stats.fileData = data;
|
|
141
148
|
return data;
|
|
142
149
|
}
|
|
@@ -154,16 +161,9 @@ const _Fetch = {
|
|
|
154
161
|
name: 'Fetch',
|
|
155
162
|
|
|
156
163
|
options: {
|
|
157
|
-
index: {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
description: 'URL to a file index as a JSON file or the file index object itself, generated with the make-index script. Defaults to `index.json`.',
|
|
161
|
-
},
|
|
162
|
-
baseUrl: {
|
|
163
|
-
type: 'string',
|
|
164
|
-
required: false,
|
|
165
|
-
description: 'Used as the URL prefix for fetched files. Default: Fetch files relative to the index.',
|
|
166
|
-
},
|
|
164
|
+
index: { type: ['string', 'object'], required: false },
|
|
165
|
+
baseUrl: { type: 'string', required: false },
|
|
166
|
+
requestInit: { type: 'object', required: false },
|
|
167
167
|
},
|
|
168
168
|
|
|
169
169
|
isAvailable(): boolean {
|
package/src/backends/memory.ts
CHANGED
|
@@ -32,11 +32,7 @@ const _InMemory = {
|
|
|
32
32
|
return true;
|
|
33
33
|
},
|
|
34
34
|
options: {
|
|
35
|
-
name: {
|
|
36
|
-
type: 'string',
|
|
37
|
-
required: false,
|
|
38
|
-
description: 'The name of the store',
|
|
39
|
-
},
|
|
35
|
+
name: { type: 'string', required: false },
|
|
40
36
|
},
|
|
41
37
|
create({ name }: { name?: string }) {
|
|
42
38
|
const fs = new StoreFS(new InMemoryStore(name));
|
package/src/backends/port/fs.ts
CHANGED
|
@@ -298,7 +298,6 @@ const _Port = {
|
|
|
298
298
|
port: {
|
|
299
299
|
type: 'object',
|
|
300
300
|
required: true,
|
|
301
|
-
description: 'The target port that you want to connect to',
|
|
302
301
|
validator(port: RPC.Port) {
|
|
303
302
|
// Check for a `postMessage` function.
|
|
304
303
|
if (typeof port?.postMessage != 'function') {
|
|
@@ -309,7 +308,6 @@ const _Port = {
|
|
|
309
308
|
timeout: {
|
|
310
309
|
type: 'number',
|
|
311
310
|
required: false,
|
|
312
|
-
description: 'How long to wait before the request times out',
|
|
313
311
|
},
|
|
314
312
|
},
|
|
315
313
|
|
package/src/config.ts
CHANGED
|
@@ -163,8 +163,8 @@ export async function configure<T extends ConfigMounts>(configuration: Partial<C
|
|
|
163
163
|
|
|
164
164
|
cache.setEnabled(configuration.cacheStats ?? false);
|
|
165
165
|
config.checkAccess = !configuration.disableAccessChecks;
|
|
166
|
-
config.
|
|
167
|
-
config.
|
|
166
|
+
config.updateOnRead = !configuration.disableUpdateOnRead;
|
|
167
|
+
config.syncImmediately = !configuration.onlySyncOnClose;
|
|
168
168
|
|
|
169
169
|
if (configuration.addDevices) {
|
|
170
170
|
const devfs = new DeviceFS();
|
package/src/devices.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is a great resource: https://www.kernel.org/doc/html/latest/admin-guide/devices.html
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import type { FileReadResult } from 'node:fs/promises';
|
|
2
6
|
import { InMemoryStore } from './backends/memory.js';
|
|
3
7
|
import { StoreFS } from './backends/store/fs.js';
|
|
@@ -12,9 +16,10 @@ import type { Ino } from './inode.js';
|
|
|
12
16
|
/**
|
|
13
17
|
* A device
|
|
14
18
|
* @todo Maybe add major/minor number or some other device information, like a UUID?
|
|
15
|
-
* @
|
|
19
|
+
* @privateRemarks
|
|
20
|
+
* UUIDs were considered, however they don't make sense without an easy mechanism for persistance
|
|
16
21
|
*/
|
|
17
|
-
export interface Device {
|
|
22
|
+
export interface Device<TData = any> {
|
|
18
23
|
/**
|
|
19
24
|
* The device's driver
|
|
20
25
|
*/
|
|
@@ -24,13 +29,31 @@ export interface Device {
|
|
|
24
29
|
* Which inode the device is assigned
|
|
25
30
|
*/
|
|
26
31
|
ino: Ino;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Data associated with a device.
|
|
35
|
+
* This is meant to be used by device drivers.
|
|
36
|
+
* @experimental
|
|
37
|
+
*/
|
|
38
|
+
data: TData;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Major device number
|
|
42
|
+
* @experimental
|
|
43
|
+
*/
|
|
44
|
+
major: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Minor device number
|
|
48
|
+
* @experimental
|
|
49
|
+
*/
|
|
50
|
+
minor: number;
|
|
27
51
|
}
|
|
28
52
|
|
|
29
53
|
/**
|
|
30
54
|
* A device driver
|
|
31
|
-
* @experimental
|
|
32
55
|
*/
|
|
33
|
-
export interface DeviceDriver {
|
|
56
|
+
export interface DeviceDriver<TData = any> {
|
|
34
57
|
/**
|
|
35
58
|
* The name of the device driver
|
|
36
59
|
*/
|
|
@@ -38,26 +61,42 @@ export interface DeviceDriver {
|
|
|
38
61
|
|
|
39
62
|
/**
|
|
40
63
|
* Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
|
|
64
|
+
* @default false
|
|
41
65
|
*/
|
|
42
|
-
isBuffered
|
|
66
|
+
isBuffered?: boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Initializes a new device.
|
|
70
|
+
* @returns `Device.data`
|
|
71
|
+
* @experimental
|
|
72
|
+
*/
|
|
73
|
+
init?(ino: Ino): {
|
|
74
|
+
data?: TData;
|
|
75
|
+
minor?: number;
|
|
76
|
+
major?: number;
|
|
77
|
+
};
|
|
43
78
|
|
|
44
79
|
/**
|
|
45
80
|
* Synchronously read from the device
|
|
81
|
+
* @group File operations
|
|
46
82
|
*/
|
|
47
83
|
read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
48
84
|
|
|
49
85
|
/**
|
|
50
86
|
* Synchronously write to the device
|
|
87
|
+
* @group File operations
|
|
51
88
|
*/
|
|
52
89
|
write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
|
|
53
90
|
|
|
54
91
|
/**
|
|
55
92
|
* Sync the device
|
|
93
|
+
* @group File operations
|
|
56
94
|
*/
|
|
57
95
|
sync?(file: DeviceFile): void;
|
|
58
96
|
|
|
59
97
|
/**
|
|
60
98
|
* Close the device
|
|
99
|
+
* @group File operations
|
|
61
100
|
*/
|
|
62
101
|
close?(file: DeviceFile): void;
|
|
63
102
|
}
|
|
@@ -67,7 +106,6 @@ export interface DeviceDriver {
|
|
|
67
106
|
* This class only does some simple things:
|
|
68
107
|
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
69
108
|
* It is up to device drivers to implement the rest of the functionality.
|
|
70
|
-
* @experimental
|
|
71
109
|
*/
|
|
72
110
|
export class DeviceFile extends File {
|
|
73
111
|
public position = 0;
|
|
@@ -182,12 +220,15 @@ export class DeviceFile extends File {
|
|
|
182
220
|
}
|
|
183
221
|
|
|
184
222
|
/**
|
|
185
|
-
*
|
|
223
|
+
* A temporary file system that manages and interfaces with devices
|
|
186
224
|
*/
|
|
187
225
|
export class DeviceFS extends StoreFS<InMemoryStore> {
|
|
188
226
|
protected readonly devices = new Map<string, Device>();
|
|
189
227
|
|
|
190
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Creates a new device at `path` relative to the `DeviceFS` root.
|
|
230
|
+
*/
|
|
231
|
+
public createDevice<TData = any>(path: string, driver: DeviceDriver<TData>): Device<TData | Record<string, never>> {
|
|
191
232
|
if (this.existsSync(path)) {
|
|
192
233
|
throw ErrnoError.With('EEXIST', path, 'mknod');
|
|
193
234
|
}
|
|
@@ -196,6 +237,10 @@ export class DeviceFS extends StoreFS<InMemoryStore> {
|
|
|
196
237
|
const dev = {
|
|
197
238
|
driver,
|
|
198
239
|
ino,
|
|
240
|
+
data: {},
|
|
241
|
+
minor: 0,
|
|
242
|
+
major: 0,
|
|
243
|
+
...driver.init?.(ino),
|
|
199
244
|
};
|
|
200
245
|
this.devices.set(path, dev);
|
|
201
246
|
return dev;
|
|
@@ -373,7 +418,9 @@ function defaultWrite(file: DeviceFile, buffer: Uint8Array, offset: number, leng
|
|
|
373
418
|
*/
|
|
374
419
|
export const nullDevice: DeviceDriver = {
|
|
375
420
|
name: 'null',
|
|
376
|
-
|
|
421
|
+
init() {
|
|
422
|
+
return { major: 1, minor: 3 };
|
|
423
|
+
},
|
|
377
424
|
read(): number {
|
|
378
425
|
return 0;
|
|
379
426
|
},
|
|
@@ -392,7 +439,9 @@ export const nullDevice: DeviceDriver = {
|
|
|
392
439
|
*/
|
|
393
440
|
export const zeroDevice: DeviceDriver = {
|
|
394
441
|
name: 'zero',
|
|
395
|
-
|
|
442
|
+
init() {
|
|
443
|
+
return { major: 1, minor: 5 };
|
|
444
|
+
},
|
|
396
445
|
read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
|
|
397
446
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
398
447
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -412,7 +461,9 @@ export const zeroDevice: DeviceDriver = {
|
|
|
412
461
|
*/
|
|
413
462
|
export const fullDevice: DeviceDriver = {
|
|
414
463
|
name: 'full',
|
|
415
|
-
|
|
464
|
+
init() {
|
|
465
|
+
return { major: 1, minor: 7 };
|
|
466
|
+
},
|
|
416
467
|
read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
|
|
417
468
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
418
469
|
for (let i = offset; i < offset + length; i++) {
|
|
@@ -435,7 +486,9 @@ export const fullDevice: DeviceDriver = {
|
|
|
435
486
|
*/
|
|
436
487
|
export const randomDevice: DeviceDriver = {
|
|
437
488
|
name: 'random',
|
|
438
|
-
|
|
489
|
+
init() {
|
|
490
|
+
return { major: 1, minor: 8 };
|
|
491
|
+
},
|
|
439
492
|
read(file: DeviceFile, buffer: ArrayBufferView, offset = 0, length = buffer.byteLength): number {
|
|
440
493
|
const data = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
441
494
|
for (let i = offset; i < offset + length; i++) {
|
package/src/emulation/config.ts
CHANGED
|
@@ -5,14 +5,14 @@ export const config = {
|
|
|
5
5
|
checkAccess: true,
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Whether to
|
|
8
|
+
* Whether to mark a file as dirty after updating its `atime` when read from
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
updateOnRead: true,
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Whether to immediately sync when files are
|
|
13
|
+
* Whether to immediately sync when files are changed
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
syncImmediately: true,
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* If a file's buffer is not large enough to store content when writing and the buffer can't be resized, reuse the buffer passed to write()
|
package/src/emulation/shared.ts
CHANGED
|
@@ -54,7 +54,7 @@ export function mount(mountPoint: string, fs: FileSystem): void {
|
|
|
54
54
|
*/
|
|
55
55
|
export function umount(mountPoint: string): void {
|
|
56
56
|
if (mountPoint[0] !== '/') {
|
|
57
|
-
mountPoint =
|
|
57
|
+
mountPoint = '/' + mountPoint;
|
|
58
58
|
}
|
|
59
59
|
mountPoint = resolve(mountPoint);
|
|
60
60
|
if (!mounts.has(mountPoint)) {
|
|
@@ -64,11 +64,10 @@ export function umount(mountPoint: string): void {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Gets the internal FileSystem for the path, then returns it along with the path relative to the FS' root
|
|
67
|
+
* Gets the internal `FileSystem` for the path, then returns it along with the path relative to the FS' root
|
|
68
68
|
*/
|
|
69
69
|
export function resolveMount(path: string): { fs: FileSystem; path: string; mountPoint: string } {
|
|
70
70
|
path = normalizePath(path);
|
|
71
|
-
// Maybe do something for devices here
|
|
72
71
|
const sortedMounts = [...mounts].sort((a, b) => (a[0].length > b[0].length ? -1 : 1)); // descending order of the string length
|
|
73
72
|
for (const [mountPoint, fs] of sortedMounts) {
|
|
74
73
|
// We know path is normalized, so it would be a substring of the mount point.
|
package/src/file.ts
CHANGED
|
@@ -441,12 +441,12 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
441
441
|
|
|
442
442
|
public async truncate(length: number): Promise<void> {
|
|
443
443
|
this._truncate(length);
|
|
444
|
-
if (config.
|
|
444
|
+
if (config.syncImmediately) await this.sync();
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
public truncateSync(length: number): void {
|
|
448
448
|
this._truncate(length);
|
|
449
|
-
if (config.
|
|
449
|
+
if (config.syncImmediately) this.syncSync();
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
protected _write(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = this.position): number {
|
|
@@ -494,7 +494,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
494
494
|
*/
|
|
495
495
|
public async write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number> {
|
|
496
496
|
const bytesWritten = this._write(buffer, offset, length, position);
|
|
497
|
-
if (config.
|
|
497
|
+
if (config.syncImmediately) await this.sync();
|
|
498
498
|
return bytesWritten;
|
|
499
499
|
}
|
|
500
500
|
|
|
@@ -509,7 +509,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
509
509
|
*/
|
|
510
510
|
public writeSync(buffer: Uint8Array, offset: number = 0, length: number = this.stats.size, position: number = this.position): number {
|
|
511
511
|
const bytesWritten = this._write(buffer, offset, length, position);
|
|
512
|
-
if (config.
|
|
512
|
+
if (config.syncImmediately) this.syncSync();
|
|
513
513
|
return bytesWritten;
|
|
514
514
|
}
|
|
515
515
|
|
|
@@ -517,11 +517,17 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
517
517
|
if (this.closed) {
|
|
518
518
|
throw ErrnoError.With('EBADF', this.path, 'File.read');
|
|
519
519
|
}
|
|
520
|
+
|
|
520
521
|
if (!isReadable(this.flag)) {
|
|
521
522
|
throw new ErrnoError(Errno.EPERM, 'File not opened with a readable mode.');
|
|
522
523
|
}
|
|
523
|
-
|
|
524
|
+
|
|
525
|
+
if (config.updateOnRead) {
|
|
526
|
+
this.dirty = true;
|
|
527
|
+
}
|
|
528
|
+
|
|
524
529
|
this.stats.atimeMs = Date.now();
|
|
530
|
+
|
|
525
531
|
position ??= this.position;
|
|
526
532
|
let end = position + length;
|
|
527
533
|
if (end > this.stats.size) {
|
|
@@ -547,7 +553,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
547
553
|
*/
|
|
548
554
|
public async read<TBuffer extends ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number, position?: number): Promise<{ bytesRead: number; buffer: TBuffer }> {
|
|
549
555
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
550
|
-
if (config.
|
|
556
|
+
if (config.syncImmediately) await this.sync();
|
|
551
557
|
return { bytesRead, buffer };
|
|
552
558
|
}
|
|
553
559
|
|
|
@@ -562,7 +568,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
562
568
|
*/
|
|
563
569
|
public readSync(buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number {
|
|
564
570
|
const bytesRead = this._read(buffer, offset, length, position);
|
|
565
|
-
if (config.
|
|
571
|
+
if (config.syncImmediately) this.syncSync();
|
|
566
572
|
return bytesRead;
|
|
567
573
|
}
|
|
568
574
|
|
|
@@ -572,7 +578,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
572
578
|
}
|
|
573
579
|
this.dirty = true;
|
|
574
580
|
this.stats.chmod(mode);
|
|
575
|
-
if (config.
|
|
581
|
+
if (config.syncImmediately) await this.sync();
|
|
576
582
|
}
|
|
577
583
|
|
|
578
584
|
public chmodSync(mode: number): void {
|
|
@@ -581,7 +587,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
581
587
|
}
|
|
582
588
|
this.dirty = true;
|
|
583
589
|
this.stats.chmod(mode);
|
|
584
|
-
if (config.
|
|
590
|
+
if (config.syncImmediately) this.syncSync();
|
|
585
591
|
}
|
|
586
592
|
|
|
587
593
|
public async chown(uid: number, gid: number): Promise<void> {
|
|
@@ -590,7 +596,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
590
596
|
}
|
|
591
597
|
this.dirty = true;
|
|
592
598
|
this.stats.chown(uid, gid);
|
|
593
|
-
if (config.
|
|
599
|
+
if (config.syncImmediately) await this.sync();
|
|
594
600
|
}
|
|
595
601
|
|
|
596
602
|
public chownSync(uid: number, gid: number): void {
|
|
@@ -599,7 +605,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
599
605
|
}
|
|
600
606
|
this.dirty = true;
|
|
601
607
|
this.stats.chown(uid, gid);
|
|
602
|
-
if (config.
|
|
608
|
+
if (config.syncImmediately) this.syncSync();
|
|
603
609
|
}
|
|
604
610
|
|
|
605
611
|
public async utimes(atime: Date, mtime: Date): Promise<void> {
|
|
@@ -609,7 +615,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
609
615
|
this.dirty = true;
|
|
610
616
|
this.stats.atime = atime;
|
|
611
617
|
this.stats.mtime = mtime;
|
|
612
|
-
if (config.
|
|
618
|
+
if (config.syncImmediately) await this.sync();
|
|
613
619
|
}
|
|
614
620
|
|
|
615
621
|
public utimesSync(atime: Date, mtime: Date): void {
|
|
@@ -619,7 +625,7 @@ export class PreloadFile<FS extends FileSystem> extends File {
|
|
|
619
625
|
this.dirty = true;
|
|
620
626
|
this.stats.atime = atime;
|
|
621
627
|
this.stats.mtime = mtime;
|
|
622
|
-
if (config.
|
|
628
|
+
if (config.syncImmediately) this.syncSync();
|
|
623
629
|
}
|
|
624
630
|
|
|
625
631
|
public async _setType(type: FileType): Promise<void> {
|