@machinen/runtime 0.2.0 → 0.3.1
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/API.md +127 -136
- package/dist/index.d.ts +90 -88
- package/dist/index.js +682 -626
- package/dist/index.js.map +1 -1
- package/package.json +3 -7
- package/dist/chunk-6SP6T537.js +0 -163
- package/dist/chunk-6SP6T537.js.map +0 -1
- package/dist/mount-server-bin.d.ts +0 -2
- package/dist/mount-server-bin.js +0 -2065
- package/dist/mount-server-bin.js.map +0 -1
package/dist/mount-server-bin.js
DELETED
|
@@ -1,2065 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
MountError,
|
|
3
|
-
isMachinenError
|
|
4
|
-
} from "./chunk-6SP6T537.js";
|
|
5
|
-
|
|
6
|
-
// src/mount-server-bin.ts
|
|
7
|
-
import { renameSync, writeFileSync, unlinkSync } from "fs";
|
|
8
|
-
|
|
9
|
-
// src/mount-server.ts
|
|
10
|
-
import { createServer } from "net";
|
|
11
|
-
import { execFile as execFileCb } from "child_process";
|
|
12
|
-
import {
|
|
13
|
-
chmod,
|
|
14
|
-
lchmod,
|
|
15
|
-
lchown,
|
|
16
|
-
link,
|
|
17
|
-
lutimes,
|
|
18
|
-
mkdir,
|
|
19
|
-
open as fsOpen,
|
|
20
|
-
lstat,
|
|
21
|
-
readdir,
|
|
22
|
-
readlink,
|
|
23
|
-
realpath as realpath2,
|
|
24
|
-
rename,
|
|
25
|
-
rmdir,
|
|
26
|
-
statfs,
|
|
27
|
-
symlink,
|
|
28
|
-
truncate,
|
|
29
|
-
unlink,
|
|
30
|
-
utimes
|
|
31
|
-
} from "fs/promises";
|
|
32
|
-
import { constants as fsConstants } from "fs";
|
|
33
|
-
import { join as join2 } from "path";
|
|
34
|
-
import { promisify } from "util";
|
|
35
|
-
import debug from "debug";
|
|
36
|
-
|
|
37
|
-
// src/mount-resolver.ts
|
|
38
|
-
import { realpath } from "fs/promises";
|
|
39
|
-
import { basename, dirname, isAbsolute, join, resolve, sep } from "path";
|
|
40
|
-
async function resolveUnderRoot(rootAbs, guestRelative, opts = {}) {
|
|
41
|
-
const mustExist = opts.mustExist ?? true;
|
|
42
|
-
if (!isAbsolute(rootAbs)) {
|
|
43
|
-
throw new MountError("MOUNT_PATH_INVALID", `mount root must be absolute: ${rootAbs}`);
|
|
44
|
-
}
|
|
45
|
-
if (guestRelative.includes("\0")) {
|
|
46
|
-
throw new MountError("MOUNT_PATH_INVALID", "null byte in guest path");
|
|
47
|
-
}
|
|
48
|
-
const rootReal = await realpath(rootAbs);
|
|
49
|
-
const relStripped = guestRelative.replace(/^\/+/, "");
|
|
50
|
-
const joined = resolve(rootReal, relStripped);
|
|
51
|
-
try {
|
|
52
|
-
const targetReal = await realpath(joined);
|
|
53
|
-
assertContained(targetReal, rootReal);
|
|
54
|
-
return targetReal;
|
|
55
|
-
} catch (err) {
|
|
56
|
-
const errno = err.code;
|
|
57
|
-
if (errno !== "ENOENT" || mustExist) {
|
|
58
|
-
throw err;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const parentReal = await realpath(dirname(joined));
|
|
62
|
-
assertContained(parentReal, rootReal);
|
|
63
|
-
return join(parentReal, basename(joined));
|
|
64
|
-
}
|
|
65
|
-
function assertContained(targetReal, rootReal) {
|
|
66
|
-
if (targetReal === rootReal) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (targetReal.startsWith(rootReal + sep)) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
throw new MountError(
|
|
73
|
-
"MOUNT_PATH_ESCAPE",
|
|
74
|
-
`path escapes mount root: ${targetReal} is not under ${rootReal}`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/mount-fuse-protocol.ts
|
|
79
|
-
var FUSE_KERNEL_VERSION = 7;
|
|
80
|
-
var FUSE_KERNEL_MINOR_VERSION = 31;
|
|
81
|
-
var FUSE_OP = {
|
|
82
|
-
LOOKUP: 1,
|
|
83
|
-
FORGET: 2,
|
|
84
|
-
GETATTR: 3,
|
|
85
|
-
SETATTR: 4,
|
|
86
|
-
READLINK: 5,
|
|
87
|
-
SYMLINK: 6,
|
|
88
|
-
MKDIR: 9,
|
|
89
|
-
UNLINK: 10,
|
|
90
|
-
RMDIR: 11,
|
|
91
|
-
RENAME: 12,
|
|
92
|
-
LINK: 13,
|
|
93
|
-
OPEN: 14,
|
|
94
|
-
READ: 15,
|
|
95
|
-
WRITE: 16,
|
|
96
|
-
STATFS: 17,
|
|
97
|
-
RELEASE: 18,
|
|
98
|
-
FSYNC: 20,
|
|
99
|
-
SETXATTR: 21,
|
|
100
|
-
GETXATTR: 22,
|
|
101
|
-
LISTXATTR: 23,
|
|
102
|
-
REMOVEXATTR: 24,
|
|
103
|
-
FLUSH: 25,
|
|
104
|
-
INIT: 26,
|
|
105
|
-
OPENDIR: 27,
|
|
106
|
-
READDIR: 28,
|
|
107
|
-
RELEASEDIR: 29,
|
|
108
|
-
FSYNCDIR: 30,
|
|
109
|
-
GETLK: 31,
|
|
110
|
-
SETLK: 32,
|
|
111
|
-
SETLKW: 33,
|
|
112
|
-
ACCESS: 34,
|
|
113
|
-
CREATE: 35,
|
|
114
|
-
INTERRUPT: 36,
|
|
115
|
-
DESTROY: 38,
|
|
116
|
-
BATCH_FORGET: 42,
|
|
117
|
-
FALLOCATE: 43,
|
|
118
|
-
READDIRPLUS: 44,
|
|
119
|
-
LSEEK: 46,
|
|
120
|
-
COPY_FILE_RANGE: 47
|
|
121
|
-
};
|
|
122
|
-
var F_LCK = {
|
|
123
|
-
RDLCK: 0,
|
|
124
|
-
WRLCK: 1,
|
|
125
|
-
UNLCK: 2
|
|
126
|
-
};
|
|
127
|
-
var FUSE_LK_FLAGS = {
|
|
128
|
-
FLOCK: 1 << 0
|
|
129
|
-
};
|
|
130
|
-
var FATTR = {
|
|
131
|
-
MODE: 1 << 0,
|
|
132
|
-
UID: 1 << 1,
|
|
133
|
-
GID: 1 << 2,
|
|
134
|
-
SIZE: 1 << 3,
|
|
135
|
-
ATIME: 1 << 4,
|
|
136
|
-
MTIME: 1 << 5,
|
|
137
|
-
FH: 1 << 6,
|
|
138
|
-
ATIME_NOW: 1 << 7,
|
|
139
|
-
MTIME_NOW: 1 << 8,
|
|
140
|
-
LOCKOWNER: 1 << 9,
|
|
141
|
-
CTIME: 1 << 10
|
|
142
|
-
};
|
|
143
|
-
var FUSE_CAP = {
|
|
144
|
-
ASYNC_READ: 1 << 0,
|
|
145
|
-
POSIX_LOCKS: 1 << 1,
|
|
146
|
-
EXPORT_SUPPORT: 1 << 4,
|
|
147
|
-
BIG_WRITES: 1 << 5,
|
|
148
|
-
DONT_MASK: 1 << 6,
|
|
149
|
-
SPLICE_WRITE: 1 << 7,
|
|
150
|
-
SPLICE_MOVE: 1 << 8,
|
|
151
|
-
SPLICE_READ: 1 << 9,
|
|
152
|
-
FLOCK_LOCKS: 1 << 10,
|
|
153
|
-
HAS_IOCTL_DIR: 1 << 11,
|
|
154
|
-
AUTO_INVAL_DATA: 1 << 12,
|
|
155
|
-
DO_READDIRPLUS: 1 << 13,
|
|
156
|
-
READDIRPLUS_AUTO: 1 << 14,
|
|
157
|
-
ASYNC_DIO: 1 << 15,
|
|
158
|
-
WRITEBACK_CACHE: 1 << 16,
|
|
159
|
-
NO_OPEN_SUPPORT: 1 << 17,
|
|
160
|
-
PARALLEL_DIROPS: 1 << 18,
|
|
161
|
-
HANDLE_KILLPRIV: 1 << 19,
|
|
162
|
-
POSIX_ACL: 1 << 20,
|
|
163
|
-
ABORT_ERROR: 1 << 21,
|
|
164
|
-
MAX_PAGES: 1 << 22,
|
|
165
|
-
CACHE_SYMLINKS: 1 << 23,
|
|
166
|
-
NO_OPENDIR_SUPPORT: 1 << 24
|
|
167
|
-
};
|
|
168
|
-
var FUSE_IN_HEADER_SIZE = 40;
|
|
169
|
-
var FUSE_OUT_HEADER_SIZE = 16;
|
|
170
|
-
function readInHeader(buf, offset = 0) {
|
|
171
|
-
const dv = viewOf(buf, offset, FUSE_IN_HEADER_SIZE);
|
|
172
|
-
return {
|
|
173
|
-
len: dv.getUint32(0, true),
|
|
174
|
-
opcode: dv.getUint32(4, true),
|
|
175
|
-
unique: dv.getBigUint64(8, true),
|
|
176
|
-
nodeid: dv.getBigUint64(16, true),
|
|
177
|
-
uid: dv.getUint32(24, true),
|
|
178
|
-
gid: dv.getUint32(28, true),
|
|
179
|
-
pid: dv.getUint32(32, true)
|
|
180
|
-
// padding at offset 36
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
function writeOutHeader(buf, offset, payloadLen, error, unique) {
|
|
184
|
-
const dv = viewOf(buf, offset, FUSE_OUT_HEADER_SIZE);
|
|
185
|
-
dv.setUint32(0, FUSE_OUT_HEADER_SIZE + payloadLen, true);
|
|
186
|
-
dv.setInt32(4, error, true);
|
|
187
|
-
dv.setBigUint64(8, unique, true);
|
|
188
|
-
}
|
|
189
|
-
function buildErrorResponse(unique, error) {
|
|
190
|
-
const buf = new Uint8Array(FUSE_OUT_HEADER_SIZE);
|
|
191
|
-
writeOutHeader(buf, 0, 0, error, unique);
|
|
192
|
-
return buf;
|
|
193
|
-
}
|
|
194
|
-
function buildResponse(unique, payload) {
|
|
195
|
-
const buf = new Uint8Array(FUSE_OUT_HEADER_SIZE + payload.length);
|
|
196
|
-
writeOutHeader(buf, 0, payload.length, 0, unique);
|
|
197
|
-
buf.set(payload, FUSE_OUT_HEADER_SIZE);
|
|
198
|
-
return buf;
|
|
199
|
-
}
|
|
200
|
-
var FUSE_ATTR_SIZE = 88;
|
|
201
|
-
function writeAttr(buf, offset, a) {
|
|
202
|
-
const dv = viewOf(buf, offset, FUSE_ATTR_SIZE);
|
|
203
|
-
dv.setBigUint64(0, a.ino, true);
|
|
204
|
-
dv.setBigUint64(8, a.size, true);
|
|
205
|
-
dv.setBigUint64(16, a.blocks, true);
|
|
206
|
-
dv.setBigUint64(24, a.atime, true);
|
|
207
|
-
dv.setBigUint64(32, a.mtime, true);
|
|
208
|
-
dv.setBigUint64(40, a.ctime, true);
|
|
209
|
-
dv.setUint32(48, a.atimensec, true);
|
|
210
|
-
dv.setUint32(52, a.mtimensec, true);
|
|
211
|
-
dv.setUint32(56, a.ctimensec, true);
|
|
212
|
-
dv.setUint32(60, a.mode, true);
|
|
213
|
-
dv.setUint32(64, a.nlink, true);
|
|
214
|
-
dv.setUint32(68, a.uid, true);
|
|
215
|
-
dv.setUint32(72, a.gid, true);
|
|
216
|
-
dv.setUint32(76, a.rdev, true);
|
|
217
|
-
dv.setUint32(80, a.blksize, true);
|
|
218
|
-
dv.setUint32(84, a.flags, true);
|
|
219
|
-
}
|
|
220
|
-
var FUSE_ENTRY_OUT_SIZE = 40 + FUSE_ATTR_SIZE;
|
|
221
|
-
function buildEntryOut(e) {
|
|
222
|
-
const buf = new Uint8Array(FUSE_ENTRY_OUT_SIZE);
|
|
223
|
-
const dv = viewOf(buf, 0, 40);
|
|
224
|
-
dv.setBigUint64(0, e.nodeid, true);
|
|
225
|
-
dv.setBigUint64(8, e.generation, true);
|
|
226
|
-
dv.setBigUint64(16, e.entry_valid, true);
|
|
227
|
-
dv.setBigUint64(24, e.attr_valid, true);
|
|
228
|
-
dv.setUint32(32, e.entry_valid_nsec, true);
|
|
229
|
-
dv.setUint32(36, e.attr_valid_nsec, true);
|
|
230
|
-
writeAttr(buf, 40, e.attr);
|
|
231
|
-
return buf;
|
|
232
|
-
}
|
|
233
|
-
var FUSE_ATTR_OUT_SIZE = 16 + FUSE_ATTR_SIZE;
|
|
234
|
-
function buildAttrOut(a) {
|
|
235
|
-
const buf = new Uint8Array(FUSE_ATTR_OUT_SIZE);
|
|
236
|
-
const dv = viewOf(buf, 0, 16);
|
|
237
|
-
dv.setBigUint64(0, a.attr_valid, true);
|
|
238
|
-
dv.setUint32(8, a.attr_valid_nsec, true);
|
|
239
|
-
writeAttr(buf, 16, a.attr);
|
|
240
|
-
return buf;
|
|
241
|
-
}
|
|
242
|
-
var FUSE_KSTATFS_SIZE = 80;
|
|
243
|
-
function buildKstatfs(s) {
|
|
244
|
-
const buf = new Uint8Array(FUSE_KSTATFS_SIZE);
|
|
245
|
-
const dv = viewOf(buf, 0, FUSE_KSTATFS_SIZE);
|
|
246
|
-
dv.setBigUint64(0, s.blocks, true);
|
|
247
|
-
dv.setBigUint64(8, s.bfree, true);
|
|
248
|
-
dv.setBigUint64(16, s.bavail, true);
|
|
249
|
-
dv.setBigUint64(24, s.files, true);
|
|
250
|
-
dv.setBigUint64(32, s.ffree, true);
|
|
251
|
-
dv.setUint32(40, s.bsize, true);
|
|
252
|
-
dv.setUint32(44, s.namelen, true);
|
|
253
|
-
dv.setUint32(48, s.frsize, true);
|
|
254
|
-
return buf;
|
|
255
|
-
}
|
|
256
|
-
var FUSE_INIT_OUT_SIZE = 64;
|
|
257
|
-
function readInitIn(buf, offset = 0) {
|
|
258
|
-
const dv = viewOf(buf, offset, Math.min(buf.length - offset, 24));
|
|
259
|
-
const major = dv.getUint32(0, true);
|
|
260
|
-
const minor = dv.getUint32(4, true);
|
|
261
|
-
const hasExtended = buf.length - offset >= 20;
|
|
262
|
-
return {
|
|
263
|
-
major,
|
|
264
|
-
minor,
|
|
265
|
-
max_readahead: hasExtended ? dv.getUint32(8, true) : 0,
|
|
266
|
-
flags: hasExtended ? dv.getUint32(12, true) : 0,
|
|
267
|
-
flags2: hasExtended && dv.byteLength >= 20 ? dv.getUint32(16, true) : 0
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function buildInitOut(o) {
|
|
271
|
-
const buf = new Uint8Array(FUSE_INIT_OUT_SIZE);
|
|
272
|
-
const dv = viewOf(buf, 0, FUSE_INIT_OUT_SIZE);
|
|
273
|
-
dv.setUint32(0, o.major, true);
|
|
274
|
-
dv.setUint32(4, o.minor, true);
|
|
275
|
-
dv.setUint32(8, o.max_readahead, true);
|
|
276
|
-
dv.setUint32(12, o.flags, true);
|
|
277
|
-
dv.setUint16(16, o.max_background, true);
|
|
278
|
-
dv.setUint16(18, o.congestion_threshold, true);
|
|
279
|
-
dv.setUint32(20, o.max_write, true);
|
|
280
|
-
dv.setUint32(24, o.time_gran, true);
|
|
281
|
-
dv.setUint16(28, o.max_pages, true);
|
|
282
|
-
dv.setUint16(30, o.map_alignment, true);
|
|
283
|
-
dv.setUint32(32, o.flags2, true);
|
|
284
|
-
return buf;
|
|
285
|
-
}
|
|
286
|
-
function readForgetIn(buf, offset = 0) {
|
|
287
|
-
const dv = viewOf(buf, offset, 8);
|
|
288
|
-
return { nlookup: dv.getBigUint64(0, true) };
|
|
289
|
-
}
|
|
290
|
-
function readBatchForgetIn(buf, offset = 0) {
|
|
291
|
-
const dv = viewOf(buf, offset, 8);
|
|
292
|
-
const count = dv.getUint32(0, true);
|
|
293
|
-
const entries = [];
|
|
294
|
-
for (let i = 0; i < count; i++) {
|
|
295
|
-
const entryOffset = offset + 8 + i * 16;
|
|
296
|
-
const entryDv = viewOf(buf, entryOffset, 16);
|
|
297
|
-
entries.push({
|
|
298
|
-
nodeid: entryDv.getBigUint64(0, true),
|
|
299
|
-
nlookup: entryDv.getBigUint64(8, true)
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
return { count, entries };
|
|
303
|
-
}
|
|
304
|
-
function readOpenIn(buf, offset = 0) {
|
|
305
|
-
const dv = viewOf(buf, offset, 8);
|
|
306
|
-
return {
|
|
307
|
-
flags: dv.getUint32(0, true),
|
|
308
|
-
open_flags: dv.getUint32(4, true)
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
function buildOpenOut(o) {
|
|
312
|
-
const buf = new Uint8Array(16);
|
|
313
|
-
const dv = viewOf(buf, 0, 16);
|
|
314
|
-
dv.setBigUint64(0, o.fh, true);
|
|
315
|
-
dv.setUint32(8, o.open_flags, true);
|
|
316
|
-
return buf;
|
|
317
|
-
}
|
|
318
|
-
function readReadIn(buf, off = 0) {
|
|
319
|
-
const dv = viewOf(buf, off, 40);
|
|
320
|
-
return {
|
|
321
|
-
fh: dv.getBigUint64(0, true),
|
|
322
|
-
offset: dv.getBigUint64(8, true),
|
|
323
|
-
size: dv.getUint32(16, true),
|
|
324
|
-
read_flags: dv.getUint32(20, true),
|
|
325
|
-
lock_owner: dv.getBigUint64(24, true),
|
|
326
|
-
flags: dv.getUint32(32, true)
|
|
327
|
-
// padding at 36
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
function readReleaseIn(buf, offset = 0) {
|
|
331
|
-
const dv = viewOf(buf, offset, 24);
|
|
332
|
-
return {
|
|
333
|
-
fh: dv.getBigUint64(0, true),
|
|
334
|
-
flags: dv.getUint32(8, true),
|
|
335
|
-
release_flags: dv.getUint32(12, true),
|
|
336
|
-
lock_owner: dv.getBigUint64(16, true)
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
function buildDirent(params) {
|
|
340
|
-
const nameBytes = new TextEncoder().encode(params.name);
|
|
341
|
-
const paddedNameLen = nameBytes.length + 7 & ~7;
|
|
342
|
-
const buf = new Uint8Array(24 + paddedNameLen);
|
|
343
|
-
const dv = viewOf(buf, 0, 24);
|
|
344
|
-
dv.setBigUint64(0, params.ino, true);
|
|
345
|
-
dv.setBigUint64(8, params.off, true);
|
|
346
|
-
dv.setUint32(16, nameBytes.length, true);
|
|
347
|
-
dv.setUint32(20, params.type, true);
|
|
348
|
-
buf.set(nameBytes, 24);
|
|
349
|
-
return buf;
|
|
350
|
-
}
|
|
351
|
-
var DT = {
|
|
352
|
-
UNKNOWN: 0,
|
|
353
|
-
FIFO: 1,
|
|
354
|
-
CHR: 2,
|
|
355
|
-
DIR: 4,
|
|
356
|
-
BLK: 6,
|
|
357
|
-
REG: 8,
|
|
358
|
-
LNK: 10,
|
|
359
|
-
SOCK: 12
|
|
360
|
-
};
|
|
361
|
-
var FUSE_WRITE_IN_SIZE = 40;
|
|
362
|
-
function readWriteIn(buf, off = 0) {
|
|
363
|
-
const dv = viewOf(buf, off, FUSE_WRITE_IN_SIZE);
|
|
364
|
-
return {
|
|
365
|
-
fh: dv.getBigUint64(0, true),
|
|
366
|
-
offset: dv.getBigUint64(8, true),
|
|
367
|
-
size: dv.getUint32(16, true),
|
|
368
|
-
write_flags: dv.getUint32(20, true),
|
|
369
|
-
lock_owner: dv.getBigUint64(24, true),
|
|
370
|
-
flags: dv.getUint32(32, true)
|
|
371
|
-
// padding at 36
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
function buildWriteOut(o) {
|
|
375
|
-
const buf = new Uint8Array(8);
|
|
376
|
-
const dv = viewOf(buf, 0, 8);
|
|
377
|
-
dv.setUint32(0, o.size, true);
|
|
378
|
-
return buf;
|
|
379
|
-
}
|
|
380
|
-
var FUSE_CREATE_IN_SIZE = 16;
|
|
381
|
-
function readCreateIn(buf, off = 0) {
|
|
382
|
-
const dv = viewOf(buf, off, FUSE_CREATE_IN_SIZE);
|
|
383
|
-
return {
|
|
384
|
-
flags: dv.getUint32(0, true),
|
|
385
|
-
mode: dv.getUint32(4, true),
|
|
386
|
-
umask: dv.getUint32(8, true),
|
|
387
|
-
open_flags: dv.getUint32(12, true)
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
function buildCreateOut(entry, open) {
|
|
391
|
-
const entryBuf = buildEntryOut(entry);
|
|
392
|
-
const openBuf = buildOpenOut(open);
|
|
393
|
-
const out = new Uint8Array(entryBuf.length + openBuf.length);
|
|
394
|
-
out.set(entryBuf, 0);
|
|
395
|
-
out.set(openBuf, entryBuf.length);
|
|
396
|
-
return out;
|
|
397
|
-
}
|
|
398
|
-
var FUSE_SETATTR_IN_SIZE = 88;
|
|
399
|
-
function readSetattrIn(buf, off = 0) {
|
|
400
|
-
const dv = viewOf(buf, off, FUSE_SETATTR_IN_SIZE);
|
|
401
|
-
return {
|
|
402
|
-
valid: dv.getUint32(0, true),
|
|
403
|
-
// padding at 4
|
|
404
|
-
fh: dv.getBigUint64(8, true),
|
|
405
|
-
size: dv.getBigUint64(16, true),
|
|
406
|
-
lock_owner: dv.getBigUint64(24, true),
|
|
407
|
-
atime: dv.getBigUint64(32, true),
|
|
408
|
-
mtime: dv.getBigUint64(40, true),
|
|
409
|
-
ctime: dv.getBigUint64(48, true),
|
|
410
|
-
atimensec: dv.getUint32(56, true),
|
|
411
|
-
mtimensec: dv.getUint32(60, true),
|
|
412
|
-
ctimensec: dv.getUint32(64, true),
|
|
413
|
-
mode: dv.getUint32(68, true),
|
|
414
|
-
// unused4 at 72
|
|
415
|
-
uid: dv.getUint32(76, true),
|
|
416
|
-
gid: dv.getUint32(80, true)
|
|
417
|
-
// unused5 at 84
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
var FUSE_MKDIR_IN_SIZE = 8;
|
|
421
|
-
function readMkdirIn(buf, off = 0) {
|
|
422
|
-
const dv = viewOf(buf, off, FUSE_MKDIR_IN_SIZE);
|
|
423
|
-
return {
|
|
424
|
-
mode: dv.getUint32(0, true),
|
|
425
|
-
umask: dv.getUint32(4, true)
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
var FUSE_LINK_IN_SIZE = 8;
|
|
429
|
-
function readLinkIn(buf, off = 0) {
|
|
430
|
-
const dv = viewOf(buf, off, FUSE_LINK_IN_SIZE);
|
|
431
|
-
return {
|
|
432
|
-
oldnodeid: dv.getBigUint64(0, true)
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
var FUSE_FALLOCATE_IN_SIZE = 32;
|
|
436
|
-
function readFallocateIn(buf, off = 0) {
|
|
437
|
-
const dv = viewOf(buf, off, FUSE_FALLOCATE_IN_SIZE);
|
|
438
|
-
return {
|
|
439
|
-
fh: dv.getBigUint64(0, true),
|
|
440
|
-
offset: dv.getBigUint64(8, true),
|
|
441
|
-
length: dv.getBigUint64(16, true),
|
|
442
|
-
mode: dv.getUint32(24, true)
|
|
443
|
-
// padding at 28
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
var FALLOC_FL = {
|
|
447
|
-
KEEP_SIZE: 1,
|
|
448
|
-
PUNCH_HOLE: 2,
|
|
449
|
-
COLLAPSE_RANGE: 8,
|
|
450
|
-
ZERO_RANGE: 16,
|
|
451
|
-
INSERT_RANGE: 32
|
|
452
|
-
};
|
|
453
|
-
var FUSE_LSEEK_IN_SIZE = 24;
|
|
454
|
-
function readLseekIn(buf, off = 0) {
|
|
455
|
-
const dv = viewOf(buf, off, FUSE_LSEEK_IN_SIZE);
|
|
456
|
-
return {
|
|
457
|
-
fh: dv.getBigUint64(0, true),
|
|
458
|
-
offset: dv.getBigUint64(8, true),
|
|
459
|
-
whence: dv.getUint32(16, true)
|
|
460
|
-
// padding at 20
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
function buildLseekOut(o) {
|
|
464
|
-
const buf = new Uint8Array(8);
|
|
465
|
-
new DataView(buf.buffer).setBigUint64(0, o.offset, true);
|
|
466
|
-
return buf;
|
|
467
|
-
}
|
|
468
|
-
var SEEK = {
|
|
469
|
-
SET: 0,
|
|
470
|
-
CUR: 1,
|
|
471
|
-
END: 2,
|
|
472
|
-
DATA: 3,
|
|
473
|
-
HOLE: 4
|
|
474
|
-
};
|
|
475
|
-
var FUSE_COPY_FILE_RANGE_IN_SIZE = 56;
|
|
476
|
-
function readCopyFileRangeIn(buf, off = 0) {
|
|
477
|
-
const dv = viewOf(buf, off, FUSE_COPY_FILE_RANGE_IN_SIZE);
|
|
478
|
-
return {
|
|
479
|
-
fh_in: dv.getBigUint64(0, true),
|
|
480
|
-
off_in: dv.getBigUint64(8, true),
|
|
481
|
-
nodeid_out: dv.getBigUint64(16, true),
|
|
482
|
-
fh_out: dv.getBigUint64(24, true),
|
|
483
|
-
off_out: dv.getBigUint64(32, true),
|
|
484
|
-
len: dv.getBigUint64(40, true),
|
|
485
|
-
flags: dv.getBigUint64(48, true)
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
var FUSE_RENAME_IN_SIZE = 8;
|
|
489
|
-
function readRenameIn(buf, off = 0) {
|
|
490
|
-
const dv = viewOf(buf, off, FUSE_RENAME_IN_SIZE);
|
|
491
|
-
return {
|
|
492
|
-
newdir: dv.getBigUint64(0, true)
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
var FUSE_FILE_LOCK_SIZE = 24;
|
|
496
|
-
function readFileLock(buf, offset) {
|
|
497
|
-
const dv = viewOf(buf, offset, FUSE_FILE_LOCK_SIZE);
|
|
498
|
-
return {
|
|
499
|
-
start: dv.getBigUint64(0, true),
|
|
500
|
-
end: dv.getBigUint64(8, true),
|
|
501
|
-
type: dv.getUint32(16, true),
|
|
502
|
-
pid: dv.getUint32(20, true)
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
function writeFileLock(buf, offset, lk) {
|
|
506
|
-
const dv = viewOf(buf, offset, FUSE_FILE_LOCK_SIZE);
|
|
507
|
-
dv.setBigUint64(0, lk.start, true);
|
|
508
|
-
dv.setBigUint64(8, lk.end, true);
|
|
509
|
-
dv.setUint32(16, lk.type, true);
|
|
510
|
-
dv.setUint32(20, lk.pid, true);
|
|
511
|
-
}
|
|
512
|
-
var FUSE_LK_IN_SIZE = 48;
|
|
513
|
-
function readLkIn(buf, offset = 0) {
|
|
514
|
-
const dv = viewOf(buf, offset, FUSE_LK_IN_SIZE);
|
|
515
|
-
return {
|
|
516
|
-
fh: dv.getBigUint64(0, true),
|
|
517
|
-
owner: dv.getBigUint64(8, true),
|
|
518
|
-
lk: readFileLock(buf, offset + 16),
|
|
519
|
-
lk_flags: dv.getUint32(40, true)
|
|
520
|
-
// padding at offset 44
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
var FUSE_LK_OUT_SIZE = FUSE_FILE_LOCK_SIZE;
|
|
524
|
-
function buildLkOut(lk) {
|
|
525
|
-
const buf = new Uint8Array(FUSE_LK_OUT_SIZE);
|
|
526
|
-
writeFileLock(buf, 0, lk);
|
|
527
|
-
return buf;
|
|
528
|
-
}
|
|
529
|
-
var FUSE_SETXATTR_IN_SIZE = 8;
|
|
530
|
-
function readSetxattrIn(buf, off = 0) {
|
|
531
|
-
const dv = viewOf(buf, off, FUSE_SETXATTR_IN_SIZE);
|
|
532
|
-
return {
|
|
533
|
-
size: dv.getUint32(0, true),
|
|
534
|
-
flags: dv.getUint32(4, true)
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
var XATTR = {
|
|
538
|
-
CREATE: 1,
|
|
539
|
-
REPLACE: 2
|
|
540
|
-
};
|
|
541
|
-
var FUSE_GETXATTR_IN_SIZE = 8;
|
|
542
|
-
function readGetxattrIn(buf, off = 0) {
|
|
543
|
-
const dv = viewOf(buf, off, FUSE_GETXATTR_IN_SIZE);
|
|
544
|
-
return {
|
|
545
|
-
size: dv.getUint32(0, true)
|
|
546
|
-
// padding at offset 4
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
var FUSE_GETXATTR_OUT_SIZE = 8;
|
|
550
|
-
function buildGetxattrOut(size) {
|
|
551
|
-
const buf = new Uint8Array(FUSE_GETXATTR_OUT_SIZE);
|
|
552
|
-
new DataView(buf.buffer).setUint32(0, size, true);
|
|
553
|
-
return buf;
|
|
554
|
-
}
|
|
555
|
-
function viewOf(buf, offset, len) {
|
|
556
|
-
if (offset < 0 || len < 0 || offset + len > buf.length) {
|
|
557
|
-
throw new Error(
|
|
558
|
-
`FUSE buffer underflow: need ${len} bytes at offset ${offset}, have ${buf.length}`
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
return new DataView(buf.buffer, buf.byteOffset + offset, len);
|
|
562
|
-
}
|
|
563
|
-
function payloadOf(message) {
|
|
564
|
-
if (message.length < FUSE_IN_HEADER_SIZE) {
|
|
565
|
-
throw new Error(`FUSE message too short: ${message.length} < header ${FUSE_IN_HEADER_SIZE}`);
|
|
566
|
-
}
|
|
567
|
-
return message.subarray(FUSE_IN_HEADER_SIZE);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// src/mount-server.ts
|
|
571
|
-
var execFile = promisify(execFileCb);
|
|
572
|
-
var log = debug("machinen:mount-server");
|
|
573
|
-
var ERRNO = {
|
|
574
|
-
EPERM: 1,
|
|
575
|
-
ENOENT: 2,
|
|
576
|
-
EINTR: 4,
|
|
577
|
-
EIO: 5,
|
|
578
|
-
ENXIO: 6,
|
|
579
|
-
EBADF: 9,
|
|
580
|
-
EAGAIN: 11,
|
|
581
|
-
EACCES: 13,
|
|
582
|
-
EBUSY: 16,
|
|
583
|
-
EEXIST: 17,
|
|
584
|
-
ENOTDIR: 20,
|
|
585
|
-
EISDIR: 21,
|
|
586
|
-
EINVAL: 22,
|
|
587
|
-
ERANGE: 34,
|
|
588
|
-
EROFS: 30,
|
|
589
|
-
ENOSYS: 38,
|
|
590
|
-
ENOTEMPTY: 39,
|
|
591
|
-
// "Attribute not found" — Linux errno for getxattr/removexattr on a
|
|
592
|
-
// name that doesn't exist. macOS calls the same condition ENOATTR
|
|
593
|
-
// (errno 93) and our shell-out translates it to ENODATA on the wire.
|
|
594
|
-
ENODATA: 61,
|
|
595
|
-
// ENOTSUP / EOPNOTSUPP share value 95 on Linux. Some macOS-host
|
|
596
|
-
// ops (lchmod on filesystems without symlink-mode support, lutimes
|
|
597
|
-
// on older APFS) throw ENOTSUP; pass it through verbatim so tar
|
|
598
|
-
// and friends see a soft failure rather than the EIO catch-all.
|
|
599
|
-
ENOTSUP: 95,
|
|
600
|
-
ESTALE: 116
|
|
601
|
-
};
|
|
602
|
-
async function serveLiveMount(udsPath, opts) {
|
|
603
|
-
const state = createState(opts.rootAbs, opts.mode ?? "rw");
|
|
604
|
-
const server = createServer((sock) => void handleConnection(sock, state));
|
|
605
|
-
await new Promise((done, fail) => {
|
|
606
|
-
server.once("error", fail);
|
|
607
|
-
server.listen(udsPath, () => {
|
|
608
|
-
server.off("error", fail);
|
|
609
|
-
done();
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
log("listening udsPath=%s rootAbs=%s mode=%s", udsPath, opts.rootAbs, state.mode);
|
|
613
|
-
return {
|
|
614
|
-
stop: () => shutdown(server, state),
|
|
615
|
-
bytesServedOnPagesImg: () => state.bytesServedOnPagesImg
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
function createState(rootAbs, mode) {
|
|
619
|
-
const inodes = /* @__PURE__ */ new Map();
|
|
620
|
-
inodes.set(1n, { relPath: "", nlookup: Number.POSITIVE_INFINITY });
|
|
621
|
-
return {
|
|
622
|
-
rootAbs,
|
|
623
|
-
mode,
|
|
624
|
-
inodes,
|
|
625
|
-
nextInode: 2n,
|
|
626
|
-
handles: /* @__PURE__ */ new Map(),
|
|
627
|
-
nextHandle: 1n,
|
|
628
|
-
socket: null,
|
|
629
|
-
bytesServedOnPagesImg: 0,
|
|
630
|
-
locks: /* @__PURE__ */ new Map(),
|
|
631
|
-
lockWaiters: []
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
var PAGES_IMG_RE = /(?:^|\/)pages-\d+\.img$/;
|
|
635
|
-
function isPagesImgPath(relPath) {
|
|
636
|
-
return PAGES_IMG_RE.test(relPath);
|
|
637
|
-
}
|
|
638
|
-
async function shutdown(server, state) {
|
|
639
|
-
state.socket?.destroy();
|
|
640
|
-
state.socket = null;
|
|
641
|
-
for (const [, entry] of state.handles) {
|
|
642
|
-
if (entry.kind === "file") {
|
|
643
|
-
await entry.fh.close().catch(() => {
|
|
644
|
-
});
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
state.handles.clear();
|
|
648
|
-
cancelAllWaiters(state);
|
|
649
|
-
state.locks.clear();
|
|
650
|
-
await new Promise((done) => server.close(() => done()));
|
|
651
|
-
}
|
|
652
|
-
async function handleConnection(sock, state) {
|
|
653
|
-
if (state.socket) {
|
|
654
|
-
sock.destroy();
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
state.socket = sock;
|
|
658
|
-
sock.on("close", () => {
|
|
659
|
-
state.socket = null;
|
|
660
|
-
cancelAllWaiters(state);
|
|
661
|
-
state.locks.clear();
|
|
662
|
-
});
|
|
663
|
-
sock.on("error", (err) => log("socket error: %s", err.message));
|
|
664
|
-
try {
|
|
665
|
-
for await (const msg of readFrames(sock)) {
|
|
666
|
-
void dispatch(msg, state, sock);
|
|
667
|
-
}
|
|
668
|
-
} catch (err) {
|
|
669
|
-
log("frame read error: %s", err.message);
|
|
670
|
-
sock.destroy();
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
async function* readFrames(sock) {
|
|
674
|
-
let pending = Buffer.alloc(0);
|
|
675
|
-
for await (const chunk of sock) {
|
|
676
|
-
pending = pending.length === 0 ? Buffer.from(chunk) : Buffer.concat([pending, chunk]);
|
|
677
|
-
while (pending.length >= 4) {
|
|
678
|
-
const len = pending.readUInt32LE(0);
|
|
679
|
-
if (len < FUSE_IN_HEADER_SIZE) {
|
|
680
|
-
throw new Error(`FUSE framing: len ${len} below header size ${FUSE_IN_HEADER_SIZE}`);
|
|
681
|
-
}
|
|
682
|
-
if (pending.length < len) {
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
yield new Uint8Array(pending.buffer, pending.byteOffset, len).slice();
|
|
686
|
-
pending = pending.subarray(len);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
if (pending.length > 0) {
|
|
690
|
-
throw new Error(`FUSE framing: ${pending.length} trailing bytes after EOF`);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
async function dispatch(msg, state, sock) {
|
|
694
|
-
const hdr = readInHeader(msg);
|
|
695
|
-
log("op=%d nodeid=%s unique=%s len=%d", hdr.opcode, hdr.nodeid, hdr.unique, hdr.len);
|
|
696
|
-
let reply;
|
|
697
|
-
try {
|
|
698
|
-
reply = await handle(hdr, msg, state);
|
|
699
|
-
} catch (err) {
|
|
700
|
-
const errno = mapErrorToErrno(err);
|
|
701
|
-
log("op=%d \u2192 errno %d (%s)", hdr.opcode, errno, err.message);
|
|
702
|
-
reply = buildErrorResponse(hdr.unique, errno);
|
|
703
|
-
}
|
|
704
|
-
if (reply && !sock.destroyed) {
|
|
705
|
-
sock.write(reply);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
async function handle(hdr, msg, state) {
|
|
709
|
-
switch (hdr.opcode) {
|
|
710
|
-
case FUSE_OP.INIT:
|
|
711
|
-
return onInit(hdr, msg);
|
|
712
|
-
case FUSE_OP.DESTROY:
|
|
713
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
714
|
-
case FUSE_OP.INTERRUPT:
|
|
715
|
-
return null;
|
|
716
|
-
// no reply ever
|
|
717
|
-
case FUSE_OP.FORGET:
|
|
718
|
-
onForget(hdr, msg, state);
|
|
719
|
-
return null;
|
|
720
|
-
case FUSE_OP.BATCH_FORGET:
|
|
721
|
-
onBatchForget(msg, state);
|
|
722
|
-
return null;
|
|
723
|
-
case FUSE_OP.LOOKUP:
|
|
724
|
-
return await onLookup(hdr, msg, state);
|
|
725
|
-
case FUSE_OP.GETATTR:
|
|
726
|
-
return await onGetattr(hdr, state);
|
|
727
|
-
case FUSE_OP.READLINK:
|
|
728
|
-
return await onReadlink(hdr, state);
|
|
729
|
-
case FUSE_OP.SYMLINK:
|
|
730
|
-
return await onSymlink(hdr, msg, state);
|
|
731
|
-
case FUSE_OP.LINK:
|
|
732
|
-
return await onLink(hdr, msg, state);
|
|
733
|
-
case FUSE_OP.SETATTR:
|
|
734
|
-
return await onSetattr(hdr, msg, state);
|
|
735
|
-
case FUSE_OP.STATFS:
|
|
736
|
-
return await onStatfs(hdr, state);
|
|
737
|
-
case FUSE_OP.OPEN:
|
|
738
|
-
return await onOpen(hdr, msg, state);
|
|
739
|
-
case FUSE_OP.READ:
|
|
740
|
-
return await onRead(hdr, msg, state);
|
|
741
|
-
case FUSE_OP.WRITE:
|
|
742
|
-
return await onWrite(hdr, msg, state);
|
|
743
|
-
case FUSE_OP.CREATE:
|
|
744
|
-
return await onCreate(hdr, msg, state);
|
|
745
|
-
case FUSE_OP.MKDIR:
|
|
746
|
-
return await onMkdir(hdr, msg, state);
|
|
747
|
-
case FUSE_OP.RMDIR:
|
|
748
|
-
return await onRmdir(hdr, msg, state);
|
|
749
|
-
case FUSE_OP.UNLINK:
|
|
750
|
-
return await onUnlink(hdr, msg, state);
|
|
751
|
-
case FUSE_OP.RENAME:
|
|
752
|
-
return await onRename(hdr, msg, state);
|
|
753
|
-
case FUSE_OP.RELEASE:
|
|
754
|
-
return await onRelease(hdr, msg, state);
|
|
755
|
-
case FUSE_OP.OPENDIR:
|
|
756
|
-
return await onOpendir(hdr, state);
|
|
757
|
-
case FUSE_OP.READDIR:
|
|
758
|
-
return await onReaddir(hdr, msg, state);
|
|
759
|
-
case FUSE_OP.RELEASEDIR:
|
|
760
|
-
return onReleasedir(hdr, msg, state);
|
|
761
|
-
case FUSE_OP.FSYNC:
|
|
762
|
-
return await onFsync(hdr, msg, state);
|
|
763
|
-
case FUSE_OP.FSYNCDIR:
|
|
764
|
-
return await onFsyncdir(hdr, msg, state);
|
|
765
|
-
case FUSE_OP.FALLOCATE:
|
|
766
|
-
return await onFallocate(hdr, msg, state);
|
|
767
|
-
case FUSE_OP.LSEEK:
|
|
768
|
-
return await onLseek(hdr, msg, state);
|
|
769
|
-
case FUSE_OP.COPY_FILE_RANGE:
|
|
770
|
-
return await onCopyFileRange(hdr, msg, state);
|
|
771
|
-
case FUSE_OP.SETXATTR:
|
|
772
|
-
return await onSetxattr(hdr, msg, state);
|
|
773
|
-
case FUSE_OP.GETXATTR:
|
|
774
|
-
return await onGetxattr(hdr, msg, state);
|
|
775
|
-
case FUSE_OP.LISTXATTR:
|
|
776
|
-
return await onListxattr(hdr, msg, state);
|
|
777
|
-
case FUSE_OP.REMOVEXATTR:
|
|
778
|
-
return await onRemovexattr(hdr, msg, state);
|
|
779
|
-
case FUSE_OP.GETLK:
|
|
780
|
-
return onGetlk(hdr, msg, state);
|
|
781
|
-
case FUSE_OP.SETLK:
|
|
782
|
-
return onSetlk(hdr, msg, state);
|
|
783
|
-
case FUSE_OP.SETLKW:
|
|
784
|
-
return await onSetlkw(hdr, msg, state);
|
|
785
|
-
case FUSE_OP.FLUSH:
|
|
786
|
-
case FUSE_OP.ACCESS:
|
|
787
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
788
|
-
default:
|
|
789
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ENOSYS);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
function onInit(hdr, msg) {
|
|
793
|
-
const init = readInitIn(payloadOf(msg));
|
|
794
|
-
const minor = Math.min(init.minor, FUSE_KERNEL_MINOR_VERSION);
|
|
795
|
-
const supported = FUSE_CAP.ASYNC_READ | FUSE_CAP.BIG_WRITES | FUSE_CAP.EXPORT_SUPPORT | FUSE_CAP.MAX_PAGES | FUSE_CAP.PARALLEL_DIROPS | FUSE_CAP.POSIX_LOCKS;
|
|
796
|
-
const flags = init.flags & supported;
|
|
797
|
-
const out = buildInitOut({
|
|
798
|
-
major: FUSE_KERNEL_VERSION,
|
|
799
|
-
minor,
|
|
800
|
-
max_readahead: init.max_readahead,
|
|
801
|
-
flags,
|
|
802
|
-
max_background: 16,
|
|
803
|
-
congestion_threshold: 12,
|
|
804
|
-
max_write: 131072,
|
|
805
|
-
time_gran: 1,
|
|
806
|
-
max_pages: 32,
|
|
807
|
-
// 128 KiB / 4 KiB page
|
|
808
|
-
map_alignment: 0,
|
|
809
|
-
flags2: 0
|
|
810
|
-
});
|
|
811
|
-
return buildResponse(hdr.unique, out);
|
|
812
|
-
}
|
|
813
|
-
function onForget(hdr, msg, state) {
|
|
814
|
-
const { nlookup } = readForgetIn(payloadOf(msg));
|
|
815
|
-
decrefInode(state, hdr.nodeid, Number(nlookup));
|
|
816
|
-
}
|
|
817
|
-
function onBatchForget(msg, state) {
|
|
818
|
-
const { entries } = readBatchForgetIn(payloadOf(msg));
|
|
819
|
-
for (const e of entries) {
|
|
820
|
-
decrefInode(state, e.nodeid, Number(e.nlookup));
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
async function onLookup(hdr, msg, state) {
|
|
824
|
-
const body = payloadOf(msg);
|
|
825
|
-
const name = decodeName(body);
|
|
826
|
-
validateName(name);
|
|
827
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
828
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
829
|
-
const childAbs = join2(parentAbs, name);
|
|
830
|
-
const childRel = joinRel(parent.relPath, name);
|
|
831
|
-
const st = await lstat(childAbs);
|
|
832
|
-
const ino = bindInode(state, childRel);
|
|
833
|
-
return buildResponse(
|
|
834
|
-
hdr.unique,
|
|
835
|
-
buildEntryOut({
|
|
836
|
-
nodeid: ino,
|
|
837
|
-
generation: 0n,
|
|
838
|
-
entry_valid: 1n,
|
|
839
|
-
attr_valid: 1n,
|
|
840
|
-
entry_valid_nsec: 0,
|
|
841
|
-
attr_valid_nsec: 0,
|
|
842
|
-
attr: statToAttr(st, ino)
|
|
843
|
-
})
|
|
844
|
-
);
|
|
845
|
-
}
|
|
846
|
-
async function onGetattr(hdr, state) {
|
|
847
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
848
|
-
const abs = await absPathForLstat(state, entry);
|
|
849
|
-
const st = await lstat(abs);
|
|
850
|
-
return buildResponse(
|
|
851
|
-
hdr.unique,
|
|
852
|
-
buildAttrOut({
|
|
853
|
-
attr_valid: 1n,
|
|
854
|
-
attr_valid_nsec: 0,
|
|
855
|
-
attr: statToAttr(st, hdr.nodeid)
|
|
856
|
-
})
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
async function onReadlink(hdr, state) {
|
|
860
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
861
|
-
const abs = await absPathForLstat(state, entry);
|
|
862
|
-
const target = await readlink(abs);
|
|
863
|
-
return buildResponse(hdr.unique, new TextEncoder().encode(target));
|
|
864
|
-
}
|
|
865
|
-
async function onStatfs(hdr, state) {
|
|
866
|
-
try {
|
|
867
|
-
const s = await statfs(state.rootAbs);
|
|
868
|
-
return buildResponse(
|
|
869
|
-
hdr.unique,
|
|
870
|
-
buildKstatfs({
|
|
871
|
-
blocks: BigInt(s.blocks),
|
|
872
|
-
bfree: BigInt(s.bfree),
|
|
873
|
-
bavail: BigInt(s.bavail),
|
|
874
|
-
files: BigInt(s.files),
|
|
875
|
-
ffree: BigInt(s.ffree),
|
|
876
|
-
bsize: s.bsize,
|
|
877
|
-
namelen: 255,
|
|
878
|
-
frsize: s.bsize
|
|
879
|
-
})
|
|
880
|
-
);
|
|
881
|
-
} catch {
|
|
882
|
-
return buildResponse(
|
|
883
|
-
hdr.unique,
|
|
884
|
-
buildKstatfs({
|
|
885
|
-
blocks: 1000000n,
|
|
886
|
-
bfree: 500000n,
|
|
887
|
-
bavail: 500000n,
|
|
888
|
-
files: 100000n,
|
|
889
|
-
ffree: 99000n,
|
|
890
|
-
bsize: 4096,
|
|
891
|
-
namelen: 255,
|
|
892
|
-
frsize: 4096
|
|
893
|
-
})
|
|
894
|
-
);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
async function onOpen(hdr, msg, state) {
|
|
898
|
-
const req = readOpenIn(payloadOf(msg));
|
|
899
|
-
const accessMode = req.flags & 3;
|
|
900
|
-
if (accessMode !== 0 && state.mode === "ro") {
|
|
901
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
902
|
-
}
|
|
903
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
904
|
-
const abs = await absPathForTraversal(state, entry);
|
|
905
|
-
const fh = await fsOpen(abs, linuxOpenFlagsToNode(req.flags));
|
|
906
|
-
const id = state.nextHandle++;
|
|
907
|
-
state.handles.set(id, {
|
|
908
|
-
kind: "file",
|
|
909
|
-
fh,
|
|
910
|
-
lazyPagesContrib: isPagesImgPath(entry.relPath),
|
|
911
|
-
nodeid: hdr.nodeid
|
|
912
|
-
});
|
|
913
|
-
return buildResponse(hdr.unique, buildOpenOut({ fh: id, open_flags: 0 }));
|
|
914
|
-
}
|
|
915
|
-
async function onRead(hdr, msg, state) {
|
|
916
|
-
const req = readReadIn(payloadOf(msg));
|
|
917
|
-
const handle2 = state.handles.get(req.fh);
|
|
918
|
-
if (!handle2 || handle2.kind !== "file") {
|
|
919
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
920
|
-
}
|
|
921
|
-
const buf = Buffer.alloc(req.size);
|
|
922
|
-
const { bytesRead } = await handle2.fh.read(buf, 0, req.size, Number(req.offset));
|
|
923
|
-
if (handle2.lazyPagesContrib && bytesRead > 0) {
|
|
924
|
-
state.bytesServedOnPagesImg += bytesRead;
|
|
925
|
-
}
|
|
926
|
-
return buildResponse(hdr.unique, new Uint8Array(buf.buffer, buf.byteOffset, bytesRead));
|
|
927
|
-
}
|
|
928
|
-
async function onRelease(hdr, msg, state) {
|
|
929
|
-
const rel = readReleaseIn(payloadOf(msg));
|
|
930
|
-
const entry = state.handles.get(rel.fh);
|
|
931
|
-
if (entry && entry.kind === "file") {
|
|
932
|
-
state.handles.delete(rel.fh);
|
|
933
|
-
dropLocksFor(state, entry.nodeid, rel.lock_owner);
|
|
934
|
-
await entry.fh.close().catch(() => {
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
938
|
-
}
|
|
939
|
-
async function onOpendir(hdr, state) {
|
|
940
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
941
|
-
const abs = await absPathForTraversal(state, entry);
|
|
942
|
-
const names = await readdir(abs);
|
|
943
|
-
const packed = [];
|
|
944
|
-
for (const name of names) {
|
|
945
|
-
try {
|
|
946
|
-
const st = await lstat(join2(abs, name));
|
|
947
|
-
packed.push(
|
|
948
|
-
buildDirent({
|
|
949
|
-
// ino in READDIR is informational. We use the host ino so
|
|
950
|
-
// the kernel can short-circuit LOOKUPs; if the guest does
|
|
951
|
-
// LOOKUP anyway we'll allocate our own then.
|
|
952
|
-
ino: BigInt(st.ino),
|
|
953
|
-
off: BigInt(packed.length + 1),
|
|
954
|
-
type: modeToDT(st.mode),
|
|
955
|
-
name
|
|
956
|
-
})
|
|
957
|
-
);
|
|
958
|
-
} catch (err) {
|
|
959
|
-
log("opendir: skip %s: %s", name, err.message);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
const id = state.nextHandle++;
|
|
963
|
-
state.handles.set(id, { kind: "dir", entries: packed });
|
|
964
|
-
return buildResponse(hdr.unique, buildOpenOut({ fh: id, open_flags: 0 }));
|
|
965
|
-
}
|
|
966
|
-
async function onReaddir(hdr, msg, state) {
|
|
967
|
-
const req = readReadIn(payloadOf(msg));
|
|
968
|
-
const entry = state.handles.get(req.fh);
|
|
969
|
-
if (!entry || entry.kind !== "dir") {
|
|
970
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
971
|
-
}
|
|
972
|
-
const startIdx = Number(req.offset);
|
|
973
|
-
let total = 0;
|
|
974
|
-
const picked = [];
|
|
975
|
-
for (let i = startIdx; i < entry.entries.length; i++) {
|
|
976
|
-
const e = entry.entries[i];
|
|
977
|
-
if (total + e.length > req.size) {
|
|
978
|
-
break;
|
|
979
|
-
}
|
|
980
|
-
picked.push(e);
|
|
981
|
-
total += e.length;
|
|
982
|
-
}
|
|
983
|
-
const payload = concatBuffers(picked, total);
|
|
984
|
-
return buildResponse(hdr.unique, payload);
|
|
985
|
-
}
|
|
986
|
-
function onReleasedir(hdr, msg, state) {
|
|
987
|
-
const { fh } = readReleaseIn(payloadOf(msg));
|
|
988
|
-
state.handles.delete(fh);
|
|
989
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
990
|
-
}
|
|
991
|
-
async function onWrite(hdr, msg, state) {
|
|
992
|
-
if (state.mode === "ro") {
|
|
993
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
994
|
-
}
|
|
995
|
-
const body = payloadOf(msg);
|
|
996
|
-
const req = readWriteIn(body);
|
|
997
|
-
const data = body.subarray(FUSE_WRITE_IN_SIZE, FUSE_WRITE_IN_SIZE + req.size);
|
|
998
|
-
const handle2 = state.handles.get(req.fh);
|
|
999
|
-
if (!handle2 || handle2.kind !== "file") {
|
|
1000
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1001
|
-
}
|
|
1002
|
-
const buf = Buffer.from(data.buffer, data.byteOffset, data.length);
|
|
1003
|
-
const { bytesWritten } = await handle2.fh.write(buf, 0, data.length, Number(req.offset));
|
|
1004
|
-
return buildResponse(hdr.unique, buildWriteOut({ size: bytesWritten }));
|
|
1005
|
-
}
|
|
1006
|
-
async function onCreate(hdr, msg, state) {
|
|
1007
|
-
if (state.mode === "ro") {
|
|
1008
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1009
|
-
}
|
|
1010
|
-
const body = payloadOf(msg);
|
|
1011
|
-
const req = readCreateIn(body);
|
|
1012
|
-
const name = decodeName(body.subarray(16));
|
|
1013
|
-
validateName(name);
|
|
1014
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
1015
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
1016
|
-
const childAbs = join2(parentAbs, name);
|
|
1017
|
-
const childRel = joinRel(parent.relPath, name);
|
|
1018
|
-
const nodeFlags = linuxOpenFlagsToNode(req.flags) | fsConstants.O_CREAT;
|
|
1019
|
-
const fh = await fsOpen(childAbs, nodeFlags, req.mode);
|
|
1020
|
-
const st = await fh.stat();
|
|
1021
|
-
const ino = bindInode(state, childRel);
|
|
1022
|
-
const id = state.nextHandle++;
|
|
1023
|
-
state.handles.set(id, {
|
|
1024
|
-
kind: "file",
|
|
1025
|
-
fh,
|
|
1026
|
-
lazyPagesContrib: isPagesImgPath(childRel),
|
|
1027
|
-
nodeid: ino
|
|
1028
|
-
});
|
|
1029
|
-
return buildResponse(
|
|
1030
|
-
hdr.unique,
|
|
1031
|
-
buildCreateOut(
|
|
1032
|
-
{
|
|
1033
|
-
nodeid: ino,
|
|
1034
|
-
generation: 0n,
|
|
1035
|
-
entry_valid: 1n,
|
|
1036
|
-
attr_valid: 1n,
|
|
1037
|
-
entry_valid_nsec: 0,
|
|
1038
|
-
attr_valid_nsec: 0,
|
|
1039
|
-
attr: statToAttr(st, ino)
|
|
1040
|
-
},
|
|
1041
|
-
{ fh: id, open_flags: 0 }
|
|
1042
|
-
)
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
async function onSymlink(hdr, msg, state) {
|
|
1046
|
-
if (state.mode === "ro") {
|
|
1047
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1048
|
-
}
|
|
1049
|
-
const body = payloadOf(msg);
|
|
1050
|
-
const firstNul = body.indexOf(0);
|
|
1051
|
-
if (firstNul < 0) {
|
|
1052
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1053
|
-
}
|
|
1054
|
-
const name = decodeName(body.subarray(0, firstNul));
|
|
1055
|
-
validateName(name);
|
|
1056
|
-
const targetEnd = body.indexOf(0, firstNul + 1);
|
|
1057
|
-
const target = decodeName(body.subarray(firstNul + 1, targetEnd < 0 ? body.length : targetEnd));
|
|
1058
|
-
if (target === "" || target.includes("\0")) {
|
|
1059
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1060
|
-
}
|
|
1061
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
1062
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
1063
|
-
const childAbs = join2(parentAbs, name);
|
|
1064
|
-
const childRel = joinRel(parent.relPath, name);
|
|
1065
|
-
await symlink(target, childAbs);
|
|
1066
|
-
const st = await lstat(childAbs);
|
|
1067
|
-
const ino = bindInode(state, childRel);
|
|
1068
|
-
return buildResponse(
|
|
1069
|
-
hdr.unique,
|
|
1070
|
-
buildEntryOut({
|
|
1071
|
-
nodeid: ino,
|
|
1072
|
-
generation: 0n,
|
|
1073
|
-
entry_valid: 1n,
|
|
1074
|
-
attr_valid: 1n,
|
|
1075
|
-
entry_valid_nsec: 0,
|
|
1076
|
-
attr_valid_nsec: 0,
|
|
1077
|
-
attr: statToAttr(st, ino)
|
|
1078
|
-
})
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
async function onLink(hdr, msg, state) {
|
|
1082
|
-
if (state.mode === "ro") {
|
|
1083
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1084
|
-
}
|
|
1085
|
-
const body = payloadOf(msg);
|
|
1086
|
-
const req = readLinkIn(body);
|
|
1087
|
-
const name = decodeName(body.subarray(8));
|
|
1088
|
-
validateName(name);
|
|
1089
|
-
const oldEntry = requireInode(state, req.oldnodeid);
|
|
1090
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
1091
|
-
const oldAbs = await absPathForLstat(state, oldEntry);
|
|
1092
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
1093
|
-
const childAbs = join2(parentAbs, name);
|
|
1094
|
-
const childRel = joinRel(parent.relPath, name);
|
|
1095
|
-
await link(oldAbs, childAbs);
|
|
1096
|
-
const st = await lstat(childAbs);
|
|
1097
|
-
const ino = bindInode(state, childRel);
|
|
1098
|
-
return buildResponse(
|
|
1099
|
-
hdr.unique,
|
|
1100
|
-
buildEntryOut({
|
|
1101
|
-
nodeid: ino,
|
|
1102
|
-
generation: 0n,
|
|
1103
|
-
entry_valid: 1n,
|
|
1104
|
-
attr_valid: 1n,
|
|
1105
|
-
entry_valid_nsec: 0,
|
|
1106
|
-
attr_valid_nsec: 0,
|
|
1107
|
-
attr: statToAttr(st, ino)
|
|
1108
|
-
})
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
async function onMkdir(hdr, msg, state) {
|
|
1112
|
-
if (state.mode === "ro") {
|
|
1113
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1114
|
-
}
|
|
1115
|
-
const body = payloadOf(msg);
|
|
1116
|
-
const req = readMkdirIn(body);
|
|
1117
|
-
const name = decodeName(body.subarray(8));
|
|
1118
|
-
validateName(name);
|
|
1119
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
1120
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
1121
|
-
const childAbs = join2(parentAbs, name);
|
|
1122
|
-
const childRel = joinRel(parent.relPath, name);
|
|
1123
|
-
await mkdir(childAbs, { mode: req.mode & 4095 });
|
|
1124
|
-
const st = await lstat(childAbs);
|
|
1125
|
-
const ino = bindInode(state, childRel);
|
|
1126
|
-
return buildResponse(
|
|
1127
|
-
hdr.unique,
|
|
1128
|
-
buildEntryOut({
|
|
1129
|
-
nodeid: ino,
|
|
1130
|
-
generation: 0n,
|
|
1131
|
-
entry_valid: 1n,
|
|
1132
|
-
attr_valid: 1n,
|
|
1133
|
-
entry_valid_nsec: 0,
|
|
1134
|
-
attr_valid_nsec: 0,
|
|
1135
|
-
attr: statToAttr(st, ino)
|
|
1136
|
-
})
|
|
1137
|
-
);
|
|
1138
|
-
}
|
|
1139
|
-
async function onRmdir(hdr, msg, state) {
|
|
1140
|
-
if (state.mode === "ro") {
|
|
1141
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1142
|
-
}
|
|
1143
|
-
const name = decodeName(payloadOf(msg));
|
|
1144
|
-
validateName(name);
|
|
1145
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
1146
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
1147
|
-
await rmdir(join2(parentAbs, name));
|
|
1148
|
-
forgetByRelPath(state, joinRel(parent.relPath, name));
|
|
1149
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1150
|
-
}
|
|
1151
|
-
async function onUnlink(hdr, msg, state) {
|
|
1152
|
-
if (state.mode === "ro") {
|
|
1153
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1154
|
-
}
|
|
1155
|
-
const name = decodeName(payloadOf(msg));
|
|
1156
|
-
validateName(name);
|
|
1157
|
-
const parent = requireInode(state, hdr.nodeid);
|
|
1158
|
-
const parentAbs = await absPathForTraversal(state, parent);
|
|
1159
|
-
await unlink(join2(parentAbs, name));
|
|
1160
|
-
forgetByRelPath(state, joinRel(parent.relPath, name));
|
|
1161
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1162
|
-
}
|
|
1163
|
-
async function onRename(hdr, msg, state) {
|
|
1164
|
-
if (state.mode === "ro") {
|
|
1165
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1166
|
-
}
|
|
1167
|
-
const body = payloadOf(msg);
|
|
1168
|
-
const req = readRenameIn(body);
|
|
1169
|
-
const after = body.subarray(8);
|
|
1170
|
-
const firstNul = after.indexOf(0);
|
|
1171
|
-
if (firstNul < 0) {
|
|
1172
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1173
|
-
}
|
|
1174
|
-
const oldName = decodeName(after.subarray(0, firstNul));
|
|
1175
|
-
const newName = decodeName(after.subarray(firstNul + 1));
|
|
1176
|
-
validateName(oldName);
|
|
1177
|
-
validateName(newName);
|
|
1178
|
-
const oldParent = requireInode(state, hdr.nodeid);
|
|
1179
|
-
const newParent = requireInode(state, req.newdir);
|
|
1180
|
-
const oldParentAbs = await absPathForTraversal(state, oldParent);
|
|
1181
|
-
const newParentAbs = await absPathForTraversal(state, newParent);
|
|
1182
|
-
await rename(join2(oldParentAbs, oldName), join2(newParentAbs, newName));
|
|
1183
|
-
forgetByRelPath(state, joinRel(oldParent.relPath, oldName));
|
|
1184
|
-
forgetByRelPath(state, joinRel(newParent.relPath, newName));
|
|
1185
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1186
|
-
}
|
|
1187
|
-
async function onSetattr(hdr, msg, state) {
|
|
1188
|
-
const req = readSetattrIn(payloadOf(msg));
|
|
1189
|
-
const isMutating = (req.valid & (FATTR.MODE | FATTR.SIZE | FATTR.ATIME | FATTR.MTIME | FATTR.CTIME | FATTR.ATIME_NOW | FATTR.MTIME_NOW | FATTR.UID | FATTR.GID)) !== 0;
|
|
1190
|
-
if (isMutating && state.mode === "ro") {
|
|
1191
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1192
|
-
}
|
|
1193
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
1194
|
-
const abs = await absPathForLstat(state, entry);
|
|
1195
|
-
const st0 = await lstat(abs);
|
|
1196
|
-
const isSymlink = (st0.mode & 61440) === 40960;
|
|
1197
|
-
if (req.valid & FATTR.SIZE) {
|
|
1198
|
-
if (isSymlink) {
|
|
1199
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1200
|
-
}
|
|
1201
|
-
if (req.valid & FATTR.FH) {
|
|
1202
|
-
const handle2 = state.handles.get(req.fh);
|
|
1203
|
-
if (!handle2 || handle2.kind !== "file") {
|
|
1204
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1205
|
-
}
|
|
1206
|
-
await handle2.fh.truncate(Number(req.size));
|
|
1207
|
-
} else {
|
|
1208
|
-
await truncate(abs, Number(req.size));
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
if (req.valid & FATTR.MODE) {
|
|
1212
|
-
const mode = req.mode & 4095;
|
|
1213
|
-
if (isSymlink) {
|
|
1214
|
-
try {
|
|
1215
|
-
await lchmod(abs, mode);
|
|
1216
|
-
} catch (err) {
|
|
1217
|
-
if (!isUnsupportedFsErr(err)) {
|
|
1218
|
-
throw err;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
} else {
|
|
1222
|
-
await chmod(abs, mode);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
if (req.valid & (FATTR.ATIME | FATTR.MTIME | FATTR.ATIME_NOW | FATTR.MTIME_NOW)) {
|
|
1226
|
-
const now = Date.now() / 1e3;
|
|
1227
|
-
const a = req.valid & FATTR.ATIME_NOW ? now : req.valid & FATTR.ATIME ? Number(req.atime) + req.atimensec / 1e9 : st0.atimeMs / 1e3;
|
|
1228
|
-
const m = req.valid & FATTR.MTIME_NOW ? now : req.valid & FATTR.MTIME ? Number(req.mtime) + req.mtimensec / 1e9 : st0.mtimeMs / 1e3;
|
|
1229
|
-
if (isSymlink) {
|
|
1230
|
-
await lutimes(abs, a, m);
|
|
1231
|
-
} else {
|
|
1232
|
-
await utimes(abs, a, m);
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
if (req.valid & (FATTR.UID | FATTR.GID)) {
|
|
1236
|
-
if (isSymlink) {
|
|
1237
|
-
try {
|
|
1238
|
-
await lchown(abs, req.uid, req.gid);
|
|
1239
|
-
} catch (err) {
|
|
1240
|
-
const code = err.code;
|
|
1241
|
-
if (code !== "EPERM" && code !== "ENOSYS" && !isUnsupportedFsErr(err)) {
|
|
1242
|
-
throw err;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
const st = await lstat(abs);
|
|
1248
|
-
return buildResponse(
|
|
1249
|
-
hdr.unique,
|
|
1250
|
-
buildAttrOut({
|
|
1251
|
-
attr_valid: 1n,
|
|
1252
|
-
attr_valid_nsec: 0,
|
|
1253
|
-
attr: statToAttr(st, hdr.nodeid)
|
|
1254
|
-
})
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
function isUnsupportedFsErr(err) {
|
|
1258
|
-
const code = err?.code;
|
|
1259
|
-
return code === "ENOTSUP" || code === "EOPNOTSUPP" || code === "ENOSYS";
|
|
1260
|
-
}
|
|
1261
|
-
async function onFsync(hdr, msg, state) {
|
|
1262
|
-
const body = payloadOf(msg);
|
|
1263
|
-
if (body.length < 8) {
|
|
1264
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1265
|
-
}
|
|
1266
|
-
const dv = new DataView(body.buffer, body.byteOffset, body.length);
|
|
1267
|
-
const fh = dv.getBigUint64(0, true);
|
|
1268
|
-
const handle2 = state.handles.get(fh);
|
|
1269
|
-
if (!handle2 || handle2.kind !== "file") {
|
|
1270
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1271
|
-
}
|
|
1272
|
-
await handle2.fh.sync();
|
|
1273
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1274
|
-
}
|
|
1275
|
-
async function onFsyncdir(hdr, msg, state) {
|
|
1276
|
-
const body = payloadOf(msg);
|
|
1277
|
-
if (body.length < 8) {
|
|
1278
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1279
|
-
}
|
|
1280
|
-
const dv = new DataView(body.buffer, body.byteOffset, body.length);
|
|
1281
|
-
const fh = dv.getBigUint64(0, true);
|
|
1282
|
-
const handle2 = state.handles.get(fh);
|
|
1283
|
-
if (!handle2 || handle2.kind !== "dir") {
|
|
1284
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1285
|
-
}
|
|
1286
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
1287
|
-
const abs = await absPathForTraversal(state, entry);
|
|
1288
|
-
const dirFh = await fsOpen(abs, fsConstants.O_RDONLY);
|
|
1289
|
-
try {
|
|
1290
|
-
await dirFh.sync();
|
|
1291
|
-
} finally {
|
|
1292
|
-
await dirFh.close().catch(() => {
|
|
1293
|
-
});
|
|
1294
|
-
}
|
|
1295
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1296
|
-
}
|
|
1297
|
-
async function onFallocate(hdr, msg, state) {
|
|
1298
|
-
if (state.mode === "ro") {
|
|
1299
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1300
|
-
}
|
|
1301
|
-
const req = readFallocateIn(payloadOf(msg));
|
|
1302
|
-
const handle2 = state.handles.get(req.fh);
|
|
1303
|
-
if (!handle2 || handle2.kind !== "file") {
|
|
1304
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1305
|
-
}
|
|
1306
|
-
const unsupported = FALLOC_FL.PUNCH_HOLE | FALLOC_FL.COLLAPSE_RANGE | FALLOC_FL.ZERO_RANGE | FALLOC_FL.INSERT_RANGE;
|
|
1307
|
-
if (req.mode & unsupported) {
|
|
1308
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ENOSYS);
|
|
1309
|
-
}
|
|
1310
|
-
if (!(req.mode & FALLOC_FL.KEEP_SIZE)) {
|
|
1311
|
-
const target = Number(req.offset + req.length);
|
|
1312
|
-
const st = await handle2.fh.stat();
|
|
1313
|
-
if (target > st.size) {
|
|
1314
|
-
await handle2.fh.truncate(target);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1318
|
-
}
|
|
1319
|
-
async function onLseek(hdr, msg, state) {
|
|
1320
|
-
const req = readLseekIn(payloadOf(msg));
|
|
1321
|
-
const handle2 = state.handles.get(req.fh);
|
|
1322
|
-
if (!handle2 || handle2.kind !== "file") {
|
|
1323
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1324
|
-
}
|
|
1325
|
-
let resolved;
|
|
1326
|
-
switch (req.whence) {
|
|
1327
|
-
case SEEK.SET:
|
|
1328
|
-
resolved = req.offset;
|
|
1329
|
-
break;
|
|
1330
|
-
case SEEK.CUR:
|
|
1331
|
-
resolved = req.offset;
|
|
1332
|
-
break;
|
|
1333
|
-
case SEEK.END: {
|
|
1334
|
-
const st = await handle2.fh.stat();
|
|
1335
|
-
resolved = BigInt(st.size) + req.offset;
|
|
1336
|
-
break;
|
|
1337
|
-
}
|
|
1338
|
-
case SEEK.HOLE:
|
|
1339
|
-
case SEEK.DATA:
|
|
1340
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ENOSYS);
|
|
1341
|
-
default:
|
|
1342
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1343
|
-
}
|
|
1344
|
-
if (resolved < 0n) {
|
|
1345
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1346
|
-
}
|
|
1347
|
-
return buildResponse(hdr.unique, buildLseekOut({ offset: resolved }));
|
|
1348
|
-
}
|
|
1349
|
-
var COPY_FILE_RANGE_CHUNK = 1 << 20;
|
|
1350
|
-
async function onCopyFileRange(hdr, msg, state) {
|
|
1351
|
-
if (state.mode === "ro") {
|
|
1352
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1353
|
-
}
|
|
1354
|
-
const req = readCopyFileRangeIn(payloadOf(msg));
|
|
1355
|
-
if (req.flags !== 0n) {
|
|
1356
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1357
|
-
}
|
|
1358
|
-
const src = state.handles.get(req.fh_in);
|
|
1359
|
-
const dst = state.handles.get(req.fh_out);
|
|
1360
|
-
if (!src || src.kind !== "file" || !dst || dst.kind !== "file") {
|
|
1361
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
|
|
1362
|
-
}
|
|
1363
|
-
let copied = 0;
|
|
1364
|
-
let srcOff = Number(req.off_in);
|
|
1365
|
-
let dstOff = Number(req.off_out);
|
|
1366
|
-
const total = Number(req.len);
|
|
1367
|
-
const buf = Buffer.allocUnsafe(Math.min(total, COPY_FILE_RANGE_CHUNK));
|
|
1368
|
-
while (copied < total) {
|
|
1369
|
-
const want = Math.min(total - copied, buf.length);
|
|
1370
|
-
const { bytesRead } = await src.fh.read(buf, 0, want, srcOff);
|
|
1371
|
-
if (bytesRead === 0) {
|
|
1372
|
-
break;
|
|
1373
|
-
}
|
|
1374
|
-
const { bytesWritten } = await dst.fh.write(buf, 0, bytesRead, dstOff);
|
|
1375
|
-
copied += bytesWritten;
|
|
1376
|
-
srcOff += bytesRead;
|
|
1377
|
-
dstOff += bytesWritten;
|
|
1378
|
-
if (bytesWritten < bytesRead) {
|
|
1379
|
-
break;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
return buildResponse(hdr.unique, buildWriteOut({ size: copied }));
|
|
1383
|
-
}
|
|
1384
|
-
var IS_DARWIN = process.platform === "darwin";
|
|
1385
|
-
function isXattrError(e) {
|
|
1386
|
-
return typeof e === "object" && e !== null && "errno" in e && typeof e.errno === "number";
|
|
1387
|
-
}
|
|
1388
|
-
function decodeXattrStderr(stderr) {
|
|
1389
|
-
if (/no such (xattr|attribute)|no data available/i.test(stderr)) {
|
|
1390
|
-
return ERRNO.ENODATA;
|
|
1391
|
-
}
|
|
1392
|
-
if (/operation not permitted|permission denied/i.test(stderr)) {
|
|
1393
|
-
return ERRNO.EACCES;
|
|
1394
|
-
}
|
|
1395
|
-
if (/not supported|operation not supported/i.test(stderr)) {
|
|
1396
|
-
return ERRNO.ENOTSUP;
|
|
1397
|
-
}
|
|
1398
|
-
if (/file name too long|argument list too long/i.test(stderr)) {
|
|
1399
|
-
return ERRNO.EINVAL;
|
|
1400
|
-
}
|
|
1401
|
-
return ERRNO.EIO;
|
|
1402
|
-
}
|
|
1403
|
-
function xattrErrorFromExec(err) {
|
|
1404
|
-
const e = err;
|
|
1405
|
-
if (e.code === "ENOENT") {
|
|
1406
|
-
return { errno: ERRNO.ENOTSUP };
|
|
1407
|
-
}
|
|
1408
|
-
const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8") ?? "";
|
|
1409
|
-
return { errno: decodeXattrStderr(stderr) };
|
|
1410
|
-
}
|
|
1411
|
-
async function hostGetxattr(absPath, name) {
|
|
1412
|
-
try {
|
|
1413
|
-
if (IS_DARWIN) {
|
|
1414
|
-
const { stdout: stdout2 } = await execFile("xattr", ["-p", "-s", "-x", name, absPath], {
|
|
1415
|
-
encoding: "utf8"
|
|
1416
|
-
});
|
|
1417
|
-
return Buffer.from(stdout2.replace(/\s+/g, ""), "hex");
|
|
1418
|
-
}
|
|
1419
|
-
const { stdout } = await execFile(
|
|
1420
|
-
"getfattr",
|
|
1421
|
-
["--no-dereference", "--absolute-names", "--only-values", "-n", name, absPath],
|
|
1422
|
-
{ encoding: "buffer" }
|
|
1423
|
-
);
|
|
1424
|
-
return stdout;
|
|
1425
|
-
} catch (err) {
|
|
1426
|
-
const xe = xattrErrorFromExec(err);
|
|
1427
|
-
if (xe.errno === ERRNO.ENODATA) {
|
|
1428
|
-
return null;
|
|
1429
|
-
}
|
|
1430
|
-
throw xe;
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
async function hostXattrExists(absPath, name) {
|
|
1434
|
-
return await hostGetxattr(absPath, name) !== null;
|
|
1435
|
-
}
|
|
1436
|
-
async function hostSetxattr(absPath, name, value) {
|
|
1437
|
-
try {
|
|
1438
|
-
if (IS_DARWIN) {
|
|
1439
|
-
const hex = Buffer.from(value.buffer, value.byteOffset, value.length).toString("hex");
|
|
1440
|
-
await execFile("xattr", ["-w", "-s", "-x", name, hex, absPath]);
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
const b64 = Buffer.from(value.buffer, value.byteOffset, value.length).toString("base64");
|
|
1444
|
-
await execFile("setfattr", ["--no-dereference", "-n", name, "-v", `0s${b64}`, absPath]);
|
|
1445
|
-
} catch (err) {
|
|
1446
|
-
throw xattrErrorFromExec(err);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
async function hostListxattr(absPath) {
|
|
1450
|
-
try {
|
|
1451
|
-
if (IS_DARWIN) {
|
|
1452
|
-
const { stdout: stdout2 } = await execFile("xattr", ["-s", absPath], { encoding: "utf8" });
|
|
1453
|
-
return stdout2.split("\n").filter((n) => n.length > 0);
|
|
1454
|
-
}
|
|
1455
|
-
const { stdout } = await execFile(
|
|
1456
|
-
"getfattr",
|
|
1457
|
-
["--no-dereference", "--absolute-names", "--match=.", absPath],
|
|
1458
|
-
{ encoding: "utf8" }
|
|
1459
|
-
);
|
|
1460
|
-
return stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
1461
|
-
} catch (err) {
|
|
1462
|
-
const xe = xattrErrorFromExec(err);
|
|
1463
|
-
if (xe.errno === ERRNO.ENODATA) {
|
|
1464
|
-
return [];
|
|
1465
|
-
}
|
|
1466
|
-
throw xe;
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
async function hostRemovexattr(absPath, name) {
|
|
1470
|
-
try {
|
|
1471
|
-
if (IS_DARWIN) {
|
|
1472
|
-
await execFile("xattr", ["-d", "-s", name, absPath]);
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
await execFile("setfattr", ["--no-dereference", "-x", name, absPath]);
|
|
1476
|
-
} catch (err) {
|
|
1477
|
-
throw xattrErrorFromExec(err);
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
function packListxattrNames(names) {
|
|
1481
|
-
const encoded = names.map((n) => Buffer.from(`${n}\0`, "utf8"));
|
|
1482
|
-
const total = encoded.reduce((sum, b) => sum + b.length, 0);
|
|
1483
|
-
const out = new Uint8Array(total);
|
|
1484
|
-
let cursor = 0;
|
|
1485
|
-
for (const b of encoded) {
|
|
1486
|
-
out.set(b, cursor);
|
|
1487
|
-
cursor += b.length;
|
|
1488
|
-
}
|
|
1489
|
-
return out;
|
|
1490
|
-
}
|
|
1491
|
-
async function onSetxattr(hdr, msg, state) {
|
|
1492
|
-
if (state.mode === "ro") {
|
|
1493
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1494
|
-
}
|
|
1495
|
-
const body = payloadOf(msg);
|
|
1496
|
-
const req = readSetxattrIn(body);
|
|
1497
|
-
const after = body.subarray(FUSE_SETXATTR_IN_SIZE);
|
|
1498
|
-
const nulAt = after.indexOf(0);
|
|
1499
|
-
if (nulAt < 0) {
|
|
1500
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1501
|
-
}
|
|
1502
|
-
const name = new TextDecoder("utf-8", { fatal: false }).decode(after.subarray(0, nulAt));
|
|
1503
|
-
if (name === "" || name.length > 255) {
|
|
1504
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1505
|
-
}
|
|
1506
|
-
const valueStart = nulAt + 1;
|
|
1507
|
-
if (valueStart + req.size > after.length) {
|
|
1508
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1509
|
-
}
|
|
1510
|
-
const value = after.subarray(valueStart, valueStart + req.size);
|
|
1511
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
1512
|
-
const abs = await absPathForLstat(state, entry);
|
|
1513
|
-
if (req.flags & XATTR.CREATE && await hostXattrExists(abs, name)) {
|
|
1514
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EEXIST);
|
|
1515
|
-
}
|
|
1516
|
-
if (req.flags & XATTR.REPLACE && !await hostXattrExists(abs, name)) {
|
|
1517
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ENODATA);
|
|
1518
|
-
}
|
|
1519
|
-
try {
|
|
1520
|
-
await hostSetxattr(abs, name, value);
|
|
1521
|
-
} catch (err) {
|
|
1522
|
-
if (isXattrError(err)) {
|
|
1523
|
-
return buildErrorResponse(hdr.unique, -err.errno);
|
|
1524
|
-
}
|
|
1525
|
-
throw err;
|
|
1526
|
-
}
|
|
1527
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1528
|
-
}
|
|
1529
|
-
async function onGetxattr(hdr, msg, state) {
|
|
1530
|
-
const body = payloadOf(msg);
|
|
1531
|
-
const req = readGetxattrIn(body);
|
|
1532
|
-
const after = body.subarray(8);
|
|
1533
|
-
const nulAt = after.indexOf(0);
|
|
1534
|
-
if (nulAt < 0) {
|
|
1535
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1536
|
-
}
|
|
1537
|
-
const name = new TextDecoder("utf-8", { fatal: false }).decode(after.subarray(0, nulAt));
|
|
1538
|
-
if (name === "" || name.length > 255) {
|
|
1539
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1540
|
-
}
|
|
1541
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
1542
|
-
const abs = await absPathForLstat(state, entry);
|
|
1543
|
-
let value;
|
|
1544
|
-
try {
|
|
1545
|
-
value = await hostGetxattr(abs, name);
|
|
1546
|
-
} catch (err) {
|
|
1547
|
-
if (isXattrError(err)) {
|
|
1548
|
-
return buildErrorResponse(hdr.unique, -err.errno);
|
|
1549
|
-
}
|
|
1550
|
-
throw err;
|
|
1551
|
-
}
|
|
1552
|
-
if (value === null) {
|
|
1553
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ENODATA);
|
|
1554
|
-
}
|
|
1555
|
-
if (req.size === 0) {
|
|
1556
|
-
return buildResponse(hdr.unique, buildGetxattrOut(value.length));
|
|
1557
|
-
}
|
|
1558
|
-
if (value.length > req.size) {
|
|
1559
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ERANGE);
|
|
1560
|
-
}
|
|
1561
|
-
return buildResponse(hdr.unique, new Uint8Array(value.buffer, value.byteOffset, value.length));
|
|
1562
|
-
}
|
|
1563
|
-
async function onListxattr(hdr, msg, state) {
|
|
1564
|
-
const req = readGetxattrIn(payloadOf(msg));
|
|
1565
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
1566
|
-
const abs = await absPathForLstat(state, entry);
|
|
1567
|
-
let names;
|
|
1568
|
-
try {
|
|
1569
|
-
names = await hostListxattr(abs);
|
|
1570
|
-
} catch (err) {
|
|
1571
|
-
if (isXattrError(err)) {
|
|
1572
|
-
return buildErrorResponse(hdr.unique, -err.errno);
|
|
1573
|
-
}
|
|
1574
|
-
throw err;
|
|
1575
|
-
}
|
|
1576
|
-
const packed = packListxattrNames(names);
|
|
1577
|
-
if (req.size === 0) {
|
|
1578
|
-
return buildResponse(hdr.unique, buildGetxattrOut(packed.length));
|
|
1579
|
-
}
|
|
1580
|
-
if (packed.length > req.size) {
|
|
1581
|
-
return buildErrorResponse(hdr.unique, -ERRNO.ERANGE);
|
|
1582
|
-
}
|
|
1583
|
-
return buildResponse(hdr.unique, packed);
|
|
1584
|
-
}
|
|
1585
|
-
async function onRemovexattr(hdr, msg, state) {
|
|
1586
|
-
if (state.mode === "ro") {
|
|
1587
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
|
|
1588
|
-
}
|
|
1589
|
-
const name = decodeName(payloadOf(msg));
|
|
1590
|
-
if (name === "" || name.length > 255) {
|
|
1591
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1592
|
-
}
|
|
1593
|
-
const entry = requireInode(state, hdr.nodeid);
|
|
1594
|
-
const abs = await absPathForLstat(state, entry);
|
|
1595
|
-
try {
|
|
1596
|
-
await hostRemovexattr(abs, name);
|
|
1597
|
-
} catch (err) {
|
|
1598
|
-
if (isXattrError(err)) {
|
|
1599
|
-
return buildErrorResponse(hdr.unique, -err.errno);
|
|
1600
|
-
}
|
|
1601
|
-
throw err;
|
|
1602
|
-
}
|
|
1603
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1604
|
-
}
|
|
1605
|
-
function rangeFromLkIn(lk, owner) {
|
|
1606
|
-
if (lk.type !== F_LCK.RDLCK && lk.type !== F_LCK.WRLCK) {
|
|
1607
|
-
return null;
|
|
1608
|
-
}
|
|
1609
|
-
return { start: lk.start, end: lk.end, type: lk.type, owner, pid: lk.pid };
|
|
1610
|
-
}
|
|
1611
|
-
function rangesOverlap(a, b) {
|
|
1612
|
-
return a.start <= b.end && b.start <= a.end;
|
|
1613
|
-
}
|
|
1614
|
-
function findConflict(state, nodeid, req) {
|
|
1615
|
-
const ranges = state.locks.get(nodeid);
|
|
1616
|
-
if (!ranges) {
|
|
1617
|
-
return null;
|
|
1618
|
-
}
|
|
1619
|
-
for (const r of ranges) {
|
|
1620
|
-
if (r.owner === req.owner) {
|
|
1621
|
-
continue;
|
|
1622
|
-
}
|
|
1623
|
-
if (!rangesOverlap(r, req)) {
|
|
1624
|
-
continue;
|
|
1625
|
-
}
|
|
1626
|
-
if (req.type === F_LCK.WRLCK || r.type === F_LCK.WRLCK) {
|
|
1627
|
-
return r;
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
return null;
|
|
1631
|
-
}
|
|
1632
|
-
function addLock(state, nodeid, req) {
|
|
1633
|
-
let ranges = state.locks.get(nodeid);
|
|
1634
|
-
if (!ranges) {
|
|
1635
|
-
ranges = [];
|
|
1636
|
-
state.locks.set(nodeid, ranges);
|
|
1637
|
-
}
|
|
1638
|
-
let merged = { ...req };
|
|
1639
|
-
const kept = [];
|
|
1640
|
-
for (const r of ranges) {
|
|
1641
|
-
if (r.owner !== merged.owner || !rangesOverlap(r, merged)) {
|
|
1642
|
-
kept.push(r);
|
|
1643
|
-
continue;
|
|
1644
|
-
}
|
|
1645
|
-
if (r.type === merged.type) {
|
|
1646
|
-
const start = r.start < merged.start ? r.start : merged.start;
|
|
1647
|
-
const end = r.end > merged.end ? r.end : merged.end;
|
|
1648
|
-
merged = { ...merged, start, end };
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
kept.push(merged);
|
|
1652
|
-
state.locks.set(nodeid, kept);
|
|
1653
|
-
}
|
|
1654
|
-
function removeLockRange(state, nodeid, owner, req) {
|
|
1655
|
-
const ranges = state.locks.get(nodeid);
|
|
1656
|
-
if (!ranges) {
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
const out = [];
|
|
1660
|
-
for (const r of ranges) {
|
|
1661
|
-
if (r.owner !== owner || !rangesOverlap(r, req)) {
|
|
1662
|
-
out.push(r);
|
|
1663
|
-
continue;
|
|
1664
|
-
}
|
|
1665
|
-
if (r.start < req.start) {
|
|
1666
|
-
out.push({ ...r, end: req.start - 1n });
|
|
1667
|
-
}
|
|
1668
|
-
if (r.end > req.end) {
|
|
1669
|
-
out.push({ ...r, start: req.end + 1n });
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
if (out.length === 0) {
|
|
1673
|
-
state.locks.delete(nodeid);
|
|
1674
|
-
} else {
|
|
1675
|
-
state.locks.set(nodeid, out);
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
function dropLocksFor(state, nodeid, owner) {
|
|
1679
|
-
const ranges = state.locks.get(nodeid);
|
|
1680
|
-
if (!ranges) {
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
const out = ranges.filter((r) => r.owner !== owner);
|
|
1684
|
-
if (out.length === 0) {
|
|
1685
|
-
state.locks.delete(nodeid);
|
|
1686
|
-
} else {
|
|
1687
|
-
state.locks.set(nodeid, out);
|
|
1688
|
-
}
|
|
1689
|
-
wakeWaiters(state);
|
|
1690
|
-
}
|
|
1691
|
-
function wakeWaiters(state) {
|
|
1692
|
-
for (; ; ) {
|
|
1693
|
-
const idx = state.lockWaiters.findIndex(
|
|
1694
|
-
(w2) => findConflict(state, w2.nodeid, w2.request) === null
|
|
1695
|
-
);
|
|
1696
|
-
if (idx < 0) {
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
const [w] = state.lockWaiters.splice(idx, 1);
|
|
1700
|
-
if (!w) {
|
|
1701
|
-
return;
|
|
1702
|
-
}
|
|
1703
|
-
addLock(state, w.nodeid, w.request);
|
|
1704
|
-
w.resolve();
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
function cancelAllWaiters(state) {
|
|
1708
|
-
const waiters = state.lockWaiters;
|
|
1709
|
-
state.lockWaiters = [];
|
|
1710
|
-
for (const w of waiters) {
|
|
1711
|
-
w.cancel();
|
|
1712
|
-
}
|
|
1713
|
-
}
|
|
1714
|
-
function onGetlk(hdr, msg, state) {
|
|
1715
|
-
const req = readLkIn(payloadOf(msg));
|
|
1716
|
-
const probe = rangeFromLkIn(req.lk, req.owner);
|
|
1717
|
-
if (!probe) {
|
|
1718
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1719
|
-
}
|
|
1720
|
-
const conflict = findConflict(state, hdr.nodeid, probe);
|
|
1721
|
-
if (!conflict) {
|
|
1722
|
-
return buildResponse(
|
|
1723
|
-
hdr.unique,
|
|
1724
|
-
buildLkOut({ start: req.lk.start, end: req.lk.end, type: F_LCK.UNLCK, pid: 0 })
|
|
1725
|
-
);
|
|
1726
|
-
}
|
|
1727
|
-
return buildResponse(
|
|
1728
|
-
hdr.unique,
|
|
1729
|
-
buildLkOut({
|
|
1730
|
-
start: conflict.start,
|
|
1731
|
-
end: conflict.end,
|
|
1732
|
-
type: conflict.type,
|
|
1733
|
-
pid: conflict.pid
|
|
1734
|
-
})
|
|
1735
|
-
);
|
|
1736
|
-
}
|
|
1737
|
-
function onSetlk(hdr, msg, state) {
|
|
1738
|
-
const req = readLkIn(payloadOf(msg));
|
|
1739
|
-
if (req.lk.type === F_LCK.UNLCK) {
|
|
1740
|
-
removeLockRange(state, hdr.nodeid, req.owner, { start: req.lk.start, end: req.lk.end });
|
|
1741
|
-
wakeWaiters(state);
|
|
1742
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1743
|
-
}
|
|
1744
|
-
const want = rangeFromLkIn(req.lk, req.owner);
|
|
1745
|
-
if (!want) {
|
|
1746
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1747
|
-
}
|
|
1748
|
-
if (findConflict(state, hdr.nodeid, want)) {
|
|
1749
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EAGAIN);
|
|
1750
|
-
}
|
|
1751
|
-
addLock(state, hdr.nodeid, want);
|
|
1752
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1753
|
-
}
|
|
1754
|
-
async function onSetlkw(hdr, msg, state) {
|
|
1755
|
-
const req = readLkIn(payloadOf(msg));
|
|
1756
|
-
if (req.lk.type === F_LCK.UNLCK) {
|
|
1757
|
-
removeLockRange(state, hdr.nodeid, req.owner, { start: req.lk.start, end: req.lk.end });
|
|
1758
|
-
wakeWaiters(state);
|
|
1759
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1760
|
-
}
|
|
1761
|
-
const want = rangeFromLkIn(req.lk, req.owner);
|
|
1762
|
-
if (!want) {
|
|
1763
|
-
return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
|
|
1764
|
-
}
|
|
1765
|
-
if (!findConflict(state, hdr.nodeid, want)) {
|
|
1766
|
-
addLock(state, hdr.nodeid, want);
|
|
1767
|
-
return buildErrorResponse(hdr.unique, 0);
|
|
1768
|
-
}
|
|
1769
|
-
return await new Promise((done) => {
|
|
1770
|
-
const waiter = {
|
|
1771
|
-
nodeid: hdr.nodeid,
|
|
1772
|
-
request: want,
|
|
1773
|
-
resolve: () => done(buildErrorResponse(hdr.unique, 0)),
|
|
1774
|
-
cancel: () => done(buildErrorResponse(hdr.unique, -ERRNO.EINTR))
|
|
1775
|
-
};
|
|
1776
|
-
state.lockWaiters.push(waiter);
|
|
1777
|
-
});
|
|
1778
|
-
}
|
|
1779
|
-
function decodeName(body) {
|
|
1780
|
-
const nulAt = body.indexOf(0);
|
|
1781
|
-
const end = nulAt >= 0 ? nulAt : body.length;
|
|
1782
|
-
return new TextDecoder("utf-8", { fatal: false }).decode(body.subarray(0, end));
|
|
1783
|
-
}
|
|
1784
|
-
function validateName(name) {
|
|
1785
|
-
if (name === "" || name === "." || name === ".." || name.includes("/") || name.includes("\0")) {
|
|
1786
|
-
const err = new Error(`invalid FUSE name: ${JSON.stringify(name)}`);
|
|
1787
|
-
err.code = "EINVAL";
|
|
1788
|
-
throw err;
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
function joinRel(parentRel, name) {
|
|
1792
|
-
if (parentRel === "") {
|
|
1793
|
-
return name;
|
|
1794
|
-
}
|
|
1795
|
-
return `${parentRel}/${name}`;
|
|
1796
|
-
}
|
|
1797
|
-
function requireInode(state, ino) {
|
|
1798
|
-
const e = state.inodes.get(ino);
|
|
1799
|
-
if (!e) {
|
|
1800
|
-
const err = new Error(`stale inode ${ino}`);
|
|
1801
|
-
err.code = "ESTALE";
|
|
1802
|
-
throw err;
|
|
1803
|
-
}
|
|
1804
|
-
return e;
|
|
1805
|
-
}
|
|
1806
|
-
async function absPathForTraversal(state, entry) {
|
|
1807
|
-
if (entry.relPath === "") {
|
|
1808
|
-
return await realpath2(state.rootAbs);
|
|
1809
|
-
}
|
|
1810
|
-
return await resolveUnderRoot(state.rootAbs, entry.relPath);
|
|
1811
|
-
}
|
|
1812
|
-
async function absPathForLstat(state, entry) {
|
|
1813
|
-
if (entry.relPath === "") {
|
|
1814
|
-
return await realpath2(state.rootAbs);
|
|
1815
|
-
}
|
|
1816
|
-
const slashAt = entry.relPath.lastIndexOf("/");
|
|
1817
|
-
const parentRel = slashAt < 0 ? "" : entry.relPath.slice(0, slashAt);
|
|
1818
|
-
const baseName = slashAt < 0 ? entry.relPath : entry.relPath.slice(slashAt + 1);
|
|
1819
|
-
const parentAbs = parentRel === "" ? await realpath2(state.rootAbs) : await resolveUnderRoot(state.rootAbs, parentRel);
|
|
1820
|
-
return join2(parentAbs, baseName);
|
|
1821
|
-
}
|
|
1822
|
-
function bindInode(state, relPath) {
|
|
1823
|
-
for (const [ino2, e] of state.inodes) {
|
|
1824
|
-
if (e.relPath === relPath) {
|
|
1825
|
-
if (e.nlookup !== Number.POSITIVE_INFINITY) {
|
|
1826
|
-
e.nlookup++;
|
|
1827
|
-
}
|
|
1828
|
-
return ino2;
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
const ino = state.nextInode++;
|
|
1832
|
-
state.inodes.set(ino, { relPath, nlookup: 1 });
|
|
1833
|
-
return ino;
|
|
1834
|
-
}
|
|
1835
|
-
function decrefInode(state, ino, n) {
|
|
1836
|
-
const e = state.inodes.get(ino);
|
|
1837
|
-
if (!e) {
|
|
1838
|
-
return;
|
|
1839
|
-
}
|
|
1840
|
-
if (e.nlookup === Number.POSITIVE_INFINITY) {
|
|
1841
|
-
return;
|
|
1842
|
-
}
|
|
1843
|
-
e.nlookup -= n;
|
|
1844
|
-
if (e.nlookup <= 0) {
|
|
1845
|
-
state.inodes.delete(ino);
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
function forgetByRelPath(state, relPath) {
|
|
1849
|
-
for (const [ino, e] of state.inodes) {
|
|
1850
|
-
if (e.relPath === relPath) {
|
|
1851
|
-
state.inodes.delete(ino);
|
|
1852
|
-
return;
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
function linuxOpenFlagsToNode(flags) {
|
|
1857
|
-
const LINUX_O_WRONLY = 1;
|
|
1858
|
-
const LINUX_O_RDWR = 2;
|
|
1859
|
-
const LINUX_O_TRUNC = 512;
|
|
1860
|
-
const LINUX_O_APPEND = 1024;
|
|
1861
|
-
const accessMode = flags & 3;
|
|
1862
|
-
let out;
|
|
1863
|
-
if (accessMode === LINUX_O_WRONLY) {
|
|
1864
|
-
out = fsConstants.O_WRONLY;
|
|
1865
|
-
} else if (accessMode === LINUX_O_RDWR) {
|
|
1866
|
-
out = fsConstants.O_RDWR;
|
|
1867
|
-
} else {
|
|
1868
|
-
out = fsConstants.O_RDONLY;
|
|
1869
|
-
}
|
|
1870
|
-
if (flags & LINUX_O_TRUNC) {
|
|
1871
|
-
out |= fsConstants.O_TRUNC;
|
|
1872
|
-
}
|
|
1873
|
-
if (flags & LINUX_O_APPEND) {
|
|
1874
|
-
out |= fsConstants.O_APPEND;
|
|
1875
|
-
}
|
|
1876
|
-
return out;
|
|
1877
|
-
}
|
|
1878
|
-
function statToAttr(st, ino) {
|
|
1879
|
-
const atime = BigInt(Math.floor(st.atimeMs / 1e3));
|
|
1880
|
-
const mtime = BigInt(Math.floor(st.mtimeMs / 1e3));
|
|
1881
|
-
const ctime = BigInt(Math.floor(st.ctimeMs / 1e3));
|
|
1882
|
-
return {
|
|
1883
|
-
ino,
|
|
1884
|
-
size: BigInt(st.size),
|
|
1885
|
-
blocks: BigInt(st.blocks ?? 0),
|
|
1886
|
-
atime,
|
|
1887
|
-
mtime,
|
|
1888
|
-
ctime,
|
|
1889
|
-
atimensec: Math.floor(st.atimeMs % 1e3 * 1e6),
|
|
1890
|
-
mtimensec: Math.floor(st.mtimeMs % 1e3 * 1e6),
|
|
1891
|
-
ctimensec: Math.floor(st.ctimeMs % 1e3 * 1e6),
|
|
1892
|
-
mode: st.mode,
|
|
1893
|
-
nlink: st.nlink,
|
|
1894
|
-
uid: st.uid,
|
|
1895
|
-
gid: st.gid,
|
|
1896
|
-
rdev: st.rdev,
|
|
1897
|
-
blksize: st.blksize,
|
|
1898
|
-
flags: 0
|
|
1899
|
-
};
|
|
1900
|
-
}
|
|
1901
|
-
function modeToDT(mode) {
|
|
1902
|
-
const type = mode & 61440;
|
|
1903
|
-
switch (type) {
|
|
1904
|
-
case 16384:
|
|
1905
|
-
return DT.DIR;
|
|
1906
|
-
case 32768:
|
|
1907
|
-
return DT.REG;
|
|
1908
|
-
case 40960:
|
|
1909
|
-
return DT.LNK;
|
|
1910
|
-
case 8192:
|
|
1911
|
-
return DT.CHR;
|
|
1912
|
-
case 24576:
|
|
1913
|
-
return DT.BLK;
|
|
1914
|
-
case 4096:
|
|
1915
|
-
return DT.FIFO;
|
|
1916
|
-
case 49152:
|
|
1917
|
-
return DT.SOCK;
|
|
1918
|
-
default:
|
|
1919
|
-
return DT.UNKNOWN;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
function concatBuffers(parts, total) {
|
|
1923
|
-
const out = new Uint8Array(total);
|
|
1924
|
-
let cursor = 0;
|
|
1925
|
-
for (const p of parts) {
|
|
1926
|
-
out.set(p, cursor);
|
|
1927
|
-
cursor += p.length;
|
|
1928
|
-
}
|
|
1929
|
-
return out;
|
|
1930
|
-
}
|
|
1931
|
-
function mapErrorToErrno(err) {
|
|
1932
|
-
if (isMachinenError(err) && err instanceof MountError) {
|
|
1933
|
-
return -ERRNO.ENOENT;
|
|
1934
|
-
}
|
|
1935
|
-
const code = err?.code;
|
|
1936
|
-
if (!code) {
|
|
1937
|
-
return -ERRNO.EIO;
|
|
1938
|
-
}
|
|
1939
|
-
if (code === "EOPNOTSUPP") {
|
|
1940
|
-
return -ERRNO.ENOTSUP;
|
|
1941
|
-
}
|
|
1942
|
-
const key = code;
|
|
1943
|
-
if (key in ERRNO) {
|
|
1944
|
-
return -ERRNO[key];
|
|
1945
|
-
}
|
|
1946
|
-
return -ERRNO.EIO;
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
// src/mount-server-bin.ts
|
|
1950
|
-
function parseArgs(argv) {
|
|
1951
|
-
const out = {};
|
|
1952
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1953
|
-
const flag = argv[i];
|
|
1954
|
-
const value = argv[i + 1];
|
|
1955
|
-
if (value === void 0) {
|
|
1956
|
-
throw new Error(`missing value for ${flag}`);
|
|
1957
|
-
}
|
|
1958
|
-
switch (flag) {
|
|
1959
|
-
case "--uds":
|
|
1960
|
-
out.uds = value;
|
|
1961
|
-
i++;
|
|
1962
|
-
break;
|
|
1963
|
-
case "--root":
|
|
1964
|
-
out.root = value;
|
|
1965
|
-
i++;
|
|
1966
|
-
break;
|
|
1967
|
-
case "--mode":
|
|
1968
|
-
if (value !== "ro" && value !== "rw") {
|
|
1969
|
-
throw new Error(`--mode must be 'ro' or 'rw' (got '${value}')`);
|
|
1970
|
-
}
|
|
1971
|
-
out.mode = value;
|
|
1972
|
-
i++;
|
|
1973
|
-
break;
|
|
1974
|
-
case "--stats":
|
|
1975
|
-
out.stats = value;
|
|
1976
|
-
i++;
|
|
1977
|
-
break;
|
|
1978
|
-
default:
|
|
1979
|
-
throw new Error(`unknown flag '${flag}'`);
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
for (const key of ["uds", "root", "mode", "stats"]) {
|
|
1983
|
-
if (out[key] === void 0) {
|
|
1984
|
-
throw new Error(`missing required --${key}`);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
return out;
|
|
1988
|
-
}
|
|
1989
|
-
function writeStatsAtomic(path, snapshot) {
|
|
1990
|
-
const tmp = `${path}.tmp.${process.pid}`;
|
|
1991
|
-
writeFileSync(tmp, JSON.stringify(snapshot));
|
|
1992
|
-
renameSync(tmp, path);
|
|
1993
|
-
}
|
|
1994
|
-
async function main() {
|
|
1995
|
-
const args = parseArgs(process.argv.slice(2));
|
|
1996
|
-
const handle2 = await serveLiveMount(args.uds, {
|
|
1997
|
-
rootAbs: args.root,
|
|
1998
|
-
mode: args.mode
|
|
1999
|
-
});
|
|
2000
|
-
writeStatsAtomic(args.stats, {
|
|
2001
|
-
bytesServedOnPagesImg: 0,
|
|
2002
|
-
updatedAtMs: Date.now()
|
|
2003
|
-
});
|
|
2004
|
-
let lastPublished = 0;
|
|
2005
|
-
const ticker = setInterval(() => {
|
|
2006
|
-
const cur = handle2.bytesServedOnPagesImg();
|
|
2007
|
-
if (cur === lastPublished) {
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
lastPublished = cur;
|
|
2011
|
-
try {
|
|
2012
|
-
writeStatsAtomic(args.stats, {
|
|
2013
|
-
bytesServedOnPagesImg: cur,
|
|
2014
|
-
updatedAtMs: Date.now()
|
|
2015
|
-
});
|
|
2016
|
-
} catch {
|
|
2017
|
-
}
|
|
2018
|
-
}, 250);
|
|
2019
|
-
ticker.unref();
|
|
2020
|
-
let shuttingDown = false;
|
|
2021
|
-
const shutdown2 = async (signal) => {
|
|
2022
|
-
if (shuttingDown) {
|
|
2023
|
-
return;
|
|
2024
|
-
}
|
|
2025
|
-
shuttingDown = true;
|
|
2026
|
-
clearInterval(ticker);
|
|
2027
|
-
try {
|
|
2028
|
-
writeStatsAtomic(args.stats, {
|
|
2029
|
-
bytesServedOnPagesImg: handle2.bytesServedOnPagesImg(),
|
|
2030
|
-
updatedAtMs: Date.now()
|
|
2031
|
-
});
|
|
2032
|
-
} catch {
|
|
2033
|
-
}
|
|
2034
|
-
try {
|
|
2035
|
-
await handle2.stop();
|
|
2036
|
-
} catch {
|
|
2037
|
-
}
|
|
2038
|
-
try {
|
|
2039
|
-
unlinkSync(args.stats);
|
|
2040
|
-
} catch {
|
|
2041
|
-
}
|
|
2042
|
-
process.exit(signal === "SIGTERM" ? 0 : 128 + (signalToNumber(signal) ?? 15));
|
|
2043
|
-
};
|
|
2044
|
-
process.on("SIGTERM", () => void shutdown2("SIGTERM"));
|
|
2045
|
-
process.on("SIGINT", () => void shutdown2("SIGINT"));
|
|
2046
|
-
process.on("SIGHUP", () => void shutdown2("SIGHUP"));
|
|
2047
|
-
}
|
|
2048
|
-
function signalToNumber(signal) {
|
|
2049
|
-
switch (signal) {
|
|
2050
|
-
case "SIGTERM":
|
|
2051
|
-
return 15;
|
|
2052
|
-
case "SIGINT":
|
|
2053
|
-
return 2;
|
|
2054
|
-
case "SIGHUP":
|
|
2055
|
-
return 1;
|
|
2056
|
-
default:
|
|
2057
|
-
return void 0;
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
main().catch((err) => {
|
|
2061
|
-
process.stderr.write(`mount-server-bin: ${err instanceof Error ? err.message : String(err)}
|
|
2062
|
-
`);
|
|
2063
|
-
process.exit(1);
|
|
2064
|
-
});
|
|
2065
|
-
//# sourceMappingURL=mount-server-bin.js.map
|