@machinen/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1341 @@
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 {
12
+ chmod,
13
+ link,
14
+ mkdir,
15
+ open as fsOpen,
16
+ lstat,
17
+ readdir,
18
+ readlink,
19
+ realpath as realpath2,
20
+ rename,
21
+ rmdir,
22
+ statfs,
23
+ symlink,
24
+ truncate,
25
+ unlink,
26
+ utimes
27
+ } from "fs/promises";
28
+ import { constants as fsConstants } from "fs";
29
+ import { join as join2 } from "path";
30
+ import debug from "debug";
31
+
32
+ // src/mount-resolver.ts
33
+ import { realpath } from "fs/promises";
34
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "path";
35
+ async function resolveUnderRoot(rootAbs, guestRelative, opts = {}) {
36
+ const mustExist = opts.mustExist ?? true;
37
+ if (!isAbsolute(rootAbs)) {
38
+ throw new MountError("MOUNT_PATH_INVALID", `mount root must be absolute: ${rootAbs}`);
39
+ }
40
+ if (guestRelative.includes("\0")) {
41
+ throw new MountError("MOUNT_PATH_INVALID", "null byte in guest path");
42
+ }
43
+ const rootReal = await realpath(rootAbs);
44
+ const relStripped = guestRelative.replace(/^\/+/, "");
45
+ const joined = resolve(rootReal, relStripped);
46
+ try {
47
+ const targetReal = await realpath(joined);
48
+ assertContained(targetReal, rootReal);
49
+ return targetReal;
50
+ } catch (err) {
51
+ const errno = err.code;
52
+ if (errno !== "ENOENT" || mustExist) {
53
+ throw err;
54
+ }
55
+ }
56
+ const parentReal = await realpath(dirname(joined));
57
+ assertContained(parentReal, rootReal);
58
+ return join(parentReal, basename(joined));
59
+ }
60
+ function assertContained(targetReal, rootReal) {
61
+ if (targetReal === rootReal) {
62
+ return;
63
+ }
64
+ if (targetReal.startsWith(rootReal + sep)) {
65
+ return;
66
+ }
67
+ throw new MountError(
68
+ "MOUNT_PATH_ESCAPE",
69
+ `path escapes mount root: ${targetReal} is not under ${rootReal}`
70
+ );
71
+ }
72
+
73
+ // src/mount-fuse-protocol.ts
74
+ var FUSE_KERNEL_VERSION = 7;
75
+ var FUSE_KERNEL_MINOR_VERSION = 31;
76
+ var FUSE_OP = {
77
+ LOOKUP: 1,
78
+ FORGET: 2,
79
+ GETATTR: 3,
80
+ SETATTR: 4,
81
+ READLINK: 5,
82
+ SYMLINK: 6,
83
+ MKDIR: 9,
84
+ UNLINK: 10,
85
+ RMDIR: 11,
86
+ RENAME: 12,
87
+ LINK: 13,
88
+ OPEN: 14,
89
+ READ: 15,
90
+ WRITE: 16,
91
+ STATFS: 17,
92
+ RELEASE: 18,
93
+ FSYNC: 20,
94
+ FLUSH: 25,
95
+ INIT: 26,
96
+ OPENDIR: 27,
97
+ READDIR: 28,
98
+ RELEASEDIR: 29,
99
+ ACCESS: 34,
100
+ CREATE: 35,
101
+ INTERRUPT: 36,
102
+ DESTROY: 38,
103
+ BATCH_FORGET: 42,
104
+ READDIRPLUS: 44
105
+ };
106
+ var FATTR = {
107
+ MODE: 1 << 0,
108
+ UID: 1 << 1,
109
+ GID: 1 << 2,
110
+ SIZE: 1 << 3,
111
+ ATIME: 1 << 4,
112
+ MTIME: 1 << 5,
113
+ FH: 1 << 6,
114
+ ATIME_NOW: 1 << 7,
115
+ MTIME_NOW: 1 << 8,
116
+ LOCKOWNER: 1 << 9,
117
+ CTIME: 1 << 10
118
+ };
119
+ var FUSE_CAP = {
120
+ ASYNC_READ: 1 << 0,
121
+ POSIX_LOCKS: 1 << 1,
122
+ EXPORT_SUPPORT: 1 << 4,
123
+ BIG_WRITES: 1 << 5,
124
+ DONT_MASK: 1 << 6,
125
+ SPLICE_WRITE: 1 << 7,
126
+ SPLICE_MOVE: 1 << 8,
127
+ SPLICE_READ: 1 << 9,
128
+ FLOCK_LOCKS: 1 << 10,
129
+ HAS_IOCTL_DIR: 1 << 11,
130
+ AUTO_INVAL_DATA: 1 << 12,
131
+ DO_READDIRPLUS: 1 << 13,
132
+ READDIRPLUS_AUTO: 1 << 14,
133
+ ASYNC_DIO: 1 << 15,
134
+ WRITEBACK_CACHE: 1 << 16,
135
+ NO_OPEN_SUPPORT: 1 << 17,
136
+ PARALLEL_DIROPS: 1 << 18,
137
+ HANDLE_KILLPRIV: 1 << 19,
138
+ POSIX_ACL: 1 << 20,
139
+ ABORT_ERROR: 1 << 21,
140
+ MAX_PAGES: 1 << 22,
141
+ CACHE_SYMLINKS: 1 << 23,
142
+ NO_OPENDIR_SUPPORT: 1 << 24
143
+ };
144
+ var FUSE_IN_HEADER_SIZE = 40;
145
+ var FUSE_OUT_HEADER_SIZE = 16;
146
+ function readInHeader(buf, offset = 0) {
147
+ const dv = viewOf(buf, offset, FUSE_IN_HEADER_SIZE);
148
+ return {
149
+ len: dv.getUint32(0, true),
150
+ opcode: dv.getUint32(4, true),
151
+ unique: dv.getBigUint64(8, true),
152
+ nodeid: dv.getBigUint64(16, true),
153
+ uid: dv.getUint32(24, true),
154
+ gid: dv.getUint32(28, true),
155
+ pid: dv.getUint32(32, true)
156
+ // padding at offset 36
157
+ };
158
+ }
159
+ function writeOutHeader(buf, offset, payloadLen, error, unique) {
160
+ const dv = viewOf(buf, offset, FUSE_OUT_HEADER_SIZE);
161
+ dv.setUint32(0, FUSE_OUT_HEADER_SIZE + payloadLen, true);
162
+ dv.setInt32(4, error, true);
163
+ dv.setBigUint64(8, unique, true);
164
+ }
165
+ function buildErrorResponse(unique, error) {
166
+ const buf = new Uint8Array(FUSE_OUT_HEADER_SIZE);
167
+ writeOutHeader(buf, 0, 0, error, unique);
168
+ return buf;
169
+ }
170
+ function buildResponse(unique, payload) {
171
+ const buf = new Uint8Array(FUSE_OUT_HEADER_SIZE + payload.length);
172
+ writeOutHeader(buf, 0, payload.length, 0, unique);
173
+ buf.set(payload, FUSE_OUT_HEADER_SIZE);
174
+ return buf;
175
+ }
176
+ var FUSE_ATTR_SIZE = 88;
177
+ function writeAttr(buf, offset, a) {
178
+ const dv = viewOf(buf, offset, FUSE_ATTR_SIZE);
179
+ dv.setBigUint64(0, a.ino, true);
180
+ dv.setBigUint64(8, a.size, true);
181
+ dv.setBigUint64(16, a.blocks, true);
182
+ dv.setBigUint64(24, a.atime, true);
183
+ dv.setBigUint64(32, a.mtime, true);
184
+ dv.setBigUint64(40, a.ctime, true);
185
+ dv.setUint32(48, a.atimensec, true);
186
+ dv.setUint32(52, a.mtimensec, true);
187
+ dv.setUint32(56, a.ctimensec, true);
188
+ dv.setUint32(60, a.mode, true);
189
+ dv.setUint32(64, a.nlink, true);
190
+ dv.setUint32(68, a.uid, true);
191
+ dv.setUint32(72, a.gid, true);
192
+ dv.setUint32(76, a.rdev, true);
193
+ dv.setUint32(80, a.blksize, true);
194
+ dv.setUint32(84, a.flags, true);
195
+ }
196
+ var FUSE_ENTRY_OUT_SIZE = 40 + FUSE_ATTR_SIZE;
197
+ function buildEntryOut(e) {
198
+ const buf = new Uint8Array(FUSE_ENTRY_OUT_SIZE);
199
+ const dv = viewOf(buf, 0, 40);
200
+ dv.setBigUint64(0, e.nodeid, true);
201
+ dv.setBigUint64(8, e.generation, true);
202
+ dv.setBigUint64(16, e.entry_valid, true);
203
+ dv.setBigUint64(24, e.attr_valid, true);
204
+ dv.setUint32(32, e.entry_valid_nsec, true);
205
+ dv.setUint32(36, e.attr_valid_nsec, true);
206
+ writeAttr(buf, 40, e.attr);
207
+ return buf;
208
+ }
209
+ var FUSE_ATTR_OUT_SIZE = 16 + FUSE_ATTR_SIZE;
210
+ function buildAttrOut(a) {
211
+ const buf = new Uint8Array(FUSE_ATTR_OUT_SIZE);
212
+ const dv = viewOf(buf, 0, 16);
213
+ dv.setBigUint64(0, a.attr_valid, true);
214
+ dv.setUint32(8, a.attr_valid_nsec, true);
215
+ writeAttr(buf, 16, a.attr);
216
+ return buf;
217
+ }
218
+ var FUSE_KSTATFS_SIZE = 80;
219
+ function buildKstatfs(s) {
220
+ const buf = new Uint8Array(FUSE_KSTATFS_SIZE);
221
+ const dv = viewOf(buf, 0, FUSE_KSTATFS_SIZE);
222
+ dv.setBigUint64(0, s.blocks, true);
223
+ dv.setBigUint64(8, s.bfree, true);
224
+ dv.setBigUint64(16, s.bavail, true);
225
+ dv.setBigUint64(24, s.files, true);
226
+ dv.setBigUint64(32, s.ffree, true);
227
+ dv.setUint32(40, s.bsize, true);
228
+ dv.setUint32(44, s.namelen, true);
229
+ dv.setUint32(48, s.frsize, true);
230
+ return buf;
231
+ }
232
+ var FUSE_INIT_OUT_SIZE = 64;
233
+ function readInitIn(buf, offset = 0) {
234
+ const dv = viewOf(buf, offset, Math.min(buf.length - offset, 24));
235
+ const major = dv.getUint32(0, true);
236
+ const minor = dv.getUint32(4, true);
237
+ const hasExtended = buf.length - offset >= 20;
238
+ return {
239
+ major,
240
+ minor,
241
+ max_readahead: hasExtended ? dv.getUint32(8, true) : 0,
242
+ flags: hasExtended ? dv.getUint32(12, true) : 0,
243
+ flags2: hasExtended && dv.byteLength >= 20 ? dv.getUint32(16, true) : 0
244
+ };
245
+ }
246
+ function buildInitOut(o) {
247
+ const buf = new Uint8Array(FUSE_INIT_OUT_SIZE);
248
+ const dv = viewOf(buf, 0, FUSE_INIT_OUT_SIZE);
249
+ dv.setUint32(0, o.major, true);
250
+ dv.setUint32(4, o.minor, true);
251
+ dv.setUint32(8, o.max_readahead, true);
252
+ dv.setUint32(12, o.flags, true);
253
+ dv.setUint16(16, o.max_background, true);
254
+ dv.setUint16(18, o.congestion_threshold, true);
255
+ dv.setUint32(20, o.max_write, true);
256
+ dv.setUint32(24, o.time_gran, true);
257
+ dv.setUint16(28, o.max_pages, true);
258
+ dv.setUint16(30, o.map_alignment, true);
259
+ dv.setUint32(32, o.flags2, true);
260
+ return buf;
261
+ }
262
+ function readForgetIn(buf, offset = 0) {
263
+ const dv = viewOf(buf, offset, 8);
264
+ return { nlookup: dv.getBigUint64(0, true) };
265
+ }
266
+ function readBatchForgetIn(buf, offset = 0) {
267
+ const dv = viewOf(buf, offset, 8);
268
+ const count = dv.getUint32(0, true);
269
+ const entries = [];
270
+ for (let i = 0; i < count; i++) {
271
+ const entryOffset = offset + 8 + i * 16;
272
+ const entryDv = viewOf(buf, entryOffset, 16);
273
+ entries.push({
274
+ nodeid: entryDv.getBigUint64(0, true),
275
+ nlookup: entryDv.getBigUint64(8, true)
276
+ });
277
+ }
278
+ return { count, entries };
279
+ }
280
+ function readOpenIn(buf, offset = 0) {
281
+ const dv = viewOf(buf, offset, 8);
282
+ return {
283
+ flags: dv.getUint32(0, true),
284
+ open_flags: dv.getUint32(4, true)
285
+ };
286
+ }
287
+ function buildOpenOut(o) {
288
+ const buf = new Uint8Array(16);
289
+ const dv = viewOf(buf, 0, 16);
290
+ dv.setBigUint64(0, o.fh, true);
291
+ dv.setUint32(8, o.open_flags, true);
292
+ return buf;
293
+ }
294
+ function readReadIn(buf, off = 0) {
295
+ const dv = viewOf(buf, off, 40);
296
+ return {
297
+ fh: dv.getBigUint64(0, true),
298
+ offset: dv.getBigUint64(8, true),
299
+ size: dv.getUint32(16, true),
300
+ read_flags: dv.getUint32(20, true),
301
+ lock_owner: dv.getBigUint64(24, true),
302
+ flags: dv.getUint32(32, true)
303
+ // padding at 36
304
+ };
305
+ }
306
+ function readReleaseIn(buf, offset = 0) {
307
+ const dv = viewOf(buf, offset, 24);
308
+ return {
309
+ fh: dv.getBigUint64(0, true),
310
+ flags: dv.getUint32(8, true),
311
+ release_flags: dv.getUint32(12, true),
312
+ lock_owner: dv.getBigUint64(16, true)
313
+ };
314
+ }
315
+ function buildDirent(params) {
316
+ const nameBytes = new TextEncoder().encode(params.name);
317
+ const paddedNameLen = nameBytes.length + 7 & ~7;
318
+ const buf = new Uint8Array(24 + paddedNameLen);
319
+ const dv = viewOf(buf, 0, 24);
320
+ dv.setBigUint64(0, params.ino, true);
321
+ dv.setBigUint64(8, params.off, true);
322
+ dv.setUint32(16, nameBytes.length, true);
323
+ dv.setUint32(20, params.type, true);
324
+ buf.set(nameBytes, 24);
325
+ return buf;
326
+ }
327
+ var DT = {
328
+ UNKNOWN: 0,
329
+ FIFO: 1,
330
+ CHR: 2,
331
+ DIR: 4,
332
+ BLK: 6,
333
+ REG: 8,
334
+ LNK: 10,
335
+ SOCK: 12
336
+ };
337
+ var FUSE_WRITE_IN_SIZE = 40;
338
+ function readWriteIn(buf, off = 0) {
339
+ const dv = viewOf(buf, off, FUSE_WRITE_IN_SIZE);
340
+ return {
341
+ fh: dv.getBigUint64(0, true),
342
+ offset: dv.getBigUint64(8, true),
343
+ size: dv.getUint32(16, true),
344
+ write_flags: dv.getUint32(20, true),
345
+ lock_owner: dv.getBigUint64(24, true),
346
+ flags: dv.getUint32(32, true)
347
+ // padding at 36
348
+ };
349
+ }
350
+ function buildWriteOut(o) {
351
+ const buf = new Uint8Array(8);
352
+ const dv = viewOf(buf, 0, 8);
353
+ dv.setUint32(0, o.size, true);
354
+ return buf;
355
+ }
356
+ var FUSE_CREATE_IN_SIZE = 16;
357
+ function readCreateIn(buf, off = 0) {
358
+ const dv = viewOf(buf, off, FUSE_CREATE_IN_SIZE);
359
+ return {
360
+ flags: dv.getUint32(0, true),
361
+ mode: dv.getUint32(4, true),
362
+ umask: dv.getUint32(8, true),
363
+ open_flags: dv.getUint32(12, true)
364
+ };
365
+ }
366
+ function buildCreateOut(entry, open) {
367
+ const entryBuf = buildEntryOut(entry);
368
+ const openBuf = buildOpenOut(open);
369
+ const out = new Uint8Array(entryBuf.length + openBuf.length);
370
+ out.set(entryBuf, 0);
371
+ out.set(openBuf, entryBuf.length);
372
+ return out;
373
+ }
374
+ var FUSE_SETATTR_IN_SIZE = 88;
375
+ function readSetattrIn(buf, off = 0) {
376
+ const dv = viewOf(buf, off, FUSE_SETATTR_IN_SIZE);
377
+ return {
378
+ valid: dv.getUint32(0, true),
379
+ // padding at 4
380
+ fh: dv.getBigUint64(8, true),
381
+ size: dv.getBigUint64(16, true),
382
+ lock_owner: dv.getBigUint64(24, true),
383
+ atime: dv.getBigUint64(32, true),
384
+ mtime: dv.getBigUint64(40, true),
385
+ ctime: dv.getBigUint64(48, true),
386
+ atimensec: dv.getUint32(56, true),
387
+ mtimensec: dv.getUint32(60, true),
388
+ ctimensec: dv.getUint32(64, true),
389
+ mode: dv.getUint32(68, true),
390
+ // unused4 at 72
391
+ uid: dv.getUint32(76, true),
392
+ gid: dv.getUint32(80, true)
393
+ // unused5 at 84
394
+ };
395
+ }
396
+ var FUSE_MKDIR_IN_SIZE = 8;
397
+ function readMkdirIn(buf, off = 0) {
398
+ const dv = viewOf(buf, off, FUSE_MKDIR_IN_SIZE);
399
+ return {
400
+ mode: dv.getUint32(0, true),
401
+ umask: dv.getUint32(4, true)
402
+ };
403
+ }
404
+ var FUSE_LINK_IN_SIZE = 8;
405
+ function readLinkIn(buf, off = 0) {
406
+ const dv = viewOf(buf, off, FUSE_LINK_IN_SIZE);
407
+ return {
408
+ oldnodeid: dv.getBigUint64(0, true)
409
+ };
410
+ }
411
+ var FUSE_RENAME_IN_SIZE = 8;
412
+ function readRenameIn(buf, off = 0) {
413
+ const dv = viewOf(buf, off, FUSE_RENAME_IN_SIZE);
414
+ return {
415
+ newdir: dv.getBigUint64(0, true)
416
+ };
417
+ }
418
+ function viewOf(buf, offset, len) {
419
+ if (offset < 0 || len < 0 || offset + len > buf.length) {
420
+ throw new Error(
421
+ `FUSE buffer underflow: need ${len} bytes at offset ${offset}, have ${buf.length}`
422
+ );
423
+ }
424
+ return new DataView(buf.buffer, buf.byteOffset + offset, len);
425
+ }
426
+ function payloadOf(message) {
427
+ if (message.length < FUSE_IN_HEADER_SIZE) {
428
+ throw new Error(`FUSE message too short: ${message.length} < header ${FUSE_IN_HEADER_SIZE}`);
429
+ }
430
+ return message.subarray(FUSE_IN_HEADER_SIZE);
431
+ }
432
+
433
+ // src/mount-server.ts
434
+ var log = debug("machinen:mount-server");
435
+ var ERRNO = {
436
+ EPERM: 1,
437
+ ENOENT: 2,
438
+ EIO: 5,
439
+ EBADF: 9,
440
+ EACCES: 13,
441
+ EBUSY: 16,
442
+ EEXIST: 17,
443
+ ENOTDIR: 20,
444
+ EISDIR: 21,
445
+ EINVAL: 22,
446
+ EROFS: 30,
447
+ ENOSYS: 38,
448
+ ENOTEMPTY: 39,
449
+ ESTALE: 116
450
+ };
451
+ async function serveLiveMount(udsPath, opts) {
452
+ const state = createState(opts.rootAbs, opts.mode ?? "rw");
453
+ const server = createServer((sock) => void handleConnection(sock, state));
454
+ await new Promise((done, fail) => {
455
+ server.once("error", fail);
456
+ server.listen(udsPath, () => {
457
+ server.off("error", fail);
458
+ done();
459
+ });
460
+ });
461
+ log("listening udsPath=%s rootAbs=%s mode=%s", udsPath, opts.rootAbs, state.mode);
462
+ return {
463
+ stop: () => shutdown(server, state),
464
+ bytesServedOnPagesImg: () => state.bytesServedOnPagesImg
465
+ };
466
+ }
467
+ function createState(rootAbs, mode) {
468
+ const inodes = /* @__PURE__ */ new Map();
469
+ inodes.set(1n, { relPath: "", nlookup: Number.POSITIVE_INFINITY });
470
+ return {
471
+ rootAbs,
472
+ mode,
473
+ inodes,
474
+ nextInode: 2n,
475
+ handles: /* @__PURE__ */ new Map(),
476
+ nextHandle: 1n,
477
+ socket: null,
478
+ bytesServedOnPagesImg: 0
479
+ };
480
+ }
481
+ var PAGES_IMG_RE = /(?:^|\/)pages-\d+\.img$/;
482
+ function isPagesImgPath(relPath) {
483
+ return PAGES_IMG_RE.test(relPath);
484
+ }
485
+ async function shutdown(server, state) {
486
+ state.socket?.destroy();
487
+ state.socket = null;
488
+ for (const [, entry] of state.handles) {
489
+ if (entry.kind === "file") {
490
+ await entry.fh.close().catch(() => {
491
+ });
492
+ }
493
+ }
494
+ state.handles.clear();
495
+ await new Promise((done) => server.close(() => done()));
496
+ }
497
+ async function handleConnection(sock, state) {
498
+ if (state.socket) {
499
+ sock.destroy();
500
+ return;
501
+ }
502
+ state.socket = sock;
503
+ sock.on("close", () => {
504
+ state.socket = null;
505
+ });
506
+ sock.on("error", (err) => log("socket error: %s", err.message));
507
+ try {
508
+ for await (const msg of readFrames(sock)) {
509
+ void dispatch(msg, state, sock);
510
+ }
511
+ } catch (err) {
512
+ log("frame read error: %s", err.message);
513
+ sock.destroy();
514
+ }
515
+ }
516
+ async function* readFrames(sock) {
517
+ let pending = Buffer.alloc(0);
518
+ for await (const chunk of sock) {
519
+ pending = pending.length === 0 ? Buffer.from(chunk) : Buffer.concat([pending, chunk]);
520
+ while (pending.length >= 4) {
521
+ const len = pending.readUInt32LE(0);
522
+ if (len < FUSE_IN_HEADER_SIZE) {
523
+ throw new Error(`FUSE framing: len ${len} below header size ${FUSE_IN_HEADER_SIZE}`);
524
+ }
525
+ if (pending.length < len) {
526
+ break;
527
+ }
528
+ yield new Uint8Array(pending.buffer, pending.byteOffset, len).slice();
529
+ pending = pending.subarray(len);
530
+ }
531
+ }
532
+ if (pending.length > 0) {
533
+ throw new Error(`FUSE framing: ${pending.length} trailing bytes after EOF`);
534
+ }
535
+ }
536
+ async function dispatch(msg, state, sock) {
537
+ const hdr = readInHeader(msg);
538
+ log("op=%d nodeid=%s unique=%s len=%d", hdr.opcode, hdr.nodeid, hdr.unique, hdr.len);
539
+ let reply;
540
+ try {
541
+ reply = await handle(hdr, msg, state);
542
+ } catch (err) {
543
+ const errno = mapErrorToErrno(err);
544
+ log("op=%d \u2192 errno %d (%s)", hdr.opcode, errno, err.message);
545
+ reply = buildErrorResponse(hdr.unique, errno);
546
+ }
547
+ if (reply && !sock.destroyed) {
548
+ sock.write(reply);
549
+ }
550
+ }
551
+ async function handle(hdr, msg, state) {
552
+ switch (hdr.opcode) {
553
+ case FUSE_OP.INIT:
554
+ return onInit(hdr, msg);
555
+ case FUSE_OP.DESTROY:
556
+ return buildErrorResponse(hdr.unique, 0);
557
+ case FUSE_OP.INTERRUPT:
558
+ return null;
559
+ // no reply ever
560
+ case FUSE_OP.FORGET:
561
+ onForget(hdr, msg, state);
562
+ return null;
563
+ case FUSE_OP.BATCH_FORGET:
564
+ onBatchForget(msg, state);
565
+ return null;
566
+ case FUSE_OP.LOOKUP:
567
+ return await onLookup(hdr, msg, state);
568
+ case FUSE_OP.GETATTR:
569
+ return await onGetattr(hdr, state);
570
+ case FUSE_OP.READLINK:
571
+ return await onReadlink(hdr, state);
572
+ case FUSE_OP.SYMLINK:
573
+ return await onSymlink(hdr, msg, state);
574
+ case FUSE_OP.LINK:
575
+ return await onLink(hdr, msg, state);
576
+ case FUSE_OP.SETATTR:
577
+ return await onSetattr(hdr, msg, state);
578
+ case FUSE_OP.STATFS:
579
+ return await onStatfs(hdr, state);
580
+ case FUSE_OP.OPEN:
581
+ return await onOpen(hdr, msg, state);
582
+ case FUSE_OP.READ:
583
+ return await onRead(hdr, msg, state);
584
+ case FUSE_OP.WRITE:
585
+ return await onWrite(hdr, msg, state);
586
+ case FUSE_OP.CREATE:
587
+ return await onCreate(hdr, msg, state);
588
+ case FUSE_OP.MKDIR:
589
+ return await onMkdir(hdr, msg, state);
590
+ case FUSE_OP.RMDIR:
591
+ return await onRmdir(hdr, msg, state);
592
+ case FUSE_OP.UNLINK:
593
+ return await onUnlink(hdr, msg, state);
594
+ case FUSE_OP.RENAME:
595
+ return await onRename(hdr, msg, state);
596
+ case FUSE_OP.RELEASE:
597
+ return await onRelease(hdr, msg, state);
598
+ case FUSE_OP.OPENDIR:
599
+ return await onOpendir(hdr, state);
600
+ case FUSE_OP.READDIR:
601
+ return await onReaddir(hdr, msg, state);
602
+ case FUSE_OP.RELEASEDIR:
603
+ return onReleasedir(hdr, msg, state);
604
+ case FUSE_OP.FSYNC:
605
+ return await onFsync(hdr, msg, state);
606
+ case FUSE_OP.FLUSH:
607
+ case FUSE_OP.ACCESS:
608
+ return buildErrorResponse(hdr.unique, 0);
609
+ default:
610
+ return buildErrorResponse(hdr.unique, -ERRNO.ENOSYS);
611
+ }
612
+ }
613
+ function onInit(hdr, msg) {
614
+ const init = readInitIn(payloadOf(msg));
615
+ const minor = Math.min(init.minor, FUSE_KERNEL_MINOR_VERSION);
616
+ const supported = FUSE_CAP.ASYNC_READ | FUSE_CAP.BIG_WRITES | FUSE_CAP.EXPORT_SUPPORT | FUSE_CAP.MAX_PAGES | FUSE_CAP.PARALLEL_DIROPS;
617
+ const flags = init.flags & supported;
618
+ const out = buildInitOut({
619
+ major: FUSE_KERNEL_VERSION,
620
+ minor,
621
+ max_readahead: init.max_readahead,
622
+ flags,
623
+ max_background: 16,
624
+ congestion_threshold: 12,
625
+ max_write: 131072,
626
+ time_gran: 1,
627
+ max_pages: 32,
628
+ // 128 KiB / 4 KiB page
629
+ map_alignment: 0,
630
+ flags2: 0
631
+ });
632
+ return buildResponse(hdr.unique, out);
633
+ }
634
+ function onForget(hdr, msg, state) {
635
+ const { nlookup } = readForgetIn(payloadOf(msg));
636
+ decrefInode(state, hdr.nodeid, Number(nlookup));
637
+ }
638
+ function onBatchForget(msg, state) {
639
+ const { entries } = readBatchForgetIn(payloadOf(msg));
640
+ for (const e of entries) {
641
+ decrefInode(state, e.nodeid, Number(e.nlookup));
642
+ }
643
+ }
644
+ async function onLookup(hdr, msg, state) {
645
+ const body = payloadOf(msg);
646
+ const name = decodeName(body);
647
+ validateName(name);
648
+ const parent = requireInode(state, hdr.nodeid);
649
+ const parentAbs = await absPathForTraversal(state, parent);
650
+ const childAbs = join2(parentAbs, name);
651
+ const childRel = joinRel(parent.relPath, name);
652
+ const st = await lstat(childAbs);
653
+ const ino = bindInode(state, childRel);
654
+ return buildResponse(
655
+ hdr.unique,
656
+ buildEntryOut({
657
+ nodeid: ino,
658
+ generation: 0n,
659
+ entry_valid: 1n,
660
+ attr_valid: 1n,
661
+ entry_valid_nsec: 0,
662
+ attr_valid_nsec: 0,
663
+ attr: statToAttr(st, ino)
664
+ })
665
+ );
666
+ }
667
+ async function onGetattr(hdr, state) {
668
+ const entry = requireInode(state, hdr.nodeid);
669
+ const abs = await absPathForLstat(state, entry);
670
+ const st = await lstat(abs);
671
+ return buildResponse(
672
+ hdr.unique,
673
+ buildAttrOut({
674
+ attr_valid: 1n,
675
+ attr_valid_nsec: 0,
676
+ attr: statToAttr(st, hdr.nodeid)
677
+ })
678
+ );
679
+ }
680
+ async function onReadlink(hdr, state) {
681
+ const entry = requireInode(state, hdr.nodeid);
682
+ const abs = await absPathForLstat(state, entry);
683
+ const target = await readlink(abs);
684
+ return buildResponse(hdr.unique, new TextEncoder().encode(target));
685
+ }
686
+ async function onStatfs(hdr, state) {
687
+ try {
688
+ const s = await statfs(state.rootAbs);
689
+ return buildResponse(
690
+ hdr.unique,
691
+ buildKstatfs({
692
+ blocks: BigInt(s.blocks),
693
+ bfree: BigInt(s.bfree),
694
+ bavail: BigInt(s.bavail),
695
+ files: BigInt(s.files),
696
+ ffree: BigInt(s.ffree),
697
+ bsize: s.bsize,
698
+ namelen: 255,
699
+ frsize: s.bsize
700
+ })
701
+ );
702
+ } catch {
703
+ return buildResponse(
704
+ hdr.unique,
705
+ buildKstatfs({
706
+ blocks: 1000000n,
707
+ bfree: 500000n,
708
+ bavail: 500000n,
709
+ files: 100000n,
710
+ ffree: 99000n,
711
+ bsize: 4096,
712
+ namelen: 255,
713
+ frsize: 4096
714
+ })
715
+ );
716
+ }
717
+ }
718
+ async function onOpen(hdr, msg, state) {
719
+ const req = readOpenIn(payloadOf(msg));
720
+ const accessMode = req.flags & 3;
721
+ if (accessMode !== 0 && state.mode === "ro") {
722
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
723
+ }
724
+ const entry = requireInode(state, hdr.nodeid);
725
+ const abs = await absPathForTraversal(state, entry);
726
+ const fh = await fsOpen(abs, linuxOpenFlagsToNode(req.flags));
727
+ const id = state.nextHandle++;
728
+ state.handles.set(id, {
729
+ kind: "file",
730
+ fh,
731
+ lazyPagesContrib: isPagesImgPath(entry.relPath)
732
+ });
733
+ return buildResponse(hdr.unique, buildOpenOut({ fh: id, open_flags: 0 }));
734
+ }
735
+ async function onRead(hdr, msg, state) {
736
+ const req = readReadIn(payloadOf(msg));
737
+ const handle2 = state.handles.get(req.fh);
738
+ if (!handle2 || handle2.kind !== "file") {
739
+ return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
740
+ }
741
+ const buf = Buffer.alloc(req.size);
742
+ const { bytesRead } = await handle2.fh.read(buf, 0, req.size, Number(req.offset));
743
+ if (handle2.lazyPagesContrib && bytesRead > 0) {
744
+ state.bytesServedOnPagesImg += bytesRead;
745
+ }
746
+ return buildResponse(hdr.unique, new Uint8Array(buf.buffer, buf.byteOffset, bytesRead));
747
+ }
748
+ async function onRelease(hdr, msg, state) {
749
+ const { fh } = readReleaseIn(payloadOf(msg));
750
+ const entry = state.handles.get(fh);
751
+ if (entry && entry.kind === "file") {
752
+ state.handles.delete(fh);
753
+ await entry.fh.close().catch(() => {
754
+ });
755
+ }
756
+ return buildErrorResponse(hdr.unique, 0);
757
+ }
758
+ async function onOpendir(hdr, state) {
759
+ const entry = requireInode(state, hdr.nodeid);
760
+ const abs = await absPathForTraversal(state, entry);
761
+ const names = await readdir(abs);
762
+ const packed = [];
763
+ for (const name of names) {
764
+ try {
765
+ const st = await lstat(join2(abs, name));
766
+ packed.push(
767
+ buildDirent({
768
+ // ino in READDIR is informational. We use the host ino so
769
+ // the kernel can short-circuit LOOKUPs; if the guest does
770
+ // LOOKUP anyway we'll allocate our own then.
771
+ ino: BigInt(st.ino),
772
+ off: BigInt(packed.length + 1),
773
+ type: modeToDT(st.mode),
774
+ name
775
+ })
776
+ );
777
+ } catch (err) {
778
+ log("opendir: skip %s: %s", name, err.message);
779
+ }
780
+ }
781
+ const id = state.nextHandle++;
782
+ state.handles.set(id, { kind: "dir", entries: packed });
783
+ return buildResponse(hdr.unique, buildOpenOut({ fh: id, open_flags: 0 }));
784
+ }
785
+ async function onReaddir(hdr, msg, state) {
786
+ const req = readReadIn(payloadOf(msg));
787
+ const entry = state.handles.get(req.fh);
788
+ if (!entry || entry.kind !== "dir") {
789
+ return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
790
+ }
791
+ const startIdx = Number(req.offset);
792
+ let total = 0;
793
+ const picked = [];
794
+ for (let i = startIdx; i < entry.entries.length; i++) {
795
+ const e = entry.entries[i];
796
+ if (total + e.length > req.size) {
797
+ break;
798
+ }
799
+ picked.push(e);
800
+ total += e.length;
801
+ }
802
+ const payload = concatBuffers(picked, total);
803
+ return buildResponse(hdr.unique, payload);
804
+ }
805
+ function onReleasedir(hdr, msg, state) {
806
+ const { fh } = readReleaseIn(payloadOf(msg));
807
+ state.handles.delete(fh);
808
+ return buildErrorResponse(hdr.unique, 0);
809
+ }
810
+ async function onWrite(hdr, msg, state) {
811
+ if (state.mode === "ro") {
812
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
813
+ }
814
+ const body = payloadOf(msg);
815
+ const req = readWriteIn(body);
816
+ const data = body.subarray(FUSE_WRITE_IN_SIZE, FUSE_WRITE_IN_SIZE + req.size);
817
+ const handle2 = state.handles.get(req.fh);
818
+ if (!handle2 || handle2.kind !== "file") {
819
+ return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
820
+ }
821
+ const buf = Buffer.from(data.buffer, data.byteOffset, data.length);
822
+ const { bytesWritten } = await handle2.fh.write(buf, 0, data.length, Number(req.offset));
823
+ return buildResponse(hdr.unique, buildWriteOut({ size: bytesWritten }));
824
+ }
825
+ async function onCreate(hdr, msg, state) {
826
+ if (state.mode === "ro") {
827
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
828
+ }
829
+ const body = payloadOf(msg);
830
+ const req = readCreateIn(body);
831
+ const name = decodeName(body.subarray(16));
832
+ validateName(name);
833
+ const parent = requireInode(state, hdr.nodeid);
834
+ const parentAbs = await absPathForTraversal(state, parent);
835
+ const childAbs = join2(parentAbs, name);
836
+ const childRel = joinRel(parent.relPath, name);
837
+ const nodeFlags = linuxOpenFlagsToNode(req.flags) | fsConstants.O_CREAT;
838
+ const fh = await fsOpen(childAbs, nodeFlags, req.mode);
839
+ const st = await fh.stat();
840
+ const ino = bindInode(state, childRel);
841
+ const id = state.nextHandle++;
842
+ state.handles.set(id, {
843
+ kind: "file",
844
+ fh,
845
+ lazyPagesContrib: isPagesImgPath(childRel)
846
+ });
847
+ return buildResponse(
848
+ hdr.unique,
849
+ buildCreateOut(
850
+ {
851
+ nodeid: ino,
852
+ generation: 0n,
853
+ entry_valid: 1n,
854
+ attr_valid: 1n,
855
+ entry_valid_nsec: 0,
856
+ attr_valid_nsec: 0,
857
+ attr: statToAttr(st, ino)
858
+ },
859
+ { fh: id, open_flags: 0 }
860
+ )
861
+ );
862
+ }
863
+ async function onSymlink(hdr, msg, state) {
864
+ if (state.mode === "ro") {
865
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
866
+ }
867
+ const body = payloadOf(msg);
868
+ const firstNul = body.indexOf(0);
869
+ if (firstNul < 0) {
870
+ return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
871
+ }
872
+ const name = decodeName(body.subarray(0, firstNul));
873
+ validateName(name);
874
+ const targetEnd = body.indexOf(0, firstNul + 1);
875
+ const target = decodeName(body.subarray(firstNul + 1, targetEnd < 0 ? body.length : targetEnd));
876
+ if (target === "" || target.includes("\0")) {
877
+ return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
878
+ }
879
+ const parent = requireInode(state, hdr.nodeid);
880
+ const parentAbs = await absPathForTraversal(state, parent);
881
+ const childAbs = join2(parentAbs, name);
882
+ const childRel = joinRel(parent.relPath, name);
883
+ await symlink(target, childAbs);
884
+ const st = await lstat(childAbs);
885
+ const ino = bindInode(state, childRel);
886
+ return buildResponse(
887
+ hdr.unique,
888
+ buildEntryOut({
889
+ nodeid: ino,
890
+ generation: 0n,
891
+ entry_valid: 1n,
892
+ attr_valid: 1n,
893
+ entry_valid_nsec: 0,
894
+ attr_valid_nsec: 0,
895
+ attr: statToAttr(st, ino)
896
+ })
897
+ );
898
+ }
899
+ async function onLink(hdr, msg, state) {
900
+ if (state.mode === "ro") {
901
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
902
+ }
903
+ const body = payloadOf(msg);
904
+ const req = readLinkIn(body);
905
+ const name = decodeName(body.subarray(8));
906
+ validateName(name);
907
+ const oldEntry = requireInode(state, req.oldnodeid);
908
+ const parent = requireInode(state, hdr.nodeid);
909
+ const oldAbs = await absPathForLstat(state, oldEntry);
910
+ const parentAbs = await absPathForTraversal(state, parent);
911
+ const childAbs = join2(parentAbs, name);
912
+ const childRel = joinRel(parent.relPath, name);
913
+ await link(oldAbs, childAbs);
914
+ const st = await lstat(childAbs);
915
+ const ino = bindInode(state, childRel);
916
+ return buildResponse(
917
+ hdr.unique,
918
+ buildEntryOut({
919
+ nodeid: ino,
920
+ generation: 0n,
921
+ entry_valid: 1n,
922
+ attr_valid: 1n,
923
+ entry_valid_nsec: 0,
924
+ attr_valid_nsec: 0,
925
+ attr: statToAttr(st, ino)
926
+ })
927
+ );
928
+ }
929
+ async function onMkdir(hdr, msg, state) {
930
+ if (state.mode === "ro") {
931
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
932
+ }
933
+ const body = payloadOf(msg);
934
+ const req = readMkdirIn(body);
935
+ const name = decodeName(body.subarray(8));
936
+ validateName(name);
937
+ const parent = requireInode(state, hdr.nodeid);
938
+ const parentAbs = await absPathForTraversal(state, parent);
939
+ const childAbs = join2(parentAbs, name);
940
+ const childRel = joinRel(parent.relPath, name);
941
+ await mkdir(childAbs, { mode: req.mode & 4095 });
942
+ const st = await lstat(childAbs);
943
+ const ino = bindInode(state, childRel);
944
+ return buildResponse(
945
+ hdr.unique,
946
+ buildEntryOut({
947
+ nodeid: ino,
948
+ generation: 0n,
949
+ entry_valid: 1n,
950
+ attr_valid: 1n,
951
+ entry_valid_nsec: 0,
952
+ attr_valid_nsec: 0,
953
+ attr: statToAttr(st, ino)
954
+ })
955
+ );
956
+ }
957
+ async function onRmdir(hdr, msg, state) {
958
+ if (state.mode === "ro") {
959
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
960
+ }
961
+ const name = decodeName(payloadOf(msg));
962
+ validateName(name);
963
+ const parent = requireInode(state, hdr.nodeid);
964
+ const parentAbs = await absPathForTraversal(state, parent);
965
+ await rmdir(join2(parentAbs, name));
966
+ forgetByRelPath(state, joinRel(parent.relPath, name));
967
+ return buildErrorResponse(hdr.unique, 0);
968
+ }
969
+ async function onUnlink(hdr, msg, state) {
970
+ if (state.mode === "ro") {
971
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
972
+ }
973
+ const name = decodeName(payloadOf(msg));
974
+ validateName(name);
975
+ const parent = requireInode(state, hdr.nodeid);
976
+ const parentAbs = await absPathForTraversal(state, parent);
977
+ await unlink(join2(parentAbs, name));
978
+ forgetByRelPath(state, joinRel(parent.relPath, name));
979
+ return buildErrorResponse(hdr.unique, 0);
980
+ }
981
+ async function onRename(hdr, msg, state) {
982
+ if (state.mode === "ro") {
983
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
984
+ }
985
+ const body = payloadOf(msg);
986
+ const req = readRenameIn(body);
987
+ const after = body.subarray(8);
988
+ const firstNul = after.indexOf(0);
989
+ if (firstNul < 0) {
990
+ return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
991
+ }
992
+ const oldName = decodeName(after.subarray(0, firstNul));
993
+ const newName = decodeName(after.subarray(firstNul + 1));
994
+ validateName(oldName);
995
+ validateName(newName);
996
+ const oldParent = requireInode(state, hdr.nodeid);
997
+ const newParent = requireInode(state, req.newdir);
998
+ const oldParentAbs = await absPathForTraversal(state, oldParent);
999
+ const newParentAbs = await absPathForTraversal(state, newParent);
1000
+ await rename(join2(oldParentAbs, oldName), join2(newParentAbs, newName));
1001
+ forgetByRelPath(state, joinRel(oldParent.relPath, oldName));
1002
+ forgetByRelPath(state, joinRel(newParent.relPath, newName));
1003
+ return buildErrorResponse(hdr.unique, 0);
1004
+ }
1005
+ async function onSetattr(hdr, msg, state) {
1006
+ const req = readSetattrIn(payloadOf(msg));
1007
+ 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;
1008
+ if (isMutating && state.mode === "ro") {
1009
+ return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
1010
+ }
1011
+ const entry = requireInode(state, hdr.nodeid);
1012
+ const abs = await absPathForTraversal(state, entry);
1013
+ if (req.valid & FATTR.SIZE) {
1014
+ if (req.valid & FATTR.FH) {
1015
+ const handle2 = state.handles.get(req.fh);
1016
+ if (!handle2 || handle2.kind !== "file") {
1017
+ return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
1018
+ }
1019
+ await handle2.fh.truncate(Number(req.size));
1020
+ } else {
1021
+ await truncate(abs, Number(req.size));
1022
+ }
1023
+ }
1024
+ if (req.valid & FATTR.MODE) {
1025
+ await chmod(abs, req.mode & 4095);
1026
+ }
1027
+ if (req.valid & (FATTR.ATIME | FATTR.MTIME | FATTR.ATIME_NOW | FATTR.MTIME_NOW)) {
1028
+ const st2 = await lstat(abs);
1029
+ const now = Date.now() / 1e3;
1030
+ const a = req.valid & FATTR.ATIME_NOW ? now : req.valid & FATTR.ATIME ? Number(req.atime) + req.atimensec / 1e9 : st2.atimeMs / 1e3;
1031
+ const m = req.valid & FATTR.MTIME_NOW ? now : req.valid & FATTR.MTIME ? Number(req.mtime) + req.mtimensec / 1e9 : st2.mtimeMs / 1e3;
1032
+ await utimes(abs, a, m);
1033
+ }
1034
+ const st = await lstat(abs);
1035
+ return buildResponse(
1036
+ hdr.unique,
1037
+ buildAttrOut({
1038
+ attr_valid: 1n,
1039
+ attr_valid_nsec: 0,
1040
+ attr: statToAttr(st, hdr.nodeid)
1041
+ })
1042
+ );
1043
+ }
1044
+ async function onFsync(hdr, msg, state) {
1045
+ const body = payloadOf(msg);
1046
+ if (body.length < 8) {
1047
+ return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
1048
+ }
1049
+ const dv = new DataView(body.buffer, body.byteOffset, body.length);
1050
+ const fh = dv.getBigUint64(0, true);
1051
+ const handle2 = state.handles.get(fh);
1052
+ if (!handle2 || handle2.kind !== "file") {
1053
+ return buildErrorResponse(hdr.unique, -ERRNO.EBADF);
1054
+ }
1055
+ await handle2.fh.sync();
1056
+ return buildErrorResponse(hdr.unique, 0);
1057
+ }
1058
+ function decodeName(body) {
1059
+ const nulAt = body.indexOf(0);
1060
+ const end = nulAt >= 0 ? nulAt : body.length;
1061
+ return new TextDecoder("utf-8", { fatal: false }).decode(body.subarray(0, end));
1062
+ }
1063
+ function validateName(name) {
1064
+ if (name === "" || name === "." || name === ".." || name.includes("/") || name.includes("\0")) {
1065
+ const err = new Error(`invalid FUSE name: ${JSON.stringify(name)}`);
1066
+ err.code = "EINVAL";
1067
+ throw err;
1068
+ }
1069
+ }
1070
+ function joinRel(parentRel, name) {
1071
+ if (parentRel === "") {
1072
+ return name;
1073
+ }
1074
+ return `${parentRel}/${name}`;
1075
+ }
1076
+ function requireInode(state, ino) {
1077
+ const e = state.inodes.get(ino);
1078
+ if (!e) {
1079
+ const err = new Error(`stale inode ${ino}`);
1080
+ err.code = "ESTALE";
1081
+ throw err;
1082
+ }
1083
+ return e;
1084
+ }
1085
+ async function absPathForTraversal(state, entry) {
1086
+ if (entry.relPath === "") {
1087
+ return await realpath2(state.rootAbs);
1088
+ }
1089
+ return await resolveUnderRoot(state.rootAbs, entry.relPath);
1090
+ }
1091
+ async function absPathForLstat(state, entry) {
1092
+ if (entry.relPath === "") {
1093
+ return await realpath2(state.rootAbs);
1094
+ }
1095
+ const slashAt = entry.relPath.lastIndexOf("/");
1096
+ const parentRel = slashAt < 0 ? "" : entry.relPath.slice(0, slashAt);
1097
+ const baseName = slashAt < 0 ? entry.relPath : entry.relPath.slice(slashAt + 1);
1098
+ const parentAbs = parentRel === "" ? await realpath2(state.rootAbs) : await resolveUnderRoot(state.rootAbs, parentRel);
1099
+ return join2(parentAbs, baseName);
1100
+ }
1101
+ function bindInode(state, relPath) {
1102
+ for (const [ino2, e] of state.inodes) {
1103
+ if (e.relPath === relPath) {
1104
+ if (e.nlookup !== Number.POSITIVE_INFINITY) {
1105
+ e.nlookup++;
1106
+ }
1107
+ return ino2;
1108
+ }
1109
+ }
1110
+ const ino = state.nextInode++;
1111
+ state.inodes.set(ino, { relPath, nlookup: 1 });
1112
+ return ino;
1113
+ }
1114
+ function decrefInode(state, ino, n) {
1115
+ const e = state.inodes.get(ino);
1116
+ if (!e) {
1117
+ return;
1118
+ }
1119
+ if (e.nlookup === Number.POSITIVE_INFINITY) {
1120
+ return;
1121
+ }
1122
+ e.nlookup -= n;
1123
+ if (e.nlookup <= 0) {
1124
+ state.inodes.delete(ino);
1125
+ }
1126
+ }
1127
+ function forgetByRelPath(state, relPath) {
1128
+ for (const [ino, e] of state.inodes) {
1129
+ if (e.relPath === relPath) {
1130
+ state.inodes.delete(ino);
1131
+ return;
1132
+ }
1133
+ }
1134
+ }
1135
+ function linuxOpenFlagsToNode(flags) {
1136
+ const LINUX_O_WRONLY = 1;
1137
+ const LINUX_O_RDWR = 2;
1138
+ const LINUX_O_TRUNC = 512;
1139
+ const LINUX_O_APPEND = 1024;
1140
+ const accessMode = flags & 3;
1141
+ let out;
1142
+ if (accessMode === LINUX_O_WRONLY) {
1143
+ out = fsConstants.O_WRONLY;
1144
+ } else if (accessMode === LINUX_O_RDWR) {
1145
+ out = fsConstants.O_RDWR;
1146
+ } else {
1147
+ out = fsConstants.O_RDONLY;
1148
+ }
1149
+ if (flags & LINUX_O_TRUNC) {
1150
+ out |= fsConstants.O_TRUNC;
1151
+ }
1152
+ if (flags & LINUX_O_APPEND) {
1153
+ out |= fsConstants.O_APPEND;
1154
+ }
1155
+ return out;
1156
+ }
1157
+ function statToAttr(st, ino) {
1158
+ const atime = BigInt(Math.floor(st.atimeMs / 1e3));
1159
+ const mtime = BigInt(Math.floor(st.mtimeMs / 1e3));
1160
+ const ctime = BigInt(Math.floor(st.ctimeMs / 1e3));
1161
+ return {
1162
+ ino,
1163
+ size: BigInt(st.size),
1164
+ blocks: BigInt(st.blocks ?? 0),
1165
+ atime,
1166
+ mtime,
1167
+ ctime,
1168
+ atimensec: Math.floor(st.atimeMs % 1e3 * 1e6),
1169
+ mtimensec: Math.floor(st.mtimeMs % 1e3 * 1e6),
1170
+ ctimensec: Math.floor(st.ctimeMs % 1e3 * 1e6),
1171
+ mode: st.mode,
1172
+ nlink: st.nlink,
1173
+ uid: st.uid,
1174
+ gid: st.gid,
1175
+ rdev: st.rdev,
1176
+ blksize: st.blksize,
1177
+ flags: 0
1178
+ };
1179
+ }
1180
+ function modeToDT(mode) {
1181
+ const type = mode & 61440;
1182
+ switch (type) {
1183
+ case 16384:
1184
+ return DT.DIR;
1185
+ case 32768:
1186
+ return DT.REG;
1187
+ case 40960:
1188
+ return DT.LNK;
1189
+ case 8192:
1190
+ return DT.CHR;
1191
+ case 24576:
1192
+ return DT.BLK;
1193
+ case 4096:
1194
+ return DT.FIFO;
1195
+ case 49152:
1196
+ return DT.SOCK;
1197
+ default:
1198
+ return DT.UNKNOWN;
1199
+ }
1200
+ }
1201
+ function concatBuffers(parts, total) {
1202
+ const out = new Uint8Array(total);
1203
+ let cursor = 0;
1204
+ for (const p of parts) {
1205
+ out.set(p, cursor);
1206
+ cursor += p.length;
1207
+ }
1208
+ return out;
1209
+ }
1210
+ function mapErrorToErrno(err) {
1211
+ if (isMachinenError(err) && err instanceof MountError) {
1212
+ return -ERRNO.ENOENT;
1213
+ }
1214
+ const code = err?.code;
1215
+ if (!code) {
1216
+ return -ERRNO.EIO;
1217
+ }
1218
+ const key = code;
1219
+ if (key in ERRNO) {
1220
+ return -ERRNO[key];
1221
+ }
1222
+ return -ERRNO.EIO;
1223
+ }
1224
+
1225
+ // src/mount-server-bin.ts
1226
+ function parseArgs(argv) {
1227
+ const out = {};
1228
+ for (let i = 0; i < argv.length; i++) {
1229
+ const flag = argv[i];
1230
+ const value = argv[i + 1];
1231
+ if (value === void 0) {
1232
+ throw new Error(`missing value for ${flag}`);
1233
+ }
1234
+ switch (flag) {
1235
+ case "--uds":
1236
+ out.uds = value;
1237
+ i++;
1238
+ break;
1239
+ case "--root":
1240
+ out.root = value;
1241
+ i++;
1242
+ break;
1243
+ case "--mode":
1244
+ if (value !== "ro" && value !== "rw") {
1245
+ throw new Error(`--mode must be 'ro' or 'rw' (got '${value}')`);
1246
+ }
1247
+ out.mode = value;
1248
+ i++;
1249
+ break;
1250
+ case "--stats":
1251
+ out.stats = value;
1252
+ i++;
1253
+ break;
1254
+ default:
1255
+ throw new Error(`unknown flag '${flag}'`);
1256
+ }
1257
+ }
1258
+ for (const key of ["uds", "root", "mode", "stats"]) {
1259
+ if (out[key] === void 0) {
1260
+ throw new Error(`missing required --${key}`);
1261
+ }
1262
+ }
1263
+ return out;
1264
+ }
1265
+ function writeStatsAtomic(path, snapshot) {
1266
+ const tmp = `${path}.tmp.${process.pid}`;
1267
+ writeFileSync(tmp, JSON.stringify(snapshot));
1268
+ renameSync(tmp, path);
1269
+ }
1270
+ async function main() {
1271
+ const args = parseArgs(process.argv.slice(2));
1272
+ const handle2 = await serveLiveMount(args.uds, {
1273
+ rootAbs: args.root,
1274
+ mode: args.mode
1275
+ });
1276
+ writeStatsAtomic(args.stats, {
1277
+ bytesServedOnPagesImg: 0,
1278
+ updatedAtMs: Date.now()
1279
+ });
1280
+ let lastPublished = 0;
1281
+ const ticker = setInterval(() => {
1282
+ const cur = handle2.bytesServedOnPagesImg();
1283
+ if (cur === lastPublished) {
1284
+ return;
1285
+ }
1286
+ lastPublished = cur;
1287
+ try {
1288
+ writeStatsAtomic(args.stats, {
1289
+ bytesServedOnPagesImg: cur,
1290
+ updatedAtMs: Date.now()
1291
+ });
1292
+ } catch {
1293
+ }
1294
+ }, 250);
1295
+ ticker.unref();
1296
+ let shuttingDown = false;
1297
+ const shutdown2 = async (signal) => {
1298
+ if (shuttingDown) {
1299
+ return;
1300
+ }
1301
+ shuttingDown = true;
1302
+ clearInterval(ticker);
1303
+ try {
1304
+ writeStatsAtomic(args.stats, {
1305
+ bytesServedOnPagesImg: handle2.bytesServedOnPagesImg(),
1306
+ updatedAtMs: Date.now()
1307
+ });
1308
+ } catch {
1309
+ }
1310
+ try {
1311
+ await handle2.stop();
1312
+ } catch {
1313
+ }
1314
+ try {
1315
+ unlinkSync(args.stats);
1316
+ } catch {
1317
+ }
1318
+ process.exit(signal === "SIGTERM" ? 0 : 128 + (signalToNumber(signal) ?? 15));
1319
+ };
1320
+ process.on("SIGTERM", () => void shutdown2("SIGTERM"));
1321
+ process.on("SIGINT", () => void shutdown2("SIGINT"));
1322
+ process.on("SIGHUP", () => void shutdown2("SIGHUP"));
1323
+ }
1324
+ function signalToNumber(signal) {
1325
+ switch (signal) {
1326
+ case "SIGTERM":
1327
+ return 15;
1328
+ case "SIGINT":
1329
+ return 2;
1330
+ case "SIGHUP":
1331
+ return 1;
1332
+ default:
1333
+ return void 0;
1334
+ }
1335
+ }
1336
+ main().catch((err) => {
1337
+ process.stderr.write(`mount-server-bin: ${err instanceof Error ? err.message : String(err)}
1338
+ `);
1339
+ process.exit(1);
1340
+ });
1341
+ //# sourceMappingURL=mount-server-bin.js.map