@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.
@@ -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 { decode, encode } from '../utils.js';
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 = encode(JSON.stringify(this.dirEntries(path)));
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(decode(stats.fileData));
157
+ const content = JSON.parse(decodeUTF8(stats.fileData));
158
158
  if (!Array.isArray(content)) {
159
159
  throw ErrnoError.With('ENODATA', path, 'readdir');
160
160
  }
@@ -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 { decode, encode } from '../utils.js';
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 = decode(buffer);
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(encode(this._deleteLog));
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<PreloadFile<this>>;
38
- createFileSync(path: string, flag: string, mode: number): PreloadFile<this>;
39
- openFile(path: string, flag: string): Promise<PreloadFile<this>>;
40
- openFileSync(path: string, flag: string): PreloadFile<this>;
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, encode, encodeDirListing } from '../../utils.js';
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, encode('{}'));
298
+ await this.commitNew(path, S_IFDIR, mode, encodeUTF8('{}'));
299
299
  }
300
300
  mkdirSync(path, mode) {
301
- this.commitNewSync(path, S_IFDIR, mode, encode('{}'));
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, encode('{}'));
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, encode('{}'));
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;