@zenfs/core 1.11.3 → 2.0.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/backend.d.ts +19 -15
- package/dist/backends/backend.js +31 -15
- package/dist/backends/cow.d.ts +20 -30
- package/dist/backends/cow.js +52 -142
- package/dist/backends/fetch.d.ts +1 -0
- package/dist/backends/fetch.js +3 -1
- package/dist/backends/index.d.ts +1 -1
- package/dist/backends/index.js +1 -1
- package/dist/backends/memory.d.ts +5 -7
- package/dist/backends/memory.js +2 -3
- package/dist/backends/passthrough.d.ts +19 -22
- package/dist/backends/passthrough.js +85 -160
- package/dist/backends/port.d.ts +207 -0
- package/dist/backends/port.js +297 -0
- package/dist/backends/single_buffer.d.ts +11 -5
- package/dist/backends/single_buffer.js +18 -12
- package/dist/backends/store/fs.d.ts +11 -27
- package/dist/backends/store/fs.js +67 -91
- package/dist/backends/store/store.d.ts +7 -12
- package/dist/config.d.ts +1 -10
- package/dist/config.js +7 -8
- package/dist/context.d.ts +8 -21
- package/dist/context.js +33 -10
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/contexts.d.ts +63 -0
- package/dist/internal/contexts.js +15 -0
- package/dist/internal/credentials.d.ts +2 -11
- package/dist/internal/credentials.js +0 -19
- package/dist/internal/devices.d.ts +18 -80
- package/dist/internal/devices.js +76 -279
- package/dist/internal/file_index.js +3 -3
- package/dist/internal/filesystem.d.ts +31 -89
- package/dist/internal/filesystem.js +21 -20
- package/dist/internal/index.d.ts +0 -1
- package/dist/internal/index.js +0 -1
- package/dist/internal/index_fs.d.ts +12 -30
- package/dist/internal/index_fs.js +23 -55
- package/dist/internal/inode.d.ts +147 -9
- package/dist/internal/inode.js +333 -25
- package/dist/internal/log.d.ts +19 -13
- package/dist/internal/log.js +81 -80
- package/dist/mixins/async.js +26 -90
- package/dist/mixins/mutexed.d.ts +17 -16
- package/dist/mixins/mutexed.js +29 -31
- package/dist/mixins/readonly.d.ts +7 -6
- package/dist/mixins/readonly.js +6 -0
- package/dist/mixins/sync.js +8 -8
- package/dist/{vfs/path.d.ts → path.d.ts} +3 -4
- package/dist/{vfs/path.js → path.js} +6 -9
- package/dist/readline.d.ts +134 -0
- package/dist/readline.js +623 -0
- package/dist/utils.d.ts +4 -35
- package/dist/utils.js +8 -73
- package/dist/vfs/acl.d.ts +42 -0
- package/dist/vfs/acl.js +249 -0
- package/dist/vfs/async.d.ts +7 -21
- package/dist/vfs/async.js +19 -19
- package/dist/vfs/config.d.ts +6 -18
- package/dist/vfs/config.js +8 -18
- package/dist/vfs/dir.d.ts +3 -3
- package/dist/vfs/dir.js +9 -8
- package/dist/vfs/file.d.ts +106 -0
- package/dist/vfs/file.js +235 -0
- package/dist/vfs/flags.d.ts +19 -0
- package/dist/vfs/flags.js +62 -0
- package/dist/vfs/index.d.ts +4 -10
- package/dist/vfs/index.js +4 -13
- package/dist/vfs/ioctl.d.ts +87 -0
- package/dist/vfs/ioctl.js +304 -0
- package/dist/vfs/promises.d.ts +78 -16
- package/dist/vfs/promises.js +273 -122
- package/dist/vfs/shared.d.ts +7 -26
- package/dist/vfs/shared.js +25 -53
- package/dist/{stats.d.ts → vfs/stats.d.ts} +14 -28
- package/dist/{stats.js → vfs/stats.js} +11 -66
- package/dist/vfs/streams.d.ts +1 -0
- package/dist/vfs/streams.js +24 -19
- package/dist/vfs/sync.d.ts +4 -3
- package/dist/vfs/sync.js +143 -128
- package/dist/vfs/watchers.d.ts +2 -2
- package/dist/vfs/watchers.js +6 -6
- package/dist/vfs/xattr.d.ts +116 -0
- package/dist/vfs/xattr.js +218 -0
- package/package.json +3 -3
- package/readme.md +1 -1
- package/tests/backend/config.worker.js +4 -1
- package/tests/backend/fetch.test.ts +3 -0
- package/tests/backend/port.test.ts +21 -35
- package/tests/backend/remote.worker.js +4 -1
- package/tests/backend/single-buffer.test.ts +24 -0
- package/tests/common/context.test.ts +1 -1
- package/tests/common/handle.test.ts +17 -12
- package/tests/common/path.test.ts +1 -1
- package/tests/common/readline.test.ts +104 -0
- package/tests/common.ts +4 -19
- package/tests/fetch/fetch.ts +1 -1
- package/tests/fs/links.test.ts +1 -1
- package/tests/fs/permissions.test.ts +7 -6
- package/tests/fs/readFile.test.ts +3 -3
- package/tests/fs/stat.test.ts +6 -6
- package/tests/fs/streams.test.ts +2 -11
- package/tests/fs/times.test.ts +1 -1
- package/tests/fs/xattr.test.ts +85 -0
- package/tests/logs.js +22 -0
- package/tests/setup/context.ts +1 -1
- package/tests/setup/index.ts +3 -3
- package/tests/setup/port.ts +1 -1
- package/dist/backends/port/fs.d.ts +0 -84
- package/dist/backends/port/fs.js +0 -151
- package/dist/backends/port/rpc.d.ts +0 -77
- package/dist/backends/port/rpc.js +0 -100
- package/dist/backends/store/simple.d.ts +0 -20
- package/dist/backends/store/simple.js +0 -13
- package/dist/internal/file.d.ts +0 -351
- package/dist/internal/file.js +0 -739
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { PassThrough } from 'node:stream';
|
|
3
|
+
import { suite, test } from 'node:test';
|
|
4
|
+
import { wait } from 'utilium';
|
|
5
|
+
import { createInterface, Interface } from '../../dist/readline.js';
|
|
6
|
+
|
|
7
|
+
suite('Readline interface', { skip: true }, () => {
|
|
8
|
+
test('creates interface with readable stream', async () => {
|
|
9
|
+
const input = new PassThrough();
|
|
10
|
+
await using rl = createInterface({ input });
|
|
11
|
+
|
|
12
|
+
assert.ok(rl instanceof Interface);
|
|
13
|
+
assert.equal(rl.input, input);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('emits line events when receiving data', async () => {
|
|
17
|
+
const input = new PassThrough();
|
|
18
|
+
await using rl = createInterface({ input });
|
|
19
|
+
|
|
20
|
+
const lines: string[] = [];
|
|
21
|
+
rl.on('line', (line: string) => lines.push(line));
|
|
22
|
+
|
|
23
|
+
input.write('first line\n');
|
|
24
|
+
input.write('second line\r\n');
|
|
25
|
+
input.write('third line\n');
|
|
26
|
+
|
|
27
|
+
await wait(10);
|
|
28
|
+
|
|
29
|
+
assert.deepEqual(lines, ['first line', 'second line', 'third line']);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('handles partial lines correctly', async () => {
|
|
33
|
+
const input = new PassThrough();
|
|
34
|
+
await using rl = createInterface({ input });
|
|
35
|
+
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
rl.on('line', (line: string) => lines.push(line));
|
|
38
|
+
|
|
39
|
+
input.write('partial ');
|
|
40
|
+
input.write('line\n');
|
|
41
|
+
input.write('another ');
|
|
42
|
+
input.write('partial line\n');
|
|
43
|
+
|
|
44
|
+
await wait(10);
|
|
45
|
+
|
|
46
|
+
assert.deepEqual(lines, ['partial line', 'another partial line']);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('emits remaining buffer on close', async () => {
|
|
50
|
+
const input = new PassThrough();
|
|
51
|
+
await using rl = createInterface({ input });
|
|
52
|
+
|
|
53
|
+
const lines: string[] = [];
|
|
54
|
+
rl.on('line', (line: string) => lines.push(line));
|
|
55
|
+
|
|
56
|
+
input.write('line with newline\n');
|
|
57
|
+
input.write('line without newline');
|
|
58
|
+
|
|
59
|
+
await wait(10);
|
|
60
|
+
|
|
61
|
+
assert.deepEqual(lines, ['line with newline']);
|
|
62
|
+
|
|
63
|
+
await wait(10);
|
|
64
|
+
|
|
65
|
+
assert.deepEqual(lines, ['line with newline', 'line without newline']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('tracks history correctly', async () => {
|
|
69
|
+
const input = new PassThrough();
|
|
70
|
+
await using rl = createInterface({ input });
|
|
71
|
+
|
|
72
|
+
let history: string[] = [];
|
|
73
|
+
rl.on('history', (h: string[]) => (history = h));
|
|
74
|
+
|
|
75
|
+
input.write('first command\n');
|
|
76
|
+
input.write('second command\n');
|
|
77
|
+
|
|
78
|
+
await wait(10);
|
|
79
|
+
|
|
80
|
+
assert.deepEqual(history, ['first command', 'second command']);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('pause and resume functionality', async () => {
|
|
84
|
+
const input = new PassThrough();
|
|
85
|
+
await using rl = createInterface({ input });
|
|
86
|
+
|
|
87
|
+
const lines: string[] = [];
|
|
88
|
+
rl.on('line', (line: string) => lines.push(line));
|
|
89
|
+
|
|
90
|
+
rl.pause();
|
|
91
|
+
input.write('should not be processed\n');
|
|
92
|
+
|
|
93
|
+
await wait(10);
|
|
94
|
+
|
|
95
|
+
assert.deepEqual(lines, []);
|
|
96
|
+
|
|
97
|
+
rl.resume();
|
|
98
|
+
input.write('should be processed\n');
|
|
99
|
+
|
|
100
|
+
await wait(10);
|
|
101
|
+
|
|
102
|
+
assert.deepEqual(lines, ['should be processed']);
|
|
103
|
+
});
|
|
104
|
+
});
|
package/tests/common.ts
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import { join, resolve } from 'node:path';
|
|
2
|
-
import { fs as defaultFS
|
|
2
|
+
import { fs as defaultFS } from '../dist/index.js';
|
|
3
|
+
import { setupLogs } from './logs.js';
|
|
3
4
|
export type * from '../dist/index.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
setupLogs();
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (ZENFS_LOG_LEVEL) {
|
|
10
|
-
const tmp = parseInt(ZENFS_LOG_LEVEL);
|
|
11
|
-
if (Number.isSafeInteger(tmp)) level = tmp;
|
|
12
|
-
else level = ZENFS_LOG_LEVEL as (typeof log.levels)[log.Level];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
log.configure({
|
|
16
|
-
enabled: true,
|
|
17
|
-
format: log.formats.ansi_message,
|
|
18
|
-
dumpBacklog: true,
|
|
19
|
-
level,
|
|
20
|
-
output: console.error,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const setupPath = resolve(SETUP || join(import.meta.dirname, 'setup/memory.ts'));
|
|
8
|
+
const setupPath = resolve(process.env.SETUP || join(import.meta.dirname, 'setup/memory.ts'));
|
|
24
9
|
|
|
25
10
|
const setup = await import(setupPath).catch(error => {
|
|
26
11
|
console.log('Failed to import test setup:');
|
package/tests/fetch/fetch.ts
CHANGED
package/tests/fs/links.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
-
import { join } from '../../dist/
|
|
3
|
+
import { join } from '../../dist/path.js';
|
|
4
4
|
import { fs } from '../common.js';
|
|
5
5
|
import type { ErrnoError } from '../../dist/index.js';
|
|
6
6
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { encodeUTF8 } from 'utilium';
|
|
4
|
+
import { ErrnoError } from '../../dist/index.js';
|
|
5
|
+
import { defaultContext } from '../../dist/internal/contexts.js';
|
|
6
|
+
import { join } from '../../dist/path.js';
|
|
5
7
|
import { R_OK, W_OK, X_OK } from '../../dist/vfs/constants.js';
|
|
6
|
-
import { join } from '../../dist/vfs/path.js';
|
|
7
8
|
import { fs } from '../common.js';
|
|
8
9
|
|
|
9
10
|
const asyncMode = 0o777;
|
|
@@ -87,8 +88,8 @@ suite('Permissions', () => {
|
|
|
87
88
|
assert(stats.hasAccess(R_OK));
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
const copy = { ...credentials };
|
|
91
|
-
Object.assign(credentials, { uid: 1000, gid: 1000, euid: 1000, egid: 1000 });
|
|
91
|
+
const copy = { ...defaultContext.credentials };
|
|
92
|
+
Object.assign(defaultContext.credentials, { uid: 1000, gid: 1000, euid: 1000, egid: 1000 });
|
|
92
93
|
test('Access controls: /', () => test_item('/'));
|
|
93
|
-
Object.assign(credentials, copy);
|
|
94
|
+
Object.assign(defaultContext.credentials, copy);
|
|
94
95
|
});
|
|
@@ -36,7 +36,7 @@ suite('Read and Unlink', () => {
|
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
suite('Read File
|
|
39
|
+
suite('Read File', () => {
|
|
40
40
|
const fn = 'empty.txt';
|
|
41
41
|
|
|
42
42
|
test('read file asynchronously', async () => {
|
|
@@ -64,10 +64,10 @@ suite('fs file reading', () => {
|
|
|
64
64
|
test('read file synchronously and verify the content', () => {
|
|
65
65
|
const content = fs.readFileSync('elipses.txt', 'utf8');
|
|
66
66
|
|
|
67
|
+
assert.equal(content.length, 10000);
|
|
68
|
+
|
|
67
69
|
for (let i = 0; i < content.length; i++) {
|
|
68
70
|
assert.equal(content[i], '…');
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
assert.equal(content.length, 10000);
|
|
72
72
|
});
|
|
73
73
|
});
|
package/tests/fs/stat.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
|
-
import {
|
|
4
|
-
import { Stats } from '../../dist/stats.js';
|
|
3
|
+
import { defaultContext } from '../../dist/internal/contexts.js';
|
|
4
|
+
import { Stats } from '../../dist/vfs/stats.js';
|
|
5
5
|
import { fs } from '../common.js';
|
|
6
6
|
|
|
7
7
|
suite('Stats', () => {
|
|
@@ -40,7 +40,7 @@ suite('Stats', () => {
|
|
|
40
40
|
|
|
41
41
|
fs.writeFileSync(newFile, 'hello', { mode: 0o640 });
|
|
42
42
|
|
|
43
|
-
const prevCredentials = { ...credentials };
|
|
43
|
+
const prevCredentials = { ...defaultContext.credentials };
|
|
44
44
|
const uid = 33;
|
|
45
45
|
const nonRootCredentials = {
|
|
46
46
|
uid,
|
|
@@ -53,7 +53,7 @@ suite('Stats', () => {
|
|
|
53
53
|
|
|
54
54
|
fs.chownSync(newFile, 0, nonRootCredentials.gid); // creating with root-user so that non-root user can access
|
|
55
55
|
|
|
56
|
-
Object.assign(credentials, nonRootCredentials);
|
|
56
|
+
Object.assign(defaultContext.credentials, nonRootCredentials);
|
|
57
57
|
const stat = fs.statSync(newFile);
|
|
58
58
|
|
|
59
59
|
assert.equal(stat.gid, nonRootCredentials.gid);
|
|
@@ -63,13 +63,13 @@ suite('Stats', () => {
|
|
|
63
63
|
assert.equal(stat.hasAccess(fs.constants.X_OK), false);
|
|
64
64
|
// changing group
|
|
65
65
|
|
|
66
|
-
Object.assign(credentials, { ...nonRootCredentials, gid: 44 });
|
|
66
|
+
Object.assign(defaultContext.credentials, { ...nonRootCredentials, gid: 44 });
|
|
67
67
|
|
|
68
68
|
assert.equal(stat.hasAccess(fs.constants.R_OK), false);
|
|
69
69
|
assert.equal(stat.hasAccess(fs.constants.W_OK), false);
|
|
70
70
|
assert.equal(stat.hasAccess(fs.constants.X_OK), false);
|
|
71
71
|
|
|
72
|
-
Object.assign(credentials, prevCredentials);
|
|
72
|
+
Object.assign(defaultContext.credentials, prevCredentials);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('stat file', async () => {
|
package/tests/fs/streams.test.ts
CHANGED
|
@@ -129,21 +129,12 @@ suite('Streams', () => {
|
|
|
129
129
|
test('FileHandle.createReadStream after close should give an error', async () => {
|
|
130
130
|
const fileHandle = await fs.promises.open(testFilePath, 'r');
|
|
131
131
|
await fileHandle.close();
|
|
132
|
-
|
|
133
|
-
const { promise, resolve, reject } = Promise.withResolvers();
|
|
134
|
-
setTimeout(resolve, 100);
|
|
135
|
-
stream.on('error', reject);
|
|
136
|
-
assert.rejects(promise);
|
|
132
|
+
assert.throws(() => fileHandle.createReadStream(), { code: 'EBADF' });
|
|
137
133
|
});
|
|
138
134
|
|
|
139
135
|
test('FileHandle.createWriteStream after close should give an error', async () => {
|
|
140
136
|
const fileHandle = await fs.promises.open(testFilePathWrite, 'w');
|
|
141
137
|
await fileHandle.close();
|
|
142
|
-
|
|
143
|
-
const { promise, resolve, reject } = Promise.withResolvers();
|
|
144
|
-
setTimeout(resolve, 100);
|
|
145
|
-
stream.on('error', reject);
|
|
146
|
-
assert.rejects(promise);
|
|
147
|
-
stream.write('Nuh-uh');
|
|
138
|
+
assert.throws(() => fileHandle.createWriteStream(), { code: 'EBADF' });
|
|
148
139
|
});
|
|
149
140
|
});
|
package/tests/fs/times.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import { suite, test } from 'node:test';
|
|
3
3
|
import { wait } from 'utilium';
|
|
4
4
|
import { ErrnoError } from '../../dist/index.js';
|
|
5
|
-
import type { StatsLike } from '../../dist/stats.js';
|
|
5
|
+
import type { StatsLike } from '../../dist/vfs/stats.js';
|
|
6
6
|
import { fs } from '../common.js';
|
|
7
7
|
|
|
8
8
|
const path = 'x.txt';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { suite, test } from 'node:test';
|
|
4
|
+
import { fs } from '../common.js';
|
|
5
|
+
|
|
6
|
+
suite('Extended Attributes', () => {
|
|
7
|
+
const testFile = 'xattr-test.txt';
|
|
8
|
+
const testValue = 'test value';
|
|
9
|
+
const testName = 'user.test';
|
|
10
|
+
|
|
11
|
+
test.before(() => fs.promises.writeFile(testFile, 'test content'));
|
|
12
|
+
test.after(() => fs.promises.unlink(testFile));
|
|
13
|
+
|
|
14
|
+
test('Non-user attribute set fails', () => {
|
|
15
|
+
assert.rejects(fs.xattr.set(testFile, 'system.test', 'value'), { code: 'EPERM' });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('set and get attributes', async () => {
|
|
19
|
+
await fs.xattr.set(testFile, testName, testValue);
|
|
20
|
+
const value = await fs.xattr.get(testFile, testName, { encoding: 'utf8' });
|
|
21
|
+
assert.equal(value, testValue);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('get attributes with buffer encoding', async () => {
|
|
25
|
+
await fs.xattr.set(testFile, 'user.buffer', 'buffer value');
|
|
26
|
+
const buffer = await fs.xattr.get(testFile, 'user.buffer', { encoding: 'buffer' });
|
|
27
|
+
assert(buffer instanceof Uint8Array);
|
|
28
|
+
assert.equal(Buffer.from(buffer).toString(), 'buffer value');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('remove attributes', async () => {
|
|
32
|
+
await fs.xattr.set(testFile, 'user.to-remove', testValue);
|
|
33
|
+
await fs.xattr.remove(testFile, 'user.to-remove');
|
|
34
|
+
|
|
35
|
+
assert.rejects(fs.xattr.get(testFile, 'user.to-remove', { encoding: 'utf8' }), { code: 'ENODATA' });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('list attributes', async () => {
|
|
39
|
+
await fs.xattr.set(testFile, 'user.list1', 'value1');
|
|
40
|
+
await fs.xattr.set(testFile, 'user.list2', 'value2');
|
|
41
|
+
|
|
42
|
+
const attrs = await fs.xattr.list(testFile);
|
|
43
|
+
assert(attrs.includes('user.list1'));
|
|
44
|
+
assert(attrs.includes('user.list2'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('handle create and replace options', async () => {
|
|
48
|
+
const flagTestName = 'user.flag-test';
|
|
49
|
+
|
|
50
|
+
await fs.xattr.set(testFile, flagTestName, 'original', { create: true });
|
|
51
|
+
|
|
52
|
+
assert.rejects(fs.xattr.set(testFile, flagTestName, 'new value', { create: true }), { code: 'EEXIST' });
|
|
53
|
+
|
|
54
|
+
await fs.xattr.set(testFile, flagTestName, 'updated', { replace: true });
|
|
55
|
+
const value = await fs.xattr.get(testFile, flagTestName, { encoding: 'utf8' });
|
|
56
|
+
assert.equal(value, 'updated');
|
|
57
|
+
|
|
58
|
+
assert.rejects(fs.xattr.set(testFile, 'user.nonexistent', 'value', { replace: true }), { code: 'ENODATA' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('file must exist', () => {
|
|
62
|
+
assert.rejects(fs.xattr.set('nonexistent-file.txt', testName, 'value'), { code: 'ENOENT' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('synchronous operations', () => {
|
|
66
|
+
const syncAttrName = 'user.sync-test';
|
|
67
|
+
|
|
68
|
+
fs.xattr.setSync(testFile, syncAttrName, testValue);
|
|
69
|
+
const value = fs.xattr.getSync(testFile, syncAttrName, { encoding: 'utf8' });
|
|
70
|
+
assert.equal(value, testValue);
|
|
71
|
+
|
|
72
|
+
fs.xattr.removeSync(testFile, syncAttrName);
|
|
73
|
+
|
|
74
|
+
assert.throws(() => fs.xattr.getSync(testFile, syncAttrName, { encoding: 'utf8' }), { code: 'ENODATA' });
|
|
75
|
+
|
|
76
|
+
const syncList1 = 'user.sync-list1';
|
|
77
|
+
const syncList2 = 'user.sync-list2';
|
|
78
|
+
fs.xattr.setSync(testFile, syncList1, 'value1');
|
|
79
|
+
fs.xattr.setSync(testFile, syncList2, 'value2');
|
|
80
|
+
|
|
81
|
+
const attrs = fs.xattr.listSync(testFile);
|
|
82
|
+
assert(attrs.includes(syncList1));
|
|
83
|
+
assert(attrs.includes(syncList2));
|
|
84
|
+
});
|
|
85
|
+
});
|
package/tests/logs.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as log from '../dist/internal/log.js';
|
|
2
|
+
|
|
3
|
+
export function setupLogs(prefix) {
|
|
4
|
+
const { ZENFS_LOG_LEVEL } = process.env;
|
|
5
|
+
|
|
6
|
+
let level = log.Level.CRIT;
|
|
7
|
+
|
|
8
|
+
if (ZENFS_LOG_LEVEL) {
|
|
9
|
+
const tmp = parseInt(ZENFS_LOG_LEVEL);
|
|
10
|
+
if (Number.isSafeInteger(tmp)) level = tmp;
|
|
11
|
+
else level = ZENFS_LOG_LEVEL;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
log.configure({
|
|
15
|
+
enabled: true,
|
|
16
|
+
format: log.fancy({ style: 'ansi', colorize: 'message' }),
|
|
17
|
+
dumpBacklog: true,
|
|
18
|
+
level,
|
|
19
|
+
stack: true,
|
|
20
|
+
output: (...msg) => (prefix ? console.error(prefix, ...msg) : console.error(...msg)),
|
|
21
|
+
});
|
|
22
|
+
}
|
package/tests/setup/context.ts
CHANGED
package/tests/setup/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path/posix';
|
|
3
|
-
import { configureSingle, InMemory, InMemoryStore, mounts,
|
|
3
|
+
import { configureSingle, CopyOnWrite, InMemory, InMemoryStore, mounts, Readonly, StoreFS } from '../../dist/index.js';
|
|
4
4
|
import { S_IFDIR } from '../../dist/vfs/constants.js';
|
|
5
5
|
import { copySync, data } from '../setup.js';
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ const readable = new MockFS();
|
|
|
32
32
|
await readable.ready();
|
|
33
33
|
|
|
34
34
|
await configureSingle({
|
|
35
|
-
backend:
|
|
35
|
+
backend: CopyOnWrite,
|
|
36
36
|
readable,
|
|
37
|
-
writable:
|
|
37
|
+
writable: { backend: InMemory, label: 'cow' },
|
|
38
38
|
});
|
package/tests/setup/port.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { copySync, data } from '../setup.js';
|
|
|
5
5
|
const { port1: localPort, port2: remotePort } = new MessageChannel();
|
|
6
6
|
|
|
7
7
|
fs.umount('/');
|
|
8
|
-
const tmpfs = await resolveMountConfig({ backend: InMemory,
|
|
8
|
+
const tmpfs = await resolveMountConfig({ backend: InMemory, label: 'tmp' });
|
|
9
9
|
|
|
10
10
|
fs.mount('/', tmpfs);
|
|
11
11
|
copySync(data, fs);
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { ExtractProperties } from 'utilium';
|
|
2
|
-
import type { Inode, InodeLike } from '../..//internal/inode.js';
|
|
3
|
-
import type { MountConfiguration } from '../../config.js';
|
|
4
|
-
import type { File } from '../../internal/file.js';
|
|
5
|
-
import type { CreationOptions, UsageInfo } from '../../internal/filesystem.js';
|
|
6
|
-
import type { Backend, FilesystemOf } from '../backend.js';
|
|
7
|
-
import { FileSystem } from '../../internal/filesystem.js';
|
|
8
|
-
import { Stats } from '../../stats.js';
|
|
9
|
-
import * as RPC from './rpc.js';
|
|
10
|
-
type FSMethods = ExtractProperties<FileSystem, (...args: any[]) => Promise<any> | UsageInfo>;
|
|
11
|
-
type FSMethod = keyof FSMethods;
|
|
12
|
-
export type FSRequest<TMethod extends FSMethod = FSMethod> = RPC.Message & {
|
|
13
|
-
[M in TMethod]: {
|
|
14
|
-
method: M;
|
|
15
|
-
args: Parameters<FSMethods[M]>;
|
|
16
|
-
};
|
|
17
|
-
}[TMethod];
|
|
18
|
-
declare const PortFS_base: import("../../index.js").Mixin<typeof FileSystem, import("../../mixins/async.js").AsyncMixin>;
|
|
19
|
-
/**
|
|
20
|
-
* PortFS lets you access an FS instance that is running in a port, or the other way around.
|
|
21
|
-
*
|
|
22
|
-
* Note that *direct* synchronous operations are not permitted on the PortFS,
|
|
23
|
-
* regardless of the configuration option of the remote FS.
|
|
24
|
-
*/
|
|
25
|
-
export declare class PortFS extends PortFS_base {
|
|
26
|
-
readonly options: RPC.Options;
|
|
27
|
-
readonly port: RPC.Port;
|
|
28
|
-
/**`
|
|
29
|
-
* @hidden
|
|
30
|
-
*/
|
|
31
|
-
_sync: import("../index.js").StoreFS<import("../memory.js").InMemoryStore>;
|
|
32
|
-
/**
|
|
33
|
-
* Constructs a new PortFS instance that connects with the FS running on `options.port`.
|
|
34
|
-
*/
|
|
35
|
-
constructor(options: RPC.Options);
|
|
36
|
-
protected rpc<const T extends FSMethod>(method: T, ...args: Parameters<FSMethods[T]>): Promise<Awaited<ReturnType<FSMethods[T]>>>;
|
|
37
|
-
ready(): Promise<void>;
|
|
38
|
-
rename(oldPath: string, newPath: string): Promise<void>;
|
|
39
|
-
stat(path: string): Promise<Stats>;
|
|
40
|
-
sync(path: string, data: Uint8Array | undefined, stats: Readonly<InodeLike | Inode>): Promise<void>;
|
|
41
|
-
openFile(path: string, flag: string): Promise<File>;
|
|
42
|
-
createFile(path: string, flag: string, mode: number, options: CreationOptions): Promise<File>;
|
|
43
|
-
unlink(path: string): Promise<void>;
|
|
44
|
-
rmdir(path: string): Promise<void>;
|
|
45
|
-
mkdir(path: string, mode: number, options: CreationOptions): Promise<void>;
|
|
46
|
-
readdir(path: string): Promise<string[]>;
|
|
47
|
-
exists(path: string): Promise<boolean>;
|
|
48
|
-
link(srcpath: string, dstpath: string): Promise<void>;
|
|
49
|
-
read(path: string, buffer: Uint8Array, offset: number, length: number): Promise<void>;
|
|
50
|
-
write(path: string, buffer: Uint8Array, offset: number): Promise<void>;
|
|
51
|
-
}
|
|
52
|
-
/** @internal */
|
|
53
|
-
export declare function handleRequest(port: RPC.Port, fs: FileSystem & {
|
|
54
|
-
_descriptors?: Map<number, File>;
|
|
55
|
-
}, request: FSRequest): Promise<void>;
|
|
56
|
-
export declare function attachFS(port: RPC.Port, fs: FileSystem): void;
|
|
57
|
-
export declare function detachFS(port: RPC.Port, fs: FileSystem): void;
|
|
58
|
-
declare const _Port: {
|
|
59
|
-
name: string;
|
|
60
|
-
options: {
|
|
61
|
-
port: {
|
|
62
|
-
type: "object";
|
|
63
|
-
required: true;
|
|
64
|
-
validator(port: RPC.Port): void;
|
|
65
|
-
};
|
|
66
|
-
timeout: {
|
|
67
|
-
type: "number";
|
|
68
|
-
required: false;
|
|
69
|
-
};
|
|
70
|
-
};
|
|
71
|
-
create(options: RPC.Options): PortFS;
|
|
72
|
-
};
|
|
73
|
-
type _Port = typeof _Port;
|
|
74
|
-
export interface Port extends _Port {
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* @category Backends and Configuration
|
|
78
|
-
*/
|
|
79
|
-
export declare const Port: Port;
|
|
80
|
-
/**
|
|
81
|
-
* @category Backends and Configuration
|
|
82
|
-
*/
|
|
83
|
-
export declare function resolveRemoteMount<T extends Backend>(port: RPC.Port, config: MountConfiguration<T>, _depth?: number): Promise<FilesystemOf<T>>;
|
|
84
|
-
export {};
|
package/dist/backends/port/fs.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { pick } from 'utilium';
|
|
2
|
-
import { resolveMountConfig } from '../../config.js';
|
|
3
|
-
import { Errno, ErrnoError } from '../../internal/error.js';
|
|
4
|
-
import { FileSystem } from '../../internal/filesystem.js';
|
|
5
|
-
import { err, info } from '../../internal/log.js';
|
|
6
|
-
import { Async } from '../../mixins/async.js';
|
|
7
|
-
import { Stats } from '../../stats.js';
|
|
8
|
-
import { InMemory } from '../memory.js';
|
|
9
|
-
import * as RPC from './rpc.js';
|
|
10
|
-
/**
|
|
11
|
-
* PortFS lets you access an FS instance that is running in a port, or the other way around.
|
|
12
|
-
*
|
|
13
|
-
* Note that *direct* synchronous operations are not permitted on the PortFS,
|
|
14
|
-
* regardless of the configuration option of the remote FS.
|
|
15
|
-
*/
|
|
16
|
-
export class PortFS extends Async(FileSystem) {
|
|
17
|
-
/**
|
|
18
|
-
* Constructs a new PortFS instance that connects with the FS running on `options.port`.
|
|
19
|
-
*/
|
|
20
|
-
constructor(options) {
|
|
21
|
-
super(0x706f7274, 'portfs');
|
|
22
|
-
this.options = options;
|
|
23
|
-
/**`
|
|
24
|
-
* @hidden
|
|
25
|
-
*/
|
|
26
|
-
this._sync = InMemory.create({ name: 'tmpfs:port' });
|
|
27
|
-
this.port = options.port;
|
|
28
|
-
RPC.attach(this.port, RPC.handleResponse);
|
|
29
|
-
}
|
|
30
|
-
rpc(method, ...args) {
|
|
31
|
-
return RPC.request({ method, args }, {
|
|
32
|
-
...this.options,
|
|
33
|
-
fs: this,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
async ready() {
|
|
37
|
-
await this.rpc('ready');
|
|
38
|
-
await super.ready();
|
|
39
|
-
}
|
|
40
|
-
rename(oldPath, newPath) {
|
|
41
|
-
return this.rpc('rename', oldPath, newPath);
|
|
42
|
-
}
|
|
43
|
-
async stat(path) {
|
|
44
|
-
return new Stats(await this.rpc('stat', path));
|
|
45
|
-
}
|
|
46
|
-
sync(path, data, stats) {
|
|
47
|
-
stats = 'toJSON' in stats ? stats.toJSON() : stats;
|
|
48
|
-
return this.rpc('sync', path, data, stats);
|
|
49
|
-
}
|
|
50
|
-
openFile(path, flag) {
|
|
51
|
-
return this.rpc('openFile', path, flag);
|
|
52
|
-
}
|
|
53
|
-
createFile(path, flag, mode, options) {
|
|
54
|
-
return this.rpc('createFile', path, flag, mode, options);
|
|
55
|
-
}
|
|
56
|
-
unlink(path) {
|
|
57
|
-
return this.rpc('unlink', path);
|
|
58
|
-
}
|
|
59
|
-
rmdir(path) {
|
|
60
|
-
return this.rpc('rmdir', path);
|
|
61
|
-
}
|
|
62
|
-
mkdir(path, mode, options) {
|
|
63
|
-
return this.rpc('mkdir', path, mode, options);
|
|
64
|
-
}
|
|
65
|
-
readdir(path) {
|
|
66
|
-
return this.rpc('readdir', path);
|
|
67
|
-
}
|
|
68
|
-
exists(path) {
|
|
69
|
-
return this.rpc('exists', path);
|
|
70
|
-
}
|
|
71
|
-
link(srcpath, dstpath) {
|
|
72
|
-
return this.rpc('link', srcpath, dstpath);
|
|
73
|
-
}
|
|
74
|
-
async read(path, buffer, offset, length) {
|
|
75
|
-
const _buf = (await this.rpc('read', path, buffer, offset, length));
|
|
76
|
-
buffer.set(_buf);
|
|
77
|
-
}
|
|
78
|
-
write(path, buffer, offset) {
|
|
79
|
-
return this.rpc('write', path, buffer, offset);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/** @internal */
|
|
83
|
-
export async function handleRequest(port, fs, request) {
|
|
84
|
-
if (!RPC.isMessage(request))
|
|
85
|
-
return;
|
|
86
|
-
const { method, args, id, stack } = request;
|
|
87
|
-
let value, error = false;
|
|
88
|
-
try {
|
|
89
|
-
// @ts-expect-error 2556
|
|
90
|
-
value = await fs[method](...args);
|
|
91
|
-
switch (method) {
|
|
92
|
-
case 'openFile':
|
|
93
|
-
case 'createFile': {
|
|
94
|
-
value = {
|
|
95
|
-
path: args[0],
|
|
96
|
-
flag: args[1],
|
|
97
|
-
stats: await fs.stat(args[0]),
|
|
98
|
-
};
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
case 'read':
|
|
102
|
-
value = args[1];
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch (e) {
|
|
107
|
-
value = e instanceof ErrnoError ? e.toJSON() : pick(e, 'message', 'stack');
|
|
108
|
-
error = true;
|
|
109
|
-
}
|
|
110
|
-
port.postMessage({ _zenfs: true, id, error, method, stack, value });
|
|
111
|
-
}
|
|
112
|
-
export function attachFS(port, fs) {
|
|
113
|
-
RPC.attach(port, request => handleRequest(port, fs, request));
|
|
114
|
-
}
|
|
115
|
-
export function detachFS(port, fs) {
|
|
116
|
-
RPC.detach(port, request => handleRequest(port, fs, request));
|
|
117
|
-
}
|
|
118
|
-
const _Port = {
|
|
119
|
-
name: 'Port',
|
|
120
|
-
options: {
|
|
121
|
-
port: {
|
|
122
|
-
type: 'object',
|
|
123
|
-
required: true,
|
|
124
|
-
validator(port) {
|
|
125
|
-
// Check for a `postMessage` function.
|
|
126
|
-
if (typeof (port === null || port === void 0 ? void 0 : port.postMessage) != 'function') {
|
|
127
|
-
throw err(new ErrnoError(Errno.EINVAL, 'option must be a port'));
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
timeout: { type: 'number', required: false },
|
|
132
|
-
},
|
|
133
|
-
create(options) {
|
|
134
|
-
return new PortFS(options);
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* @category Backends and Configuration
|
|
139
|
-
*/
|
|
140
|
-
export const Port = _Port;
|
|
141
|
-
/**
|
|
142
|
-
* @category Backends and Configuration
|
|
143
|
-
*/
|
|
144
|
-
export async function resolveRemoteMount(port, config, _depth = 0) {
|
|
145
|
-
const stopAndReplay = RPC.catchMessages(port);
|
|
146
|
-
const fs = await resolveMountConfig(config, _depth);
|
|
147
|
-
attachFS(port, fs);
|
|
148
|
-
await stopAndReplay(fs);
|
|
149
|
-
info('Resolved remote mount: ' + fs.toString());
|
|
150
|
-
return fs;
|
|
151
|
-
}
|