@zenfs/core 1.0.11 → 1.1.0
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/file_index.js +3 -3
- package/dist/backends/overlay.js +3 -3
- package/dist/backends/store/fs.d.ts +5 -5
- package/dist/backends/store/fs.js +5 -5
- package/dist/config.d.ts +8 -0
- package/dist/config.js +10 -0
- package/dist/devices.d.ts +158 -0
- package/dist/devices.js +423 -0
- package/dist/emulation/constants.d.ts +5 -0
- package/dist/emulation/constants.js +5 -0
- package/dist/emulation/shared.js +3 -2
- package/dist/file.js +1 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/inode.d.ts +0 -5
- package/dist/inode.js +0 -5
- package/dist/stats.js +1 -2
- package/dist/utils.d.ts +14 -4
- package/dist/utils.js +28 -6
- package/package.json +1 -1
- package/readme.md +58 -2
- package/src/backends/file_index.ts +3 -3
- package/src/backends/overlay.ts +3 -3
- package/src/backends/store/fs.ts +10 -9
- package/src/config.ts +22 -0
- package/src/devices.ts +469 -0
- package/src/emulation/constants.ts +6 -0
- package/src/emulation/shared.ts +3 -2
- package/src/file.ts +1 -2
- package/src/index.ts +2 -0
- package/src/inode.ts +0 -6
- package/src/stats.ts +1 -2
- package/src/utils.ts +33 -6
|
@@ -6,7 +6,7 @@ import { NoSyncFile, isWriteable } from '../file.js';
|
|
|
6
6
|
import { FileSystem } from '../filesystem.js';
|
|
7
7
|
import { Readonly } from '../mixins/readonly.js';
|
|
8
8
|
import { Stats } from '../stats.js';
|
|
9
|
-
import {
|
|
9
|
+
import { decodeUTF8, encodeUTF8 } from '../utils.js';
|
|
10
10
|
export const version = 1;
|
|
11
11
|
/**
|
|
12
12
|
* An index of files
|
|
@@ -64,7 +64,7 @@ export class Index extends Map {
|
|
|
64
64
|
for (const [path, data] of Object.entries(json.entries)) {
|
|
65
65
|
const stats = new Stats(data);
|
|
66
66
|
if (stats.isDirectory()) {
|
|
67
|
-
stats.fileData =
|
|
67
|
+
stats.fileData = encodeUTF8(JSON.stringify(this.dirEntries(path)));
|
|
68
68
|
}
|
|
69
69
|
this.set(path, stats);
|
|
70
70
|
}
|
|
@@ -154,7 +154,7 @@ export class IndexFS extends Readonly(FileSystem) {
|
|
|
154
154
|
if (!stats.isDirectory()) {
|
|
155
155
|
throw ErrnoError.With('ENOTDIR', path, 'readdir');
|
|
156
156
|
}
|
|
157
|
-
const content = JSON.parse(
|
|
157
|
+
const content = JSON.parse(decodeUTF8(stats.fileData));
|
|
158
158
|
if (!Array.isArray(content)) {
|
|
159
159
|
throw ErrnoError.With('ENODATA', path, 'readdir');
|
|
160
160
|
}
|
package/dist/backends/overlay.js
CHANGED
|
@@ -51,7 +51,7 @@ import { PreloadFile, parseFlag } from '../file.js';
|
|
|
51
51
|
import { FileSystem } from '../filesystem.js';
|
|
52
52
|
import { Mutexed } from '../mixins/mutexed.js';
|
|
53
53
|
import { Stats } from '../stats.js';
|
|
54
|
-
import {
|
|
54
|
+
import { decodeUTF8, encodeUTF8 } from '../utils.js';
|
|
55
55
|
/** @internal */
|
|
56
56
|
const deletionLogPath = '/.deleted';
|
|
57
57
|
/**
|
|
@@ -115,7 +115,7 @@ export class UnmutexedOverlayFS extends FileSystem {
|
|
|
115
115
|
const file = await this.writable.openFile(deletionLogPath, parseFlag('r'));
|
|
116
116
|
const { size } = await file.stat();
|
|
117
117
|
const { buffer } = await file.read(new Uint8Array(size));
|
|
118
|
-
this._deleteLog =
|
|
118
|
+
this._deleteLog = decodeUTF8(buffer);
|
|
119
119
|
}
|
|
120
120
|
catch (err) {
|
|
121
121
|
if (err.errno !== Errno.ENOENT) {
|
|
@@ -379,7 +379,7 @@ export class UnmutexedOverlayFS extends FileSystem {
|
|
|
379
379
|
this._deleteLogUpdatePending = true;
|
|
380
380
|
const log = await this.writable.openFile(deletionLogPath, parseFlag('w'));
|
|
381
381
|
try {
|
|
382
|
-
await log.write(
|
|
382
|
+
await log.write(encodeUTF8(this._deleteLog));
|
|
383
383
|
if (this._deleteLogUpdateNeeded) {
|
|
384
384
|
this._deleteLogUpdateNeeded = false;
|
|
385
385
|
await this.updateLog('');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { PreloadFile } from '../../file.js';
|
|
2
1
|
import { FileSystem, type FileSystemMetadata } from '../../filesystem.js';
|
|
3
2
|
import { type Ino, Inode } from '../../inode.js';
|
|
4
3
|
import type { FileType, Stats } from '../../stats.js';
|
|
5
4
|
import type { Store, Transaction } from './store.js';
|
|
5
|
+
import type { File } from '../../file.js';
|
|
6
6
|
/**
|
|
7
7
|
* A file system which uses a key-value store.
|
|
8
8
|
*
|
|
@@ -34,10 +34,10 @@ export declare class StoreFS<T extends Store = Store> extends FileSystem {
|
|
|
34
34
|
renameSync(oldPath: string, newPath: string): void;
|
|
35
35
|
stat(path: string): Promise<Stats>;
|
|
36
36
|
statSync(path: string): Stats;
|
|
37
|
-
createFile(path: string, flag: string, mode: number): Promise<
|
|
38
|
-
createFileSync(path: string, flag: string, mode: number):
|
|
39
|
-
openFile(path: string, flag: string): Promise<
|
|
40
|
-
openFileSync(path: string, flag: string):
|
|
37
|
+
createFile(path: string, flag: string, mode: number): Promise<File>;
|
|
38
|
+
createFileSync(path: string, flag: string, mode: number): File;
|
|
39
|
+
openFile(path: string, flag: string): Promise<File>;
|
|
40
|
+
openFileSync(path: string, flag: string): File;
|
|
41
41
|
unlink(path: string): Promise<void>;
|
|
42
42
|
unlinkSync(path: string): void;
|
|
43
43
|
rmdir(path: string): Promise<void>;
|
|
@@ -52,7 +52,7 @@ import { Errno, ErrnoError } from '../../error.js';
|
|
|
52
52
|
import { PreloadFile } from '../../file.js';
|
|
53
53
|
import { FileSystem } from '../../filesystem.js';
|
|
54
54
|
import { Inode, randomIno, rootIno } from '../../inode.js';
|
|
55
|
-
import { decodeDirListing,
|
|
55
|
+
import { decodeDirListing, encodeUTF8, encodeDirListing } from '../../utils.js';
|
|
56
56
|
const maxInodeAllocTries = 5;
|
|
57
57
|
/**
|
|
58
58
|
* A file system which uses a key-value store.
|
|
@@ -295,10 +295,10 @@ export class StoreFS extends FileSystem {
|
|
|
295
295
|
this.removeSync(path, true);
|
|
296
296
|
}
|
|
297
297
|
async mkdir(path, mode) {
|
|
298
|
-
await this.commitNew(path, S_IFDIR, mode,
|
|
298
|
+
await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'));
|
|
299
299
|
}
|
|
300
300
|
mkdirSync(path, mode) {
|
|
301
|
-
this.commitNewSync(path, S_IFDIR, mode,
|
|
301
|
+
this.commitNewSync(path, S_IFDIR, mode, encodeUTF8('{}'));
|
|
302
302
|
}
|
|
303
303
|
async readdir(path) {
|
|
304
304
|
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
@@ -444,7 +444,7 @@ export class StoreFS extends FileSystem {
|
|
|
444
444
|
const inode = new Inode();
|
|
445
445
|
inode.mode = 0o777 | S_IFDIR;
|
|
446
446
|
// If the root doesn't exist, the first random ID shouldn't exist either.
|
|
447
|
-
await tx.set(inode.ino,
|
|
447
|
+
await tx.set(inode.ino, encodeUTF8('{}'));
|
|
448
448
|
await tx.set(rootIno, inode.data);
|
|
449
449
|
await tx.commit();
|
|
450
450
|
}
|
|
@@ -472,7 +472,7 @@ export class StoreFS extends FileSystem {
|
|
|
472
472
|
const inode = new Inode();
|
|
473
473
|
inode.mode = 0o777 | S_IFDIR;
|
|
474
474
|
// If the root doesn't exist, the first random ID shouldn't exist either.
|
|
475
|
-
tx.setSync(inode.ino,
|
|
475
|
+
tx.setSync(inode.ino, encodeUTF8('{}'));
|
|
476
476
|
tx.setSync(rootIno, inode.data);
|
|
477
477
|
tx.commitSync();
|
|
478
478
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -24,12 +24,20 @@ export interface Configuration<T extends ConfigMounts> extends SharedConfig {
|
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* The uid to use
|
|
27
|
+
* @default 0
|
|
27
28
|
*/
|
|
28
29
|
uid: number;
|
|
29
30
|
/**
|
|
30
31
|
* The gid to use
|
|
32
|
+
* @default 0
|
|
31
33
|
*/
|
|
32
34
|
gid: number;
|
|
35
|
+
/**
|
|
36
|
+
* Whether to automatically add normal Linux devices
|
|
37
|
+
* @default false
|
|
38
|
+
* @experimental
|
|
39
|
+
*/
|
|
40
|
+
addDevices: boolean;
|
|
33
41
|
}
|
|
34
42
|
/**
|
|
35
43
|
* Configures ZenFS with single mount point /
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { checkOptions, isBackend, isBackendConfig } from './backends/backend.js';
|
|
2
2
|
import { credentials } from './credentials.js';
|
|
3
|
+
import { DeviceFS, fullDevice, nullDevice, randomDevice, zeroDevice } from './devices.js';
|
|
3
4
|
import * as fs from './emulation/index.js';
|
|
4
5
|
import { Errno, ErrnoError } from './error.js';
|
|
5
6
|
import { FileSystem } from './filesystem.js';
|
|
@@ -64,6 +65,15 @@ export async function configure(configuration) {
|
|
|
64
65
|
const uid = 'uid' in configuration ? configuration.uid || 0 : 0;
|
|
65
66
|
const gid = 'gid' in configuration ? configuration.gid || 0 : 0;
|
|
66
67
|
Object.assign(credentials, { uid, gid, suid: uid, sgid: gid, euid: uid, egid: gid });
|
|
68
|
+
if (configuration.addDevices) {
|
|
69
|
+
const devfs = new DeviceFS();
|
|
70
|
+
devfs.createDevice('/null', nullDevice);
|
|
71
|
+
devfs.createDevice('/zero', zeroDevice);
|
|
72
|
+
devfs.createDevice('/full', fullDevice);
|
|
73
|
+
devfs.createDevice('/random', randomDevice);
|
|
74
|
+
await devfs.ready();
|
|
75
|
+
fs.mount('/dev', devfs);
|
|
76
|
+
}
|
|
67
77
|
if (!configuration.mounts) {
|
|
68
78
|
return;
|
|
69
79
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { FileReadResult } from 'fs/promises';
|
|
2
|
+
import { InMemoryStore } from './backends/memory.js';
|
|
3
|
+
import { StoreFS } from './backends/store/fs.js';
|
|
4
|
+
import { File } from './file.js';
|
|
5
|
+
import type { StatsLike } from './stats.js';
|
|
6
|
+
import { Stats } from './stats.js';
|
|
7
|
+
import type { Ino } from './inode.js';
|
|
8
|
+
/**
|
|
9
|
+
* A device
|
|
10
|
+
* @todo Maybe add major/minor number or some other device information, like a UUID?
|
|
11
|
+
* @experimental
|
|
12
|
+
*/
|
|
13
|
+
export interface Device {
|
|
14
|
+
/**
|
|
15
|
+
* The device's driver
|
|
16
|
+
*/
|
|
17
|
+
driver: DeviceDriver;
|
|
18
|
+
/**
|
|
19
|
+
* Which inode the device is assigned
|
|
20
|
+
*/
|
|
21
|
+
ino: Ino;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A device driver
|
|
25
|
+
* @experimental
|
|
26
|
+
*/
|
|
27
|
+
export interface DeviceDriver {
|
|
28
|
+
/**
|
|
29
|
+
* The name of the device driver
|
|
30
|
+
*/
|
|
31
|
+
name: string;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the device is buffered (a "block" device) or unbuffered (a "character" device)
|
|
34
|
+
*/
|
|
35
|
+
isBuffered: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Synchronously read from the device
|
|
38
|
+
*/
|
|
39
|
+
read(file: DeviceFile, buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Synchronously write to the device
|
|
42
|
+
*/
|
|
43
|
+
write(file: DeviceFile, buffer: Uint8Array, offset: number, length: number, position?: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* Sync the device
|
|
46
|
+
*/
|
|
47
|
+
sync?(file: DeviceFile): void;
|
|
48
|
+
/**
|
|
49
|
+
* Close the device
|
|
50
|
+
*/
|
|
51
|
+
close?(file: DeviceFile): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The base class for device files
|
|
55
|
+
* This class only does some simple things:
|
|
56
|
+
* It implements `truncate` using `write` and it has non-device methods throw.
|
|
57
|
+
* It is up to device drivers to implement the rest of the functionality.
|
|
58
|
+
* @experimental
|
|
59
|
+
*/
|
|
60
|
+
export declare class DeviceFile extends File {
|
|
61
|
+
fs: DeviceFS;
|
|
62
|
+
readonly device: Device;
|
|
63
|
+
position: number;
|
|
64
|
+
constructor(fs: DeviceFS, path: string, device: Device);
|
|
65
|
+
get driver(): DeviceDriver;
|
|
66
|
+
protected get stats(): Partial<StatsLike>;
|
|
67
|
+
stat(): Promise<Stats>;
|
|
68
|
+
statSync(): Stats;
|
|
69
|
+
readSync(buffer: ArrayBufferView, offset?: number, length?: number, position?: number): number;
|
|
70
|
+
read<TBuffer extends NodeJS.ArrayBufferView>(buffer: TBuffer, offset?: number, length?: number): Promise<FileReadResult<TBuffer>>;
|
|
71
|
+
writeSync(buffer: Uint8Array, offset?: number, length?: number, position?: number): number;
|
|
72
|
+
write(buffer: Uint8Array, offset?: number, length?: number, position?: number): Promise<number>;
|
|
73
|
+
truncate(length: number): Promise<void>;
|
|
74
|
+
truncateSync(length: number): void;
|
|
75
|
+
closeSync(): void;
|
|
76
|
+
close(): Promise<void>;
|
|
77
|
+
syncSync(): void;
|
|
78
|
+
sync(): Promise<void>;
|
|
79
|
+
chown(): Promise<void>;
|
|
80
|
+
chownSync(): void;
|
|
81
|
+
chmod(): Promise<void>;
|
|
82
|
+
chmodSync(): void;
|
|
83
|
+
utimes(): Promise<void>;
|
|
84
|
+
utimesSync(): void;
|
|
85
|
+
_setType(): Promise<void>;
|
|
86
|
+
_setTypeSync(): void;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* @experimental
|
|
90
|
+
*/
|
|
91
|
+
export declare class DeviceFS extends StoreFS<InMemoryStore> {
|
|
92
|
+
protected readonly devices: Map<string, Device>;
|
|
93
|
+
createDevice(path: string, driver: DeviceDriver): Device;
|
|
94
|
+
constructor();
|
|
95
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
96
|
+
renameSync(oldPath: string, newPath: string): void;
|
|
97
|
+
stat(path: string): Promise<Stats>;
|
|
98
|
+
statSync(path: string): Stats;
|
|
99
|
+
openFile(path: string, flag: string): Promise<File>;
|
|
100
|
+
openFileSync(path: string, flag: string): File;
|
|
101
|
+
createFile(path: string, flag: string, mode: number): Promise<File>;
|
|
102
|
+
createFileSync(path: string, flag: string, mode: number): File;
|
|
103
|
+
unlink(path: string): Promise<void>;
|
|
104
|
+
unlinkSync(path: string): void;
|
|
105
|
+
rmdir(path: string): Promise<void>;
|
|
106
|
+
rmdirSync(path: string): void;
|
|
107
|
+
mkdir(path: string, mode: number): Promise<void>;
|
|
108
|
+
mkdirSync(path: string, mode: number): void;
|
|
109
|
+
readdir(path: string): Promise<string[]>;
|
|
110
|
+
readdirSync(path: string): string[];
|
|
111
|
+
link(target: string, link: string): Promise<void>;
|
|
112
|
+
linkSync(target: string, link: string): void;
|
|
113
|
+
sync(path: string, data: Uint8Array, stats: Readonly<Stats>): Promise<void>;
|
|
114
|
+
syncSync(path: string, data: Uint8Array, stats: Readonly<Stats>): void;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Simulates the `/dev/null` device.
|
|
118
|
+
* - Reads return 0 bytes (EOF).
|
|
119
|
+
* - Writes discard data, advancing the file position.
|
|
120
|
+
* @experimental
|
|
121
|
+
*/
|
|
122
|
+
export declare const nullDevice: DeviceDriver;
|
|
123
|
+
/**
|
|
124
|
+
* Simulates the `/dev/zero` device
|
|
125
|
+
* Provides an infinite stream of zeroes when read.
|
|
126
|
+
* Discards any data written to it.
|
|
127
|
+
*
|
|
128
|
+
* - Reads fill the buffer with zeroes.
|
|
129
|
+
* - Writes discard data but update the file position.
|
|
130
|
+
* - Provides basic file metadata, treating it as a character device.
|
|
131
|
+
* @experimental
|
|
132
|
+
*/
|
|
133
|
+
export declare const zeroDevice: DeviceDriver;
|
|
134
|
+
/**
|
|
135
|
+
* Simulates the `/dev/full` device.
|
|
136
|
+
* - Reads behave like `/dev/zero` (returns zeroes).
|
|
137
|
+
* - Writes always fail with ENOSPC (no space left on device).
|
|
138
|
+
* @experimental
|
|
139
|
+
*/
|
|
140
|
+
export declare const fullDevice: DeviceDriver;
|
|
141
|
+
/**
|
|
142
|
+
* Simulates the `/dev/random` device.
|
|
143
|
+
* - Reads return random bytes.
|
|
144
|
+
* - Writes discard data, advancing the file position.
|
|
145
|
+
* @experimental
|
|
146
|
+
*/
|
|
147
|
+
export declare const randomDevice: DeviceDriver;
|
|
148
|
+
/**
|
|
149
|
+
* Shortcuts for importing.
|
|
150
|
+
* @experimental
|
|
151
|
+
*/
|
|
152
|
+
declare const _default: {
|
|
153
|
+
null: DeviceDriver;
|
|
154
|
+
zero: DeviceDriver;
|
|
155
|
+
full: DeviceDriver;
|
|
156
|
+
random: DeviceDriver;
|
|
157
|
+
};
|
|
158
|
+
export default _default;
|