@machinen/runtime 0.1.1 → 0.2.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.
@@ -8,9 +8,13 @@ import { renameSync, writeFileSync, unlinkSync } from "fs";
8
8
 
9
9
  // src/mount-server.ts
10
10
  import { createServer } from "net";
11
+ import { execFile as execFileCb } from "child_process";
11
12
  import {
12
13
  chmod,
14
+ lchmod,
15
+ lchown,
13
16
  link,
17
+ lutimes,
14
18
  mkdir,
15
19
  open as fsOpen,
16
20
  lstat,
@@ -27,6 +31,7 @@ import {
27
31
  } from "fs/promises";
28
32
  import { constants as fsConstants } from "fs";
29
33
  import { join as join2 } from "path";
34
+ import { promisify } from "util";
30
35
  import debug from "debug";
31
36
 
32
37
  // src/mount-resolver.ts
@@ -91,17 +96,36 @@ var FUSE_OP = {
91
96
  STATFS: 17,
92
97
  RELEASE: 18,
93
98
  FSYNC: 20,
99
+ SETXATTR: 21,
100
+ GETXATTR: 22,
101
+ LISTXATTR: 23,
102
+ REMOVEXATTR: 24,
94
103
  FLUSH: 25,
95
104
  INIT: 26,
96
105
  OPENDIR: 27,
97
106
  READDIR: 28,
98
107
  RELEASEDIR: 29,
108
+ FSYNCDIR: 30,
109
+ GETLK: 31,
110
+ SETLK: 32,
111
+ SETLKW: 33,
99
112
  ACCESS: 34,
100
113
  CREATE: 35,
101
114
  INTERRUPT: 36,
102
115
  DESTROY: 38,
103
116
  BATCH_FORGET: 42,
104
- READDIRPLUS: 44
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
105
129
  };
106
130
  var FATTR = {
107
131
  MODE: 1 << 0,
@@ -408,6 +432,59 @@ function readLinkIn(buf, off = 0) {
408
432
  oldnodeid: dv.getBigUint64(0, true)
409
433
  };
410
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
+ }
411
488
  var FUSE_RENAME_IN_SIZE = 8;
412
489
  function readRenameIn(buf, off = 0) {
413
490
  const dv = viewOf(buf, off, FUSE_RENAME_IN_SIZE);
@@ -415,6 +492,66 @@ function readRenameIn(buf, off = 0) {
415
492
  newdir: dv.getBigUint64(0, true)
416
493
  };
417
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
+ }
418
555
  function viewOf(buf, offset, len) {
419
556
  if (offset < 0 || len < 0 || offset + len > buf.length) {
420
557
  throw new Error(
@@ -431,21 +568,35 @@ function payloadOf(message) {
431
568
  }
432
569
 
433
570
  // src/mount-server.ts
571
+ var execFile = promisify(execFileCb);
434
572
  var log = debug("machinen:mount-server");
435
573
  var ERRNO = {
436
574
  EPERM: 1,
437
575
  ENOENT: 2,
576
+ EINTR: 4,
438
577
  EIO: 5,
578
+ ENXIO: 6,
439
579
  EBADF: 9,
580
+ EAGAIN: 11,
440
581
  EACCES: 13,
441
582
  EBUSY: 16,
442
583
  EEXIST: 17,
443
584
  ENOTDIR: 20,
444
585
  EISDIR: 21,
445
586
  EINVAL: 22,
587
+ ERANGE: 34,
446
588
  EROFS: 30,
447
589
  ENOSYS: 38,
448
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,
449
600
  ESTALE: 116
450
601
  };
451
602
  async function serveLiveMount(udsPath, opts) {
@@ -475,7 +626,9 @@ function createState(rootAbs, mode) {
475
626
  handles: /* @__PURE__ */ new Map(),
476
627
  nextHandle: 1n,
477
628
  socket: null,
478
- bytesServedOnPagesImg: 0
629
+ bytesServedOnPagesImg: 0,
630
+ locks: /* @__PURE__ */ new Map(),
631
+ lockWaiters: []
479
632
  };
480
633
  }
481
634
  var PAGES_IMG_RE = /(?:^|\/)pages-\d+\.img$/;
@@ -492,6 +645,8 @@ async function shutdown(server, state) {
492
645
  }
493
646
  }
494
647
  state.handles.clear();
648
+ cancelAllWaiters(state);
649
+ state.locks.clear();
495
650
  await new Promise((done) => server.close(() => done()));
496
651
  }
497
652
  async function handleConnection(sock, state) {
@@ -502,6 +657,8 @@ async function handleConnection(sock, state) {
502
657
  state.socket = sock;
503
658
  sock.on("close", () => {
504
659
  state.socket = null;
660
+ cancelAllWaiters(state);
661
+ state.locks.clear();
505
662
  });
506
663
  sock.on("error", (err) => log("socket error: %s", err.message));
507
664
  try {
@@ -603,6 +760,28 @@ async function handle(hdr, msg, state) {
603
760
  return onReleasedir(hdr, msg, state);
604
761
  case FUSE_OP.FSYNC:
605
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);
606
785
  case FUSE_OP.FLUSH:
607
786
  case FUSE_OP.ACCESS:
608
787
  return buildErrorResponse(hdr.unique, 0);
@@ -613,7 +792,7 @@ async function handle(hdr, msg, state) {
613
792
  function onInit(hdr, msg) {
614
793
  const init = readInitIn(payloadOf(msg));
615
794
  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;
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;
617
796
  const flags = init.flags & supported;
618
797
  const out = buildInitOut({
619
798
  major: FUSE_KERNEL_VERSION,
@@ -728,7 +907,8 @@ async function onOpen(hdr, msg, state) {
728
907
  state.handles.set(id, {
729
908
  kind: "file",
730
909
  fh,
731
- lazyPagesContrib: isPagesImgPath(entry.relPath)
910
+ lazyPagesContrib: isPagesImgPath(entry.relPath),
911
+ nodeid: hdr.nodeid
732
912
  });
733
913
  return buildResponse(hdr.unique, buildOpenOut({ fh: id, open_flags: 0 }));
734
914
  }
@@ -746,10 +926,11 @@ async function onRead(hdr, msg, state) {
746
926
  return buildResponse(hdr.unique, new Uint8Array(buf.buffer, buf.byteOffset, bytesRead));
747
927
  }
748
928
  async function onRelease(hdr, msg, state) {
749
- const { fh } = readReleaseIn(payloadOf(msg));
750
- const entry = state.handles.get(fh);
929
+ const rel = readReleaseIn(payloadOf(msg));
930
+ const entry = state.handles.get(rel.fh);
751
931
  if (entry && entry.kind === "file") {
752
- state.handles.delete(fh);
932
+ state.handles.delete(rel.fh);
933
+ dropLocksFor(state, entry.nodeid, rel.lock_owner);
753
934
  await entry.fh.close().catch(() => {
754
935
  });
755
936
  }
@@ -842,7 +1023,8 @@ async function onCreate(hdr, msg, state) {
842
1023
  state.handles.set(id, {
843
1024
  kind: "file",
844
1025
  fh,
845
- lazyPagesContrib: isPagesImgPath(childRel)
1026
+ lazyPagesContrib: isPagesImgPath(childRel),
1027
+ nodeid: ino
846
1028
  });
847
1029
  return buildResponse(
848
1030
  hdr.unique,
@@ -1009,8 +1191,13 @@ async function onSetattr(hdr, msg, state) {
1009
1191
  return buildErrorResponse(hdr.unique, -ERRNO.EROFS);
1010
1192
  }
1011
1193
  const entry = requireInode(state, hdr.nodeid);
1012
- const abs = await absPathForTraversal(state, entry);
1194
+ const abs = await absPathForLstat(state, entry);
1195
+ const st0 = await lstat(abs);
1196
+ const isSymlink = (st0.mode & 61440) === 40960;
1013
1197
  if (req.valid & FATTR.SIZE) {
1198
+ if (isSymlink) {
1199
+ return buildErrorResponse(hdr.unique, -ERRNO.EINVAL);
1200
+ }
1014
1201
  if (req.valid & FATTR.FH) {
1015
1202
  const handle2 = state.handles.get(req.fh);
1016
1203
  if (!handle2 || handle2.kind !== "file") {
@@ -1022,14 +1209,40 @@ async function onSetattr(hdr, msg, state) {
1022
1209
  }
1023
1210
  }
1024
1211
  if (req.valid & FATTR.MODE) {
1025
- await chmod(abs, req.mode & 4095);
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
+ }
1026
1224
  }
1027
1225
  if (req.valid & (FATTR.ATIME | FATTR.MTIME | FATTR.ATIME_NOW | FATTR.MTIME_NOW)) {
1028
- const st2 = await lstat(abs);
1029
1226
  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);
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
+ }
1033
1246
  }
1034
1247
  const st = await lstat(abs);
1035
1248
  return buildResponse(
@@ -1041,6 +1254,10 @@ async function onSetattr(hdr, msg, state) {
1041
1254
  })
1042
1255
  );
1043
1256
  }
1257
+ function isUnsupportedFsErr(err) {
1258
+ const code = err?.code;
1259
+ return code === "ENOTSUP" || code === "EOPNOTSUPP" || code === "ENOSYS";
1260
+ }
1044
1261
  async function onFsync(hdr, msg, state) {
1045
1262
  const body = payloadOf(msg);
1046
1263
  if (body.length < 8) {
@@ -1055,6 +1272,510 @@ async function onFsync(hdr, msg, state) {
1055
1272
  await handle2.fh.sync();
1056
1273
  return buildErrorResponse(hdr.unique, 0);
1057
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
+ }
1058
1779
  function decodeName(body) {
1059
1780
  const nulAt = body.indexOf(0);
1060
1781
  const end = nulAt >= 0 ? nulAt : body.length;
@@ -1215,6 +1936,9 @@ function mapErrorToErrno(err) {
1215
1936
  if (!code) {
1216
1937
  return -ERRNO.EIO;
1217
1938
  }
1939
+ if (code === "EOPNOTSUPP") {
1940
+ return -ERRNO.ENOTSUP;
1941
+ }
1218
1942
  const key = code;
1219
1943
  if (key in ERRNO) {
1220
1944
  return -ERRNO[key];