@php-wasm/node 3.0.54 → 3.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.
- package/index.cjs +1188 -673
- package/index.js +1163 -638
- package/lib/file-lock-manager-for-posix.d.ts +14 -0
- package/lib/file-lock-manager-for-windows.d.ts +15 -0
- package/lib/index.d.ts +4 -2
- package/lib/load-runtime.d.ts +16 -21
- package/lib/wasm-kernel-space.d.ts +9 -0
- package/lib/wasm-user-space.d.ts +81 -0
- package/package.json +15 -17
- package/test/file-lock-manager-composite-with-posix--test-process.d.ts +1 -0
- package/test/file-lock-manager-composite-with-posix--test-worker-thread.d.ts +1 -0
- package/test/file-lock-manager-composite-with-windows--test-process.d.ts +1 -0
- package/test/file-lock-manager-composite-with-windows--test-worker-thread.d.ts +1 -0
- package/test/file-lock-manager-for-posix--test-process.d.ts +1 -0
- package/test/file-lock-manager-for-windows--test-process.d.ts +1 -0
- package/test/file-lock-manager-test-utils.d.ts +20 -0
- package/test/file-lock-manager-tests.d.ts +6 -0
- package/lib/file-lock-manager-for-node.d.ts +0 -164
- package/lib/file-lock-manager.d.ts +0 -96
- package/lib/syscalls-for-node.d.ts +0 -9
package/index.js
CHANGED
|
@@ -390,9 +390,1156 @@ async function withNetworking(phpModuleArgs = {}) {
|
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
// packages/php-wasm/node/src/lib/load-runtime.ts
|
|
393
|
-
import {
|
|
393
|
+
import {
|
|
394
|
+
loadPHPRuntime,
|
|
395
|
+
FSHelpers as FSHelpers5,
|
|
396
|
+
FileLockManagerComposite
|
|
397
|
+
} from "@php-wasm/universal";
|
|
398
|
+
|
|
399
|
+
// packages/php-wasm/node/src/lib/wasm-user-space.ts
|
|
400
|
+
import { lookup as lookup2 } from "dns/promises";
|
|
401
|
+
function bindUserSpace({ fileLockManager }, {
|
|
402
|
+
pid,
|
|
403
|
+
memory: { HEAP16, HEAP64, HEAP32 },
|
|
404
|
+
constants: {
|
|
405
|
+
F_RDLCK,
|
|
406
|
+
F_WRLCK,
|
|
407
|
+
F_UNLCK,
|
|
408
|
+
F_GETFL,
|
|
409
|
+
O_ACCMODE,
|
|
410
|
+
O_RDONLY,
|
|
411
|
+
O_WRONLY,
|
|
412
|
+
O_APPEND,
|
|
413
|
+
O_NONBLOCK,
|
|
414
|
+
F_SETFL,
|
|
415
|
+
F_GETLK,
|
|
416
|
+
F_SETLK,
|
|
417
|
+
F_SETLKW,
|
|
418
|
+
SEEK_SET,
|
|
419
|
+
SEEK_CUR,
|
|
420
|
+
SEEK_END,
|
|
421
|
+
LOCK_SH,
|
|
422
|
+
LOCK_EX,
|
|
423
|
+
LOCK_NB,
|
|
424
|
+
LOCK_UN
|
|
425
|
+
},
|
|
426
|
+
errnoCodes: { EBADF, EINVAL, EAGAIN, EWOULDBLOCK },
|
|
427
|
+
wasmImports: { builtin_fcntl64, builtin_fd_close, js_wasm_trace },
|
|
428
|
+
wasmExports: { wasm_get_end_offset },
|
|
429
|
+
syscalls: { getStreamFromFD },
|
|
430
|
+
FS,
|
|
431
|
+
PROXYFS,
|
|
432
|
+
NODEFS
|
|
433
|
+
}) {
|
|
434
|
+
class VarArgsAccessor {
|
|
435
|
+
constructor(argsAddr) {
|
|
436
|
+
this.argsAddr = argsAddr;
|
|
437
|
+
}
|
|
438
|
+
getNextAsPointer() {
|
|
439
|
+
return this.getNextAsInt();
|
|
440
|
+
}
|
|
441
|
+
getNextAsInt() {
|
|
442
|
+
const fourByteOffset = this.argsAddr >> 2;
|
|
443
|
+
const value = HEAP32[fourByteOffset];
|
|
444
|
+
this.argsAddr += 4;
|
|
445
|
+
return value;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const locking = {
|
|
449
|
+
/*
|
|
450
|
+
* This is a set of possibly locked file descriptors.
|
|
451
|
+
*
|
|
452
|
+
* When a file descriptor is closed, we need to release any associated held by this process.
|
|
453
|
+
* Instead of trying remember and forget file descriptors as they are locked and unlocked,
|
|
454
|
+
* we just track file descriptors we have locked before and try an unlock when they are closed.
|
|
455
|
+
*/
|
|
456
|
+
maybeLockedFds: /* @__PURE__ */ new Set(),
|
|
457
|
+
lockStateToFcntl: {
|
|
458
|
+
shared: F_RDLCK,
|
|
459
|
+
exclusive: F_WRLCK,
|
|
460
|
+
unlocked: F_UNLCK
|
|
461
|
+
},
|
|
462
|
+
fcntlToLockState: {
|
|
463
|
+
[F_RDLCK]: "shared",
|
|
464
|
+
[F_WRLCK]: "exclusive",
|
|
465
|
+
[F_UNLCK]: "unlocked"
|
|
466
|
+
},
|
|
467
|
+
is_path_to_shared_fs(path2) {
|
|
468
|
+
const { node } = FS.lookupPath(path2, { noent_okay: true });
|
|
469
|
+
if (!node) {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
if (node.mount.type !== PROXYFS) {
|
|
473
|
+
return !!node.isSharedFS;
|
|
474
|
+
}
|
|
475
|
+
const nodePath = PROXYFS.realPath(node);
|
|
476
|
+
const backingFs = node?.mount?.opts?.["fs"];
|
|
477
|
+
if (backingFs) {
|
|
478
|
+
const { node: backingNode } = backingFs.lookupPath(nodePath, {
|
|
479
|
+
noent_okay: true
|
|
480
|
+
});
|
|
481
|
+
return !!backingNode?.isSharedFS;
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
484
|
+
},
|
|
485
|
+
get_fd_access_mode(fd) {
|
|
486
|
+
return builtin_fcntl64(fd, F_GETFL) & O_ACCMODE;
|
|
487
|
+
},
|
|
488
|
+
get_vfs_path_from_fd(fd) {
|
|
489
|
+
try {
|
|
490
|
+
return [FS.readlink(`/proc/self/fd/${fd}`), 0];
|
|
491
|
+
} catch {
|
|
492
|
+
return [null, EBADF];
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
get_native_path_from_vfs_path(vfsPath) {
|
|
496
|
+
const { node } = FS.lookupPath(vfsPath, {
|
|
497
|
+
noent_okay: true
|
|
498
|
+
});
|
|
499
|
+
if (!node) {
|
|
500
|
+
throw new Error(`No node found for VFS path ${vfsPath}`);
|
|
501
|
+
}
|
|
502
|
+
if (node.mount.type === NODEFS) {
|
|
503
|
+
return NODEFS.realPath(node);
|
|
504
|
+
} else if (node.mount.type === PROXYFS) {
|
|
505
|
+
const { node: backingNode, path: backingPath } = node.mount.opts["fs"].lookupPath(vfsPath);
|
|
506
|
+
js_wasm_trace(
|
|
507
|
+
"backingNode for %s: %s",
|
|
508
|
+
vfsPath,
|
|
509
|
+
backingPath,
|
|
510
|
+
backingNode
|
|
511
|
+
);
|
|
512
|
+
return backingNode.mount.type.realPath(backingNode);
|
|
513
|
+
} else {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`Unsupported filesystem type for path ${vfsPath}`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
get_native_fd_from_emscripten_fd(fd) {
|
|
520
|
+
try {
|
|
521
|
+
const stream = getStreamFromFD(fd);
|
|
522
|
+
if (stream.nfd === void 0) {
|
|
523
|
+
return [null, EBADF];
|
|
524
|
+
}
|
|
525
|
+
return [stream.nfd, 0];
|
|
526
|
+
} catch {
|
|
527
|
+
return [null, EBADF];
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
check_lock_params(fd, l_type) {
|
|
531
|
+
const accessMode = locking.get_fd_access_mode(fd);
|
|
532
|
+
if (l_type === F_WRLCK && accessMode === O_RDONLY || l_type === F_RDLCK && accessMode === O_WRONLY) {
|
|
533
|
+
js_wasm_trace(
|
|
534
|
+
"check_lock_params(%d, %d, %d) EBADF",
|
|
535
|
+
fd,
|
|
536
|
+
l_type,
|
|
537
|
+
accessMode
|
|
538
|
+
);
|
|
539
|
+
return EBADF;
|
|
540
|
+
}
|
|
541
|
+
return 0;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
const emscripten_flock_l_type_offset = 0;
|
|
545
|
+
const emscripten_flock_l_whence_offset = 2;
|
|
546
|
+
const emscripten_flock_l_start_offset = 8;
|
|
547
|
+
const emscripten_flock_l_len_offset = 16;
|
|
548
|
+
const emscripten_flock_l_pid_offset = 24;
|
|
549
|
+
function readFlockStruct(flockStructAddress) {
|
|
550
|
+
return {
|
|
551
|
+
l_type: HEAP16[
|
|
552
|
+
// Shift right by 1 to divide by 2^1.
|
|
553
|
+
flockStructAddress + emscripten_flock_l_type_offset >> 1
|
|
554
|
+
],
|
|
555
|
+
l_whence: HEAP16[
|
|
556
|
+
// Shift right by 1 to divide by 2^1.
|
|
557
|
+
flockStructAddress + emscripten_flock_l_whence_offset >> 1
|
|
558
|
+
],
|
|
559
|
+
l_start: HEAP64[
|
|
560
|
+
// Shift right by 3 to divide by 2^3.
|
|
561
|
+
flockStructAddress + emscripten_flock_l_start_offset >> 3
|
|
562
|
+
],
|
|
563
|
+
l_len: HEAP64[
|
|
564
|
+
// Shift right by 3 to divide by 2^3.
|
|
565
|
+
flockStructAddress + emscripten_flock_l_len_offset >> 3
|
|
566
|
+
],
|
|
567
|
+
l_pid: HEAP32[
|
|
568
|
+
// Shift right by 2 to divide by 2^2.
|
|
569
|
+
flockStructAddress + emscripten_flock_l_pid_offset >> 2
|
|
570
|
+
]
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function updateFlockStruct(flockStructAddress, fields) {
|
|
574
|
+
if (fields.l_type !== void 0) {
|
|
575
|
+
HEAP16[
|
|
576
|
+
// Shift right by 1 to divide by 2^1.
|
|
577
|
+
flockStructAddress + emscripten_flock_l_type_offset >> 1
|
|
578
|
+
] = fields.l_type;
|
|
579
|
+
}
|
|
580
|
+
if (fields.l_whence !== void 0) {
|
|
581
|
+
HEAP16[
|
|
582
|
+
// Shift right by 1 to divide by 2^1.
|
|
583
|
+
flockStructAddress + emscripten_flock_l_whence_offset >> 1
|
|
584
|
+
] = fields.l_whence;
|
|
585
|
+
}
|
|
586
|
+
if (fields.l_start !== void 0) {
|
|
587
|
+
HEAP64[
|
|
588
|
+
// Shift right by 3 to divide by 2^3.
|
|
589
|
+
flockStructAddress + emscripten_flock_l_start_offset >> 3
|
|
590
|
+
] = fields.l_start;
|
|
591
|
+
}
|
|
592
|
+
if (fields.l_len !== void 0) {
|
|
593
|
+
HEAP64[
|
|
594
|
+
// Shift right by 3 to divide by 2^3.
|
|
595
|
+
flockStructAddress + emscripten_flock_l_len_offset >> 3
|
|
596
|
+
] = fields.l_len;
|
|
597
|
+
}
|
|
598
|
+
if (fields.l_pid !== void 0) {
|
|
599
|
+
HEAP32[
|
|
600
|
+
// Shift right by 2 to divide by 2^2.
|
|
601
|
+
flockStructAddress + emscripten_flock_l_pid_offset >> 2
|
|
602
|
+
] = fields.l_pid;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function getBaseAddress(fd, whence, startOffset) {
|
|
606
|
+
let baseAddress;
|
|
607
|
+
switch (whence) {
|
|
608
|
+
case SEEK_SET:
|
|
609
|
+
baseAddress = 0n;
|
|
610
|
+
break;
|
|
611
|
+
case SEEK_CUR:
|
|
612
|
+
try {
|
|
613
|
+
const stream = getStreamFromFD(fd);
|
|
614
|
+
baseAddress = FS.llseek(stream, 0, whence);
|
|
615
|
+
} catch (e) {
|
|
616
|
+
js_wasm_trace(
|
|
617
|
+
"get_base_address(%d, %d, %d) getStreamFromFD error %s",
|
|
618
|
+
fd,
|
|
619
|
+
whence,
|
|
620
|
+
startOffset,
|
|
621
|
+
e
|
|
622
|
+
);
|
|
623
|
+
return [null, EINVAL];
|
|
624
|
+
}
|
|
625
|
+
break;
|
|
626
|
+
case SEEK_END:
|
|
627
|
+
baseAddress = wasm_get_end_offset(fd);
|
|
628
|
+
break;
|
|
629
|
+
default:
|
|
630
|
+
return [null, EINVAL];
|
|
631
|
+
}
|
|
632
|
+
if (baseAddress == -1) {
|
|
633
|
+
return [null, EBADF];
|
|
634
|
+
}
|
|
635
|
+
const resolvedOffset = baseAddress + startOffset;
|
|
636
|
+
if (resolvedOffset < 0) {
|
|
637
|
+
return [null, EINVAL];
|
|
638
|
+
}
|
|
639
|
+
return [resolvedOffset, 0];
|
|
640
|
+
}
|
|
641
|
+
function fcntl64(fd, cmd, varargs) {
|
|
642
|
+
js_wasm_trace("fcntl64(%d, %d)", fd, cmd);
|
|
643
|
+
if (!fileLockManager) {
|
|
644
|
+
js_wasm_trace(
|
|
645
|
+
"fcntl64(%d, %d) file lock manager is not available. delegate to Emscripten builtin fcntl64.",
|
|
646
|
+
fd,
|
|
647
|
+
cmd
|
|
648
|
+
);
|
|
649
|
+
return builtin_fcntl64(fd, cmd, varargs);
|
|
650
|
+
}
|
|
651
|
+
switch (cmd) {
|
|
652
|
+
case F_GETLK: {
|
|
653
|
+
const reportUnlockedFileByDefault = function reportUnlockedFileByDefault2() {
|
|
654
|
+
updateFlockStruct(flockStructAddr, {
|
|
655
|
+
l_type: F_UNLCK
|
|
656
|
+
});
|
|
657
|
+
return 0;
|
|
658
|
+
};
|
|
659
|
+
js_wasm_trace("fcntl(%d, F_GETLK)", fd);
|
|
660
|
+
const [vfsPath, vfsPathErrno] = locking.get_vfs_path_from_fd(fd);
|
|
661
|
+
if (vfsPathErrno !== 0) {
|
|
662
|
+
js_wasm_trace(
|
|
663
|
+
"fcntl(%d, F_GETLK) %s get_vfs_path_from_fd errno %d",
|
|
664
|
+
fd,
|
|
665
|
+
vfsPath,
|
|
666
|
+
vfsPathErrno
|
|
667
|
+
);
|
|
668
|
+
return -EBADF;
|
|
669
|
+
}
|
|
670
|
+
const varArgsAccessor = new VarArgsAccessor(varargs);
|
|
671
|
+
const flockStructAddr = varArgsAccessor.getNextAsPointer();
|
|
672
|
+
if (!locking.is_path_to_shared_fs(vfsPath) || fileLockManager === void 0) {
|
|
673
|
+
js_wasm_trace(
|
|
674
|
+
"fcntl(%d, F_GETLK) locking is not implemented for non-NodeFS path '%s'",
|
|
675
|
+
fd,
|
|
676
|
+
vfsPath
|
|
677
|
+
);
|
|
678
|
+
return reportUnlockedFileByDefault();
|
|
679
|
+
}
|
|
680
|
+
const flockStruct = readFlockStruct(flockStructAddr);
|
|
681
|
+
if (!(flockStruct.l_type in locking.fcntlToLockState)) {
|
|
682
|
+
return -EINVAL;
|
|
683
|
+
}
|
|
684
|
+
const paramsCheckErrno = locking.check_lock_params(
|
|
685
|
+
fd,
|
|
686
|
+
flockStruct.l_type
|
|
687
|
+
);
|
|
688
|
+
if (paramsCheckErrno !== 0) {
|
|
689
|
+
js_wasm_trace(
|
|
690
|
+
"fcntl(%d, F_GETLK) %s check_lock_params errno %d",
|
|
691
|
+
fd,
|
|
692
|
+
vfsPath,
|
|
693
|
+
paramsCheckErrno
|
|
694
|
+
);
|
|
695
|
+
return -EINVAL;
|
|
696
|
+
}
|
|
697
|
+
const requestedLockType = locking.fcntlToLockState[flockStruct.l_type];
|
|
698
|
+
const [absoluteStartOffset, baseAddressErrno] = getBaseAddress(
|
|
699
|
+
fd,
|
|
700
|
+
flockStruct.l_whence,
|
|
701
|
+
flockStruct.l_start
|
|
702
|
+
);
|
|
703
|
+
if (baseAddressErrno !== 0) {
|
|
704
|
+
js_wasm_trace(
|
|
705
|
+
"fcntl(%d, F_GETLK) %s get_base_address errno %d",
|
|
706
|
+
fd,
|
|
707
|
+
vfsPath,
|
|
708
|
+
baseAddressErrno
|
|
709
|
+
);
|
|
710
|
+
return -EINVAL;
|
|
711
|
+
}
|
|
712
|
+
const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
|
|
713
|
+
if (nativeFdErrno !== 0) {
|
|
714
|
+
js_wasm_trace(
|
|
715
|
+
"fcntl(%d, F_GETLK) get_native_fd_from_emscripten_fd errno %d",
|
|
716
|
+
fd,
|
|
717
|
+
nativeFdErrno
|
|
718
|
+
);
|
|
719
|
+
return -nativeFdErrno;
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
|
|
723
|
+
const conflictingLock = fileLockManager.findFirstConflictingByteRangeLock(
|
|
724
|
+
nativeFilePath,
|
|
725
|
+
{
|
|
726
|
+
type: requestedLockType,
|
|
727
|
+
start: absoluteStartOffset,
|
|
728
|
+
end: absoluteStartOffset + flockStruct.l_len,
|
|
729
|
+
pid,
|
|
730
|
+
fd: nativeFd
|
|
731
|
+
}
|
|
732
|
+
);
|
|
733
|
+
if (conflictingLock === void 0) {
|
|
734
|
+
js_wasm_trace(
|
|
735
|
+
"fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=unlocked start=0x%x end=0x%x",
|
|
736
|
+
fd,
|
|
737
|
+
vfsPath,
|
|
738
|
+
absoluteStartOffset,
|
|
739
|
+
absoluteStartOffset + flockStruct.l_len
|
|
740
|
+
);
|
|
741
|
+
updateFlockStruct(flockStructAddr, {
|
|
742
|
+
l_type: F_UNLCK
|
|
743
|
+
});
|
|
744
|
+
return 0;
|
|
745
|
+
}
|
|
746
|
+
js_wasm_trace(
|
|
747
|
+
"fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=%s start=0x%x end=0x%x conflictingLock %d",
|
|
748
|
+
fd,
|
|
749
|
+
vfsPath,
|
|
750
|
+
conflictingLock.type,
|
|
751
|
+
conflictingLock.start,
|
|
752
|
+
conflictingLock.end,
|
|
753
|
+
conflictingLock.pid
|
|
754
|
+
);
|
|
755
|
+
const fcntlLockState = locking.lockStateToFcntl[conflictingLock.type];
|
|
756
|
+
updateFlockStruct(flockStructAddr, {
|
|
757
|
+
l_type: fcntlLockState,
|
|
758
|
+
l_whence: SEEK_SET,
|
|
759
|
+
l_start: conflictingLock.start,
|
|
760
|
+
l_len: BigInt(
|
|
761
|
+
conflictingLock.end - conflictingLock.start
|
|
762
|
+
),
|
|
763
|
+
l_pid: conflictingLock.pid
|
|
764
|
+
});
|
|
765
|
+
return 0;
|
|
766
|
+
} catch (e) {
|
|
767
|
+
js_wasm_trace(
|
|
768
|
+
"fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock error %s",
|
|
769
|
+
fd,
|
|
770
|
+
vfsPath,
|
|
771
|
+
e
|
|
772
|
+
);
|
|
773
|
+
return -EINVAL;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
case F_SETLKW:
|
|
777
|
+
case F_SETLK: {
|
|
778
|
+
js_wasm_trace("fcntl(%d, F_SETLK)", fd);
|
|
779
|
+
const [vfsPath, vfsPathErrno] = locking.get_vfs_path_from_fd(fd);
|
|
780
|
+
if (vfsPathErrno !== 0) {
|
|
781
|
+
js_wasm_trace(
|
|
782
|
+
"fcntl(%d, F_SETLK) %s get_vfs_path_from_fd errno %d",
|
|
783
|
+
fd,
|
|
784
|
+
vfsPath,
|
|
785
|
+
vfsPathErrno
|
|
786
|
+
);
|
|
787
|
+
return -vfsPathErrno;
|
|
788
|
+
}
|
|
789
|
+
if (!locking.is_path_to_shared_fs(vfsPath)) {
|
|
790
|
+
js_wasm_trace(
|
|
791
|
+
"fcntl(%d, F_SETLK) locking is not implemented for non-NodeFS path %s",
|
|
792
|
+
fd,
|
|
793
|
+
vfsPath
|
|
794
|
+
);
|
|
795
|
+
return 0;
|
|
796
|
+
}
|
|
797
|
+
const varArgsAccessor = new VarArgsAccessor(varargs);
|
|
798
|
+
const flockStructAddr = varArgsAccessor.getNextAsPointer();
|
|
799
|
+
const flockStruct = readFlockStruct(flockStructAddr);
|
|
800
|
+
const [absoluteStartOffset, baseAddressErrno] = getBaseAddress(
|
|
801
|
+
fd,
|
|
802
|
+
flockStruct.l_whence,
|
|
803
|
+
flockStruct.l_start
|
|
804
|
+
);
|
|
805
|
+
if (baseAddressErrno !== 0) {
|
|
806
|
+
js_wasm_trace(
|
|
807
|
+
"fcntl(%d, F_SETLK) %s get_base_address errno %d",
|
|
808
|
+
fd,
|
|
809
|
+
vfsPath,
|
|
810
|
+
baseAddressErrno
|
|
811
|
+
);
|
|
812
|
+
return -EINVAL;
|
|
813
|
+
}
|
|
814
|
+
if (!(flockStruct.l_type in locking.fcntlToLockState)) {
|
|
815
|
+
js_wasm_trace(
|
|
816
|
+
"fcntl(%d, F_SETLK) %s invalid lock type %d",
|
|
817
|
+
fd,
|
|
818
|
+
vfsPath,
|
|
819
|
+
flockStruct.l_type
|
|
820
|
+
);
|
|
821
|
+
return -EINVAL;
|
|
822
|
+
}
|
|
823
|
+
const paramsCheckErrno = locking.check_lock_params(
|
|
824
|
+
fd,
|
|
825
|
+
flockStruct.l_type
|
|
826
|
+
);
|
|
827
|
+
if (paramsCheckErrno !== 0) {
|
|
828
|
+
js_wasm_trace(
|
|
829
|
+
"fcntl(%d, F_SETLK) %s check_lock_params errno %d",
|
|
830
|
+
fd,
|
|
831
|
+
vfsPath,
|
|
832
|
+
paramsCheckErrno
|
|
833
|
+
);
|
|
834
|
+
return -paramsCheckErrno;
|
|
835
|
+
}
|
|
836
|
+
const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
|
|
837
|
+
if (nativeFdErrno !== 0) {
|
|
838
|
+
js_wasm_trace(
|
|
839
|
+
"fcntl(%d, F_SETLK) get_native_fd_from_emscripten_fd errno %d",
|
|
840
|
+
fd,
|
|
841
|
+
nativeFdErrno
|
|
842
|
+
);
|
|
843
|
+
return -nativeFdErrno;
|
|
844
|
+
}
|
|
845
|
+
const requestedLockType = locking.fcntlToLockState[flockStruct.l_type];
|
|
846
|
+
const rangeLock = {
|
|
847
|
+
type: requestedLockType,
|
|
848
|
+
start: absoluteStartOffset,
|
|
849
|
+
end: absoluteStartOffset + flockStruct.l_len,
|
|
850
|
+
pid,
|
|
851
|
+
fd: nativeFd
|
|
852
|
+
};
|
|
853
|
+
try {
|
|
854
|
+
const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
|
|
855
|
+
js_wasm_trace(
|
|
856
|
+
"fcntl(%d, F_SETLK) %s calling lockFileByteRange for range lock %s",
|
|
857
|
+
fd,
|
|
858
|
+
vfsPath,
|
|
859
|
+
rangeLock
|
|
860
|
+
);
|
|
861
|
+
const waitForLock = cmd === F_SETLKW;
|
|
862
|
+
const succeeded = fileLockManager.lockFileByteRange(
|
|
863
|
+
nativeFilePath,
|
|
864
|
+
rangeLock,
|
|
865
|
+
waitForLock
|
|
866
|
+
);
|
|
867
|
+
if (succeeded) {
|
|
868
|
+
locking.maybeLockedFds.add(nativeFd);
|
|
869
|
+
}
|
|
870
|
+
js_wasm_trace(
|
|
871
|
+
"fcntl(%d, F_SETLK) %s lockFileByteRange returned %d for range lock %s",
|
|
872
|
+
fd,
|
|
873
|
+
vfsPath,
|
|
874
|
+
succeeded,
|
|
875
|
+
rangeLock
|
|
876
|
+
);
|
|
877
|
+
return succeeded ? 0 : -EAGAIN;
|
|
878
|
+
} catch (e) {
|
|
879
|
+
js_wasm_trace(
|
|
880
|
+
"fcntl(%d, F_SETLK) %s lockFileByteRange error %s for range lock %s",
|
|
881
|
+
fd,
|
|
882
|
+
vfsPath,
|
|
883
|
+
e,
|
|
884
|
+
rangeLock
|
|
885
|
+
);
|
|
886
|
+
return -EINVAL;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
case F_SETFL: {
|
|
890
|
+
let arg = 0;
|
|
891
|
+
if (varargs !== void 0) {
|
|
892
|
+
const varArgsAccessor = new VarArgsAccessor(varargs);
|
|
893
|
+
arg = varArgsAccessor.getNextAsInt();
|
|
894
|
+
}
|
|
895
|
+
const stream = getStreamFromFD(fd);
|
|
896
|
+
const SETFL_MASK = O_APPEND | O_NONBLOCK;
|
|
897
|
+
stream.flags = arg & SETFL_MASK | stream.flags & ~SETFL_MASK;
|
|
898
|
+
return 0;
|
|
899
|
+
}
|
|
900
|
+
default:
|
|
901
|
+
return builtin_fcntl64(fd, cmd, varargs);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function flock(fd, op) {
|
|
905
|
+
js_wasm_trace("flock(%d, %d)", fd, op);
|
|
906
|
+
if (!fileLockManager) {
|
|
907
|
+
js_wasm_trace(
|
|
908
|
+
"flock(%d, %d) file lock manager is not available. succeed by default as Emscripten does.",
|
|
909
|
+
fd,
|
|
910
|
+
op
|
|
911
|
+
);
|
|
912
|
+
return 0;
|
|
913
|
+
}
|
|
914
|
+
const flockToLockOpType = {
|
|
915
|
+
[LOCK_SH]: "shared",
|
|
916
|
+
[LOCK_EX]: "exclusive",
|
|
917
|
+
[LOCK_UN]: "unlock"
|
|
918
|
+
};
|
|
919
|
+
const [vfsPath, vfsPathErrno] = locking.get_vfs_path_from_fd(fd);
|
|
920
|
+
if (vfsPathErrno !== 0) {
|
|
921
|
+
js_wasm_trace(
|
|
922
|
+
"flock(%d, %d) get_vfs_path_from_fd errno %d",
|
|
923
|
+
fd,
|
|
924
|
+
op,
|
|
925
|
+
vfsPath,
|
|
926
|
+
vfsPathErrno
|
|
927
|
+
);
|
|
928
|
+
return -vfsPathErrno;
|
|
929
|
+
}
|
|
930
|
+
if (!locking.is_path_to_shared_fs(vfsPath)) {
|
|
931
|
+
js_wasm_trace(
|
|
932
|
+
"flock(%d, %d) locking is not implemented for non-NodeFS path %s",
|
|
933
|
+
fd,
|
|
934
|
+
op,
|
|
935
|
+
vfsPath
|
|
936
|
+
);
|
|
937
|
+
return 0;
|
|
938
|
+
}
|
|
939
|
+
const paramsCheckErrno = locking.check_lock_params(fd, op);
|
|
940
|
+
if (paramsCheckErrno !== 0) {
|
|
941
|
+
js_wasm_trace(
|
|
942
|
+
"flock(%d, %d) %s check_lock_params errno %d",
|
|
943
|
+
fd,
|
|
944
|
+
op,
|
|
945
|
+
vfsPath,
|
|
946
|
+
paramsCheckErrno
|
|
947
|
+
);
|
|
948
|
+
return -paramsCheckErrno;
|
|
949
|
+
}
|
|
950
|
+
const maskedOp = op & (LOCK_SH | LOCK_EX | LOCK_UN);
|
|
951
|
+
const waitForLock = (op & LOCK_NB) === 0;
|
|
952
|
+
if (maskedOp === 0) {
|
|
953
|
+
js_wasm_trace("flock(%d, %d) invalid flock() operation", fd, op);
|
|
954
|
+
return -EINVAL;
|
|
955
|
+
}
|
|
956
|
+
const lockOpType = flockToLockOpType[maskedOp];
|
|
957
|
+
if (lockOpType === void 0) {
|
|
958
|
+
js_wasm_trace("flock(%d, %d) invalid flock() operation", fd, op);
|
|
959
|
+
return -EINVAL;
|
|
960
|
+
}
|
|
961
|
+
const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
|
|
962
|
+
if (nativeFdErrno !== 0) {
|
|
963
|
+
js_wasm_trace(
|
|
964
|
+
"js_flock(%d, %d) get_native_fd_from_emscripten_fd errno %d",
|
|
965
|
+
fd,
|
|
966
|
+
op,
|
|
967
|
+
nativeFdErrno
|
|
968
|
+
);
|
|
969
|
+
return -nativeFdErrno;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
|
|
973
|
+
const succeeded = fileLockManager.lockWholeFile(nativeFilePath, {
|
|
974
|
+
type: lockOpType,
|
|
975
|
+
pid,
|
|
976
|
+
fd: nativeFd,
|
|
977
|
+
waitForLock
|
|
978
|
+
});
|
|
979
|
+
js_wasm_trace(
|
|
980
|
+
"flock(%d, %d) lockWholeFile %s returned %d",
|
|
981
|
+
fd,
|
|
982
|
+
op,
|
|
983
|
+
vfsPath,
|
|
984
|
+
succeeded
|
|
985
|
+
);
|
|
986
|
+
if (succeeded) {
|
|
987
|
+
locking.maybeLockedFds.add(nativeFd);
|
|
988
|
+
}
|
|
989
|
+
return succeeded ? 0 : -EWOULDBLOCK;
|
|
990
|
+
} catch (e) {
|
|
991
|
+
js_wasm_trace("flock(%d, %d) lockWholeFile error %s", fd, op, e);
|
|
992
|
+
return -EINVAL;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function fd_close(fd) {
|
|
996
|
+
if (!fileLockManager) {
|
|
997
|
+
js_wasm_trace(
|
|
998
|
+
"fd_close(%d) file lock manager is not available. delegate to Emscripten builtin fd_close.",
|
|
999
|
+
fd
|
|
1000
|
+
);
|
|
1001
|
+
return builtin_fd_close(fd);
|
|
1002
|
+
}
|
|
1003
|
+
const [vfsPath, vfsPathResolutionErrno] = locking.get_vfs_path_from_fd(fd);
|
|
1004
|
+
const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
|
|
1005
|
+
const fdCloseResult = builtin_fd_close(fd);
|
|
1006
|
+
if (fdCloseResult !== 0) {
|
|
1007
|
+
js_wasm_trace(
|
|
1008
|
+
"fd_close(%d) %s result %d",
|
|
1009
|
+
fd,
|
|
1010
|
+
vfsPath,
|
|
1011
|
+
fdCloseResult
|
|
1012
|
+
);
|
|
1013
|
+
return fdCloseResult;
|
|
1014
|
+
}
|
|
1015
|
+
if (!locking.maybeLockedFds.has(nativeFd)) {
|
|
1016
|
+
js_wasm_trace(
|
|
1017
|
+
"fd_close(%d) not in maybe-locked-list %s result %d",
|
|
1018
|
+
fd,
|
|
1019
|
+
vfsPath,
|
|
1020
|
+
fdCloseResult
|
|
1021
|
+
);
|
|
1022
|
+
return fdCloseResult;
|
|
1023
|
+
}
|
|
1024
|
+
if (vfsPathResolutionErrno !== 0) {
|
|
1025
|
+
js_wasm_trace(
|
|
1026
|
+
"fd_close(%d) get_vfs_path_from_fd error %d",
|
|
1027
|
+
fd,
|
|
1028
|
+
vfsPathResolutionErrno
|
|
1029
|
+
);
|
|
1030
|
+
return fdCloseResult;
|
|
1031
|
+
}
|
|
1032
|
+
if (nativeFdErrno !== 0) {
|
|
1033
|
+
js_wasm_trace(
|
|
1034
|
+
"fd_close(%d) %s get_native_fd_from_emscripten_fd error %d",
|
|
1035
|
+
fd,
|
|
1036
|
+
vfsPath,
|
|
1037
|
+
nativeFdErrno
|
|
1038
|
+
);
|
|
1039
|
+
return fdCloseResult;
|
|
1040
|
+
}
|
|
1041
|
+
if (!locking.is_path_to_shared_fs(vfsPath)) {
|
|
1042
|
+
return fdCloseResult;
|
|
1043
|
+
}
|
|
1044
|
+
try {
|
|
1045
|
+
js_wasm_trace("fd_close(%d) %s release locks", fd, vfsPath);
|
|
1046
|
+
const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
|
|
1047
|
+
fileLockManager.releaseLocksOnFdClose(
|
|
1048
|
+
pid,
|
|
1049
|
+
nativeFd,
|
|
1050
|
+
nativeFilePath
|
|
1051
|
+
);
|
|
1052
|
+
js_wasm_trace("fd_close(%d) %s release locks success", fd, vfsPath);
|
|
1053
|
+
} catch (e) {
|
|
1054
|
+
js_wasm_trace("fd_close(%d) %s error '%s'", fd, vfsPath, e);
|
|
1055
|
+
}
|
|
1056
|
+
return fdCloseResult;
|
|
1057
|
+
}
|
|
1058
|
+
function js_release_file_locks() {
|
|
1059
|
+
js_wasm_trace("js_release_file_locks()");
|
|
1060
|
+
if (pid === void 0) {
|
|
1061
|
+
js_wasm_trace("js_release_file_locks pid is undefined");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
if (fileLockManager === void 0) {
|
|
1065
|
+
js_wasm_trace(
|
|
1066
|
+
"js_release_file_locks file lock manager is undefined"
|
|
1067
|
+
);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
try {
|
|
1071
|
+
fileLockManager.releaseLocksForProcess(pid);
|
|
1072
|
+
js_wasm_trace("js_release_file_locks succeeded");
|
|
1073
|
+
} catch (e) {
|
|
1074
|
+
js_wasm_trace("js_release_file_locks error %s", e);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
async function gethostbyname(hostname) {
|
|
1078
|
+
const { address } = await lookup2(hostname, {
|
|
1079
|
+
family: 4,
|
|
1080
|
+
verbatim: false
|
|
1081
|
+
});
|
|
1082
|
+
return address;
|
|
1083
|
+
}
|
|
1084
|
+
return {
|
|
1085
|
+
fcntl64,
|
|
1086
|
+
flock,
|
|
1087
|
+
fd_close,
|
|
1088
|
+
js_release_file_locks,
|
|
1089
|
+
gethostbyname
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// packages/php-wasm/node/src/lib/load-runtime.ts
|
|
394
1094
|
import fs5 from "fs";
|
|
395
1095
|
|
|
1096
|
+
// packages/php-wasm/node/src/lib/file-lock-manager-for-posix.ts
|
|
1097
|
+
import { MAX_ADDRESSABLE_FILE_OFFSET } from "@php-wasm/universal";
|
|
1098
|
+
import { constants, fcntlSync, flockSync } from "fs-ext-extra-prebuilt";
|
|
1099
|
+
import { logger as logger2 } from "@php-wasm/logger";
|
|
1100
|
+
function isLockDenialError(e) {
|
|
1101
|
+
if (e && typeof e === "object" && "code" in e) {
|
|
1102
|
+
const code = e.code;
|
|
1103
|
+
return code === "EWOULDBLOCK" || code === "EAGAIN" || code === "EACCES";
|
|
1104
|
+
}
|
|
1105
|
+
return false;
|
|
1106
|
+
}
|
|
1107
|
+
var FileLockManagerForPosix = class {
|
|
1108
|
+
constructor() {
|
|
1109
|
+
this.wholeFileLockMap = /* @__PURE__ */ new Map();
|
|
1110
|
+
this.rangeLockedFds = /* @__PURE__ */ new Map();
|
|
1111
|
+
}
|
|
1112
|
+
lockWholeFile(path2, op) {
|
|
1113
|
+
const opType = op.type === "unlock" ? "un" : op.waitForLock ? op.type === "exclusive" ? "ex" : "sh" : op.type === "exclusive" ? "exnb" : "shnb";
|
|
1114
|
+
try {
|
|
1115
|
+
flockSync(op.fd, opType);
|
|
1116
|
+
if (op.type === "unlock") {
|
|
1117
|
+
this.wholeFileLockMap.get(op.pid)?.delete(op.fd);
|
|
1118
|
+
} else {
|
|
1119
|
+
if (!this.wholeFileLockMap.has(op.pid)) {
|
|
1120
|
+
this.wholeFileLockMap.set(op.pid, /* @__PURE__ */ new Map());
|
|
1121
|
+
}
|
|
1122
|
+
this.wholeFileLockMap.get(op.pid).set(op.fd, {
|
|
1123
|
+
...op,
|
|
1124
|
+
path: path2
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
return true;
|
|
1128
|
+
} catch (e) {
|
|
1129
|
+
if (!isLockDenialError(e)) {
|
|
1130
|
+
logger2.warn("flock(): unexpected error", e);
|
|
1131
|
+
}
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
lockFileByteRange(path2, op, waitForLock) {
|
|
1136
|
+
if (op.start === op.end) {
|
|
1137
|
+
op = {
|
|
1138
|
+
...op,
|
|
1139
|
+
end: MAX_ADDRESSABLE_FILE_OFFSET
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
const fcntlCmd = waitForLock ? "setlkw" : "setlk";
|
|
1143
|
+
const fcntlOp = op.type === "unlocked" ? constants.F_UNLCK : op.type === "exclusive" ? constants.F_WRLCK : constants.F_RDLCK;
|
|
1144
|
+
try {
|
|
1145
|
+
fcntlSync(
|
|
1146
|
+
op.fd,
|
|
1147
|
+
fcntlCmd,
|
|
1148
|
+
fcntlOp,
|
|
1149
|
+
Number(op.start),
|
|
1150
|
+
Number(op.end - op.start)
|
|
1151
|
+
);
|
|
1152
|
+
if (!this.rangeLockedFds.has(op.pid)) {
|
|
1153
|
+
this.rangeLockedFds.set(op.pid, /* @__PURE__ */ new Map());
|
|
1154
|
+
}
|
|
1155
|
+
const pidMap = this.rangeLockedFds.get(op.pid);
|
|
1156
|
+
if (!pidMap.has(path2)) {
|
|
1157
|
+
pidMap.set(path2, /* @__PURE__ */ new Set());
|
|
1158
|
+
}
|
|
1159
|
+
pidMap.get(path2).add(op.fd);
|
|
1160
|
+
return true;
|
|
1161
|
+
} catch (e) {
|
|
1162
|
+
if (!isLockDenialError(e)) {
|
|
1163
|
+
logger2.warn("fcntl(): unexpected error", e);
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
findFirstConflictingByteRangeLock(path2, op) {
|
|
1169
|
+
if (op.type === "unlocked") {
|
|
1170
|
+
return void 0;
|
|
1171
|
+
}
|
|
1172
|
+
const obtainedLock = this.lockFileByteRange(path2, op, false);
|
|
1173
|
+
if (obtainedLock) {
|
|
1174
|
+
this.lockFileByteRange(path2, { ...op, type: "unlocked" }, true);
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1177
|
+
return {
|
|
1178
|
+
type: "exclusive",
|
|
1179
|
+
start: 0n,
|
|
1180
|
+
end: 0xffffffffffffffffn,
|
|
1181
|
+
pid: -1
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
releaseLocksForProcess(targetPid) {
|
|
1185
|
+
const fdMap = this.wholeFileLockMap.get(targetPid);
|
|
1186
|
+
if (fdMap) {
|
|
1187
|
+
for (const storedLock of fdMap.values()) {
|
|
1188
|
+
try {
|
|
1189
|
+
this.lockWholeFile(storedLock.path, {
|
|
1190
|
+
...storedLock,
|
|
1191
|
+
type: "unlock"
|
|
1192
|
+
});
|
|
1193
|
+
} catch (e) {
|
|
1194
|
+
logger2.error(
|
|
1195
|
+
`releaseLocksForProcess: failed to unlock whole-file lock for pid=${targetPid} fd=${storedLock.fd}`,
|
|
1196
|
+
e
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
this.wholeFileLockMap.delete(targetPid);
|
|
1201
|
+
}
|
|
1202
|
+
for (const [path2, fdSet] of this.rangeLockedFds.get(targetPid) ?? []) {
|
|
1203
|
+
for (const fd of fdSet) {
|
|
1204
|
+
try {
|
|
1205
|
+
this.lockFileByteRange(
|
|
1206
|
+
path2,
|
|
1207
|
+
{
|
|
1208
|
+
pid: targetPid,
|
|
1209
|
+
fd,
|
|
1210
|
+
type: "unlocked",
|
|
1211
|
+
start: 0n,
|
|
1212
|
+
end: MAX_ADDRESSABLE_FILE_OFFSET
|
|
1213
|
+
},
|
|
1214
|
+
false
|
|
1215
|
+
);
|
|
1216
|
+
} catch (e) {
|
|
1217
|
+
logger2.error(
|
|
1218
|
+
`releaseLocksForProcess: failed to unlock byte range for pid=${targetPid} fd=${fd} path=${path2}`,
|
|
1219
|
+
e
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
this.rangeLockedFds.delete(targetPid);
|
|
1225
|
+
}
|
|
1226
|
+
releaseLocksOnFdClose(targetPid, targetFd, targetPath) {
|
|
1227
|
+
this.wholeFileLockMap.get(targetPid)?.delete(targetFd);
|
|
1228
|
+
this.rangeLockedFds.get(targetPid)?.delete(targetPath);
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
// packages/php-wasm/node/src/lib/file-lock-manager-for-windows.ts
|
|
1233
|
+
import {
|
|
1234
|
+
lockFileExSync,
|
|
1235
|
+
unlockFileExSync,
|
|
1236
|
+
constants as constants2
|
|
1237
|
+
} from "fs-ext-extra-prebuilt";
|
|
1238
|
+
import { logger as logger3 } from "@php-wasm/logger";
|
|
1239
|
+
import {
|
|
1240
|
+
MAX_ADDRESSABLE_FILE_OFFSET as MAX_ADDRESSABLE_FILE_OFFSET2,
|
|
1241
|
+
FileLockIntervalTree
|
|
1242
|
+
} from "@php-wasm/universal";
|
|
1243
|
+
function toLowAndHigh32BitNumbers(num) {
|
|
1244
|
+
const low = Number(num & 0xffffffffn);
|
|
1245
|
+
const high = Number(num >> 32n & 0xffffffffn);
|
|
1246
|
+
return [low, high];
|
|
1247
|
+
}
|
|
1248
|
+
function isErrnoError(e) {
|
|
1249
|
+
return e !== null && typeof e === "object" && ("errno" in e || "code" in e || "syscall" in e);
|
|
1250
|
+
}
|
|
1251
|
+
function tryLockFileExSync(fd, flags, start, end) {
|
|
1252
|
+
const [offsetLow, offsetHigh] = toLowAndHigh32BitNumbers(start);
|
|
1253
|
+
const [lengthLow, lengthHigh] = toLowAndHigh32BitNumbers(end - start);
|
|
1254
|
+
try {
|
|
1255
|
+
lockFileExSync(fd, flags, offsetLow, offsetHigh, lengthLow, lengthHigh);
|
|
1256
|
+
return true;
|
|
1257
|
+
} catch (e) {
|
|
1258
|
+
if (!isErrnoError(e)) {
|
|
1259
|
+
throw e;
|
|
1260
|
+
}
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function tryUnlockFileExSync(fd, start, end) {
|
|
1265
|
+
const [offsetLow, offsetHigh] = toLowAndHigh32BitNumbers(start);
|
|
1266
|
+
const [lengthLow, lengthHigh] = toLowAndHigh32BitNumbers(end - start);
|
|
1267
|
+
try {
|
|
1268
|
+
unlockFileExSync(fd, offsetLow, offsetHigh, lengthLow, lengthHigh);
|
|
1269
|
+
return true;
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
if (!isErrnoError(e)) {
|
|
1272
|
+
throw e;
|
|
1273
|
+
}
|
|
1274
|
+
return false;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
var FileLockManagerForWindows = class {
|
|
1278
|
+
constructor() {
|
|
1279
|
+
this.wholeFileLockMap = /* @__PURE__ */ new Map();
|
|
1280
|
+
this.rangeLockedFds = /* @__PURE__ */ new Map();
|
|
1281
|
+
}
|
|
1282
|
+
lockWholeFile(path2, op) {
|
|
1283
|
+
const start = 0n;
|
|
1284
|
+
const end = 2n ** 64n - 1n;
|
|
1285
|
+
if (op.type === "unlock") {
|
|
1286
|
+
const success2 = tryUnlockFileExSync(op.fd, start, end);
|
|
1287
|
+
if (success2) {
|
|
1288
|
+
this.wholeFileLockMap.get(op.pid)?.delete(op.fd);
|
|
1289
|
+
if (this.wholeFileLockMap.get(op.pid)?.size === 0) {
|
|
1290
|
+
this.wholeFileLockMap.delete(op.pid);
|
|
1291
|
+
}
|
|
1292
|
+
} else {
|
|
1293
|
+
logger3.warn(
|
|
1294
|
+
`lockWholeFile: unlock failed for pid=${op.pid} fd=${op.fd} path=${path2}`
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
return success2;
|
|
1298
|
+
}
|
|
1299
|
+
const preexistingLock = this.wholeFileLockMap.get(op.pid)?.get(op.fd);
|
|
1300
|
+
if (op.type === preexistingLock?.type) {
|
|
1301
|
+
return true;
|
|
1302
|
+
}
|
|
1303
|
+
let flags = 0;
|
|
1304
|
+
if (!op.waitForLock) {
|
|
1305
|
+
flags |= constants2.LOCKFILE_FAIL_IMMEDIATELY;
|
|
1306
|
+
}
|
|
1307
|
+
let success = false;
|
|
1308
|
+
if (op.type === "shared") {
|
|
1309
|
+
success = tryLockFileExSync(op.fd, flags, start, end);
|
|
1310
|
+
if (success && preexistingLock?.type === "exclusive") {
|
|
1311
|
+
const exclusiveUnlockSuccess = tryUnlockFileExSync(
|
|
1312
|
+
op.fd,
|
|
1313
|
+
start,
|
|
1314
|
+
end
|
|
1315
|
+
);
|
|
1316
|
+
if (!exclusiveUnlockSuccess) {
|
|
1317
|
+
const message = "Failed to unlock preexisting exclusive lock after failing to obtain shared lock";
|
|
1318
|
+
logger3.error(message);
|
|
1319
|
+
throw new Error(message);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
} else if (op.type === "exclusive") {
|
|
1323
|
+
flags |= constants2.LOCKFILE_EXCLUSIVE_LOCK;
|
|
1324
|
+
let sharedUnlockSuccess;
|
|
1325
|
+
if (preexistingLock?.type === "shared") {
|
|
1326
|
+
sharedUnlockSuccess = tryUnlockFileExSync(op.fd, start, end);
|
|
1327
|
+
if (!sharedUnlockSuccess) {
|
|
1328
|
+
logger3.warn(
|
|
1329
|
+
`lockWholeFile: failed to release shared lock before exclusive upgrade for pid=${op.pid} fd=${op.fd}`
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
success = tryLockFileExSync(op.fd, flags, start, end);
|
|
1334
|
+
if (!success) {
|
|
1335
|
+
logger3.debug(
|
|
1336
|
+
`lockWholeFile: failed to obtain exclusive lock for pid=${op.pid} fd=${op.fd}`
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
if (!success && sharedUnlockSuccess) {
|
|
1340
|
+
const sharedReLockResult = tryLockFileExSync(
|
|
1341
|
+
op.fd,
|
|
1342
|
+
// Wait to restore the shared lock.
|
|
1343
|
+
0,
|
|
1344
|
+
start,
|
|
1345
|
+
end
|
|
1346
|
+
);
|
|
1347
|
+
if (!sharedReLockResult) {
|
|
1348
|
+
const message = "Failed to re-lock preexisting shared lock after failing to obtain exclusive lock";
|
|
1349
|
+
logger3.error(message);
|
|
1350
|
+
throw new Error(message);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
} else {
|
|
1354
|
+
throw new Error(`Unexpected wholeFileLock() op: '${op.type}'`);
|
|
1355
|
+
}
|
|
1356
|
+
if (success) {
|
|
1357
|
+
if (!this.wholeFileLockMap.has(op.pid)) {
|
|
1358
|
+
this.wholeFileLockMap.set(op.pid, /* @__PURE__ */ new Map());
|
|
1359
|
+
}
|
|
1360
|
+
this.wholeFileLockMap.get(op.pid).set(op.fd, {
|
|
1361
|
+
...op,
|
|
1362
|
+
path: path2
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
return success;
|
|
1366
|
+
}
|
|
1367
|
+
lockFileByteRange(path2, op, waitForLock) {
|
|
1368
|
+
if (op.start === op.end) {
|
|
1369
|
+
op = {
|
|
1370
|
+
...op,
|
|
1371
|
+
end: MAX_ADDRESSABLE_FILE_OFFSET2
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
if (!this.rangeLockedFds.has(path2)) {
|
|
1375
|
+
this.rangeLockedFds.set(path2, new FileLockIntervalTree());
|
|
1376
|
+
}
|
|
1377
|
+
const lockedRangeTree = this.rangeLockedFds.get(path2);
|
|
1378
|
+
const overlappingLocks = lockedRangeTree.findOverlapping(op);
|
|
1379
|
+
let preexistingLock;
|
|
1380
|
+
if (overlappingLocks.length === 1 && overlappingLocks[0].pid === op.pid && // NOTE: FD shouldn't matter for fcntl() F_SETLK because it is a process-level lock,
|
|
1381
|
+
// but it matters for Windows where locks are fd-specific.
|
|
1382
|
+
overlappingLocks[0].fd === op.fd && overlappingLocks[0].start === op.start && overlappingLocks[0].end === op.end) {
|
|
1383
|
+
preexistingLock = overlappingLocks[0];
|
|
1384
|
+
}
|
|
1385
|
+
if (op.type === preexistingLock?.type) {
|
|
1386
|
+
return true;
|
|
1387
|
+
}
|
|
1388
|
+
let flags = 0;
|
|
1389
|
+
if (!waitForLock) {
|
|
1390
|
+
flags |= constants2.LOCKFILE_FAIL_IMMEDIATELY;
|
|
1391
|
+
}
|
|
1392
|
+
if (op.type === "shared") {
|
|
1393
|
+
const success = tryLockFileExSync(op.fd, flags, op.start, op.end);
|
|
1394
|
+
if (!success) {
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
if (preexistingLock?.type === "exclusive") {
|
|
1398
|
+
const releasedPreexistingExclusiveLock = tryUnlockFileExSync(
|
|
1399
|
+
preexistingLock.fd,
|
|
1400
|
+
preexistingLock.start,
|
|
1401
|
+
preexistingLock.end
|
|
1402
|
+
);
|
|
1403
|
+
if (!releasedPreexistingExclusiveLock) {
|
|
1404
|
+
const message = "Failed to unlock preexisting exclusive lock after obtaining a shared lock";
|
|
1405
|
+
logger3.error(message);
|
|
1406
|
+
throw new Error(message);
|
|
1407
|
+
}
|
|
1408
|
+
lockedRangeTree.remove(preexistingLock);
|
|
1409
|
+
}
|
|
1410
|
+
lockedRangeTree.insert(op);
|
|
1411
|
+
return true;
|
|
1412
|
+
} else if (op.type === "exclusive") {
|
|
1413
|
+
let sharedUnlockSuccess;
|
|
1414
|
+
if (preexistingLock?.type === "shared") {
|
|
1415
|
+
sharedUnlockSuccess = tryUnlockFileExSync(
|
|
1416
|
+
op.fd,
|
|
1417
|
+
op.start,
|
|
1418
|
+
op.end
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
if (op.type === "exclusive") {
|
|
1422
|
+
flags |= constants2.LOCKFILE_EXCLUSIVE_LOCK;
|
|
1423
|
+
}
|
|
1424
|
+
const success = tryLockFileExSync(op.fd, flags, op.start, op.end);
|
|
1425
|
+
if (!success) {
|
|
1426
|
+
if (preexistingLock && sharedUnlockSuccess) {
|
|
1427
|
+
const sharedRelockSuccess = tryLockFileExSync(
|
|
1428
|
+
op.fd,
|
|
1429
|
+
0,
|
|
1430
|
+
op.start,
|
|
1431
|
+
op.end
|
|
1432
|
+
);
|
|
1433
|
+
if (!sharedRelockSuccess) {
|
|
1434
|
+
const message = "Failed to re-lock preexisting shared lock after failing to obtain exclusive lock";
|
|
1435
|
+
logger3.error(message);
|
|
1436
|
+
throw new Error(message);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
lockedRangeTree.insert(op);
|
|
1442
|
+
return true;
|
|
1443
|
+
} else {
|
|
1444
|
+
const intersectingLocksForThisProcess = overlappingLocks.filter((lock) => lock.pid === op.pid).filter((lock) => lock.start >= op.start && lock.end <= op.end);
|
|
1445
|
+
for (const lock of intersectingLocksForThisProcess) {
|
|
1446
|
+
const success = tryUnlockFileExSync(
|
|
1447
|
+
lock.fd,
|
|
1448
|
+
lock.start,
|
|
1449
|
+
lock.end
|
|
1450
|
+
);
|
|
1451
|
+
if (!success) {
|
|
1452
|
+
logger3.warn(
|
|
1453
|
+
`lockFileByteRange: unlock failed for pid=${op.pid} fd=${lock.fd} range=[${lock.start},${lock.end}]`
|
|
1454
|
+
);
|
|
1455
|
+
return false;
|
|
1456
|
+
}
|
|
1457
|
+
lockedRangeTree.remove(lock);
|
|
1458
|
+
}
|
|
1459
|
+
return true;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
findFirstConflictingByteRangeLock(path2, op) {
|
|
1463
|
+
if (op.type === "unlocked") {
|
|
1464
|
+
return void 0;
|
|
1465
|
+
}
|
|
1466
|
+
const obtainedLock = !!this.lockFileByteRange(path2, op, false);
|
|
1467
|
+
this.lockFileByteRange(path2, { ...op, type: "unlocked" }, false);
|
|
1468
|
+
if (obtainedLock) {
|
|
1469
|
+
return void 0;
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
type: "exclusive",
|
|
1473
|
+
start: 0n,
|
|
1474
|
+
end: 0xffffffffffffffffn,
|
|
1475
|
+
pid: -1
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
releaseLocksForProcess(targetPid) {
|
|
1479
|
+
const fdMap = this.wholeFileLockMap.get(targetPid);
|
|
1480
|
+
if (fdMap) {
|
|
1481
|
+
for (const storedLock of fdMap.values()) {
|
|
1482
|
+
try {
|
|
1483
|
+
this.lockWholeFile(storedLock.path, {
|
|
1484
|
+
...storedLock,
|
|
1485
|
+
type: "unlock"
|
|
1486
|
+
});
|
|
1487
|
+
} catch (e) {
|
|
1488
|
+
logger3.error(
|
|
1489
|
+
`releaseLocksForProcess: failed to unlock whole-file lock for pid=${targetPid} fd=${storedLock.fd}`,
|
|
1490
|
+
e
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
this.wholeFileLockMap.delete(targetPid);
|
|
1495
|
+
}
|
|
1496
|
+
for (const [path2, lockedRangeTree] of this.rangeLockedFds.entries()) {
|
|
1497
|
+
const rangesLockedByTargetPid = lockedRangeTree.findLocksForProcess(targetPid);
|
|
1498
|
+
for (const op of rangesLockedByTargetPid) {
|
|
1499
|
+
try {
|
|
1500
|
+
this.lockFileByteRange(
|
|
1501
|
+
path2,
|
|
1502
|
+
{ ...op, type: "unlocked" },
|
|
1503
|
+
false
|
|
1504
|
+
);
|
|
1505
|
+
} catch (e) {
|
|
1506
|
+
logger3.error(
|
|
1507
|
+
`releaseLocksForProcess: failed to unlock byte range for pid=${targetPid} fd=${op.fd} path=${path2}`,
|
|
1508
|
+
e
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
lockedRangeTree.remove(op);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
releaseLocksOnFdClose(targetPid, targetFd, targetPath) {
|
|
1516
|
+
const storedLock = this.wholeFileLockMap.get(targetPid)?.get(targetFd);
|
|
1517
|
+
if (storedLock) {
|
|
1518
|
+
this.lockWholeFile(storedLock.path, {
|
|
1519
|
+
...storedLock,
|
|
1520
|
+
type: "unlock"
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
this.wholeFileLockMap.get(targetPid)?.delete(targetFd);
|
|
1524
|
+
const lockedRangeTree = this.rangeLockedFds.get(targetPath);
|
|
1525
|
+
for (const op of lockedRangeTree?.findLocksForProcess(targetPid) ?? []) {
|
|
1526
|
+
this.lockFileByteRange(
|
|
1527
|
+
targetPath,
|
|
1528
|
+
{
|
|
1529
|
+
...op,
|
|
1530
|
+
type: "unlocked",
|
|
1531
|
+
// Use a dummy FD because we're releasing locks for
|
|
1532
|
+
// all FDs on this file (POSIX semantics), not just
|
|
1533
|
+
// the one being closed.
|
|
1534
|
+
fd: -1
|
|
1535
|
+
},
|
|
1536
|
+
false
|
|
1537
|
+
);
|
|
1538
|
+
lockedRangeTree.remove(op);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
|
|
396
1543
|
// packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts
|
|
397
1544
|
import { LatestSupportedPHPVersion as LatestSupportedPHPVersion3, FSHelpers } from "@php-wasm/universal";
|
|
398
1545
|
import fs from "fs";
|
|
@@ -709,6 +1856,7 @@ async function withMemcached(version = LatestSupportedPHPVersion9, options) {
|
|
|
709
1856
|
|
|
710
1857
|
// packages/php-wasm/node/src/lib/load-runtime.ts
|
|
711
1858
|
import { dirname, joinPaths, toPosixPath } from "@php-wasm/util";
|
|
1859
|
+
import { platform } from "os";
|
|
712
1860
|
async function loadNodeRuntime(phpVersion, options = {}) {
|
|
713
1861
|
let emscriptenOptions = {
|
|
714
1862
|
/**
|
|
@@ -719,6 +1867,14 @@ async function loadNodeRuntime(phpVersion, options = {}) {
|
|
|
719
1867
|
quit: function(code, error) {
|
|
720
1868
|
throw error;
|
|
721
1869
|
},
|
|
1870
|
+
bindUserSpace: (userSpaceContext) => {
|
|
1871
|
+
const nativeFileLockManager = platform() === "win32" ? new FileLockManagerForWindows() : new FileLockManagerForPosix();
|
|
1872
|
+
const fileLockManager = options.fileLockManager ? new FileLockManagerComposite({
|
|
1873
|
+
nativeLockManager: nativeFileLockManager,
|
|
1874
|
+
wasmLockManager: options.fileLockManager
|
|
1875
|
+
}) : nativeFileLockManager;
|
|
1876
|
+
return bindUserSpace({ fileLockManager }, userSpaceContext);
|
|
1877
|
+
},
|
|
722
1878
|
...options.emscriptenOptions || {},
|
|
723
1879
|
onRuntimeInitialized: (phpRuntime) => {
|
|
724
1880
|
if (options?.followSymlinks === true) {
|
|
@@ -821,9 +1977,9 @@ function createNodeFsMountHandler(localPath) {
|
|
|
821
1977
|
}
|
|
822
1978
|
removeVfsNode = true;
|
|
823
1979
|
}
|
|
824
|
-
let
|
|
1980
|
+
let lookup3;
|
|
825
1981
|
try {
|
|
826
|
-
|
|
1982
|
+
lookup3 = FS.lookupPath(vfsMountPoint);
|
|
827
1983
|
} catch (e) {
|
|
828
1984
|
const error = e;
|
|
829
1985
|
if (error.errno === 44) {
|
|
@@ -837,7 +1993,7 @@ function createNodeFsMountHandler(localPath) {
|
|
|
837
1993
|
return () => {
|
|
838
1994
|
FS.unmount(vfsMountPoint);
|
|
839
1995
|
if (removeVfsNode) {
|
|
840
|
-
if (FS.isDir(
|
|
1996
|
+
if (FS.isDir(lookup3.node.mode)) {
|
|
841
1997
|
if (isParentOf(vfsMountPoint, FS.cwd())) {
|
|
842
1998
|
throw new Error(
|
|
843
1999
|
`Cannot remove the VFS directory "${vfsMountPoint}" on umount cleanup \u2013 it is a parent of the CWD "${FS.cwd()}". Change CWD before unmounting or explicitly disable post-unmount node cleanup with createNodeFsMountHandler(path, {cleanupNodesOnUnmount: false}).`
|
|
@@ -891,641 +2047,10 @@ function statPathFollowSymlinks(path2) {
|
|
|
891
2047
|
}
|
|
892
2048
|
return stat;
|
|
893
2049
|
}
|
|
894
|
-
|
|
895
|
-
// packages/php-wasm/node/src/lib/file-lock-manager-for-node.ts
|
|
896
|
-
import { logger as logger2 } from "@php-wasm/logger";
|
|
897
|
-
import { openSync, closeSync } from "fs";
|
|
898
|
-
var MAX_64BIT_OFFSET = BigInt(2n ** 64n - 1n);
|
|
899
|
-
var FileLockManagerForNode = class {
|
|
900
|
-
/**
|
|
901
|
-
* Create a new FileLockManagerForNode instance.
|
|
902
|
-
*
|
|
903
|
-
* @param nativeFlockSync A synchronous flock() function to lock files via the host OS.
|
|
904
|
-
*/
|
|
905
|
-
constructor(nativeFlockSync = function flockSyncNoOp() {
|
|
906
|
-
}) {
|
|
907
|
-
this.nativeFlockSync = nativeFlockSync;
|
|
908
|
-
this.locks = /* @__PURE__ */ new Map();
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Lock the whole file.
|
|
912
|
-
*
|
|
913
|
-
* @param path The path to the file to lock. This should be the path
|
|
914
|
-
* of the file in the native filesystem.
|
|
915
|
-
* @param op The whole file lock operation to perform.
|
|
916
|
-
* @returns True if the lock was granted, false otherwise.
|
|
917
|
-
*/
|
|
918
|
-
lockWholeFile(path2, op) {
|
|
919
|
-
if (this.locks.get(path2) === void 0) {
|
|
920
|
-
if (op.type === "unlock") {
|
|
921
|
-
return true;
|
|
922
|
-
}
|
|
923
|
-
const maybeLock = FileLock.maybeCreate(
|
|
924
|
-
path2,
|
|
925
|
-
op.type,
|
|
926
|
-
this.nativeFlockSync
|
|
927
|
-
);
|
|
928
|
-
if (maybeLock === void 0) {
|
|
929
|
-
return false;
|
|
930
|
-
}
|
|
931
|
-
this.locks.set(path2, maybeLock);
|
|
932
|
-
}
|
|
933
|
-
const lock = this.locks.get(path2);
|
|
934
|
-
const result = lock.lockWholeFile(op);
|
|
935
|
-
this.forgetPathIfUnlocked(path2);
|
|
936
|
-
return result;
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Lock a byte range.
|
|
940
|
-
*
|
|
941
|
-
* @param path The path to the file to lock. This should be the path
|
|
942
|
-
* of the file in the native filesystem.
|
|
943
|
-
* @param requestedLock The byte range lock to perform.
|
|
944
|
-
* @returns True if the lock was granted, false otherwise.
|
|
945
|
-
*/
|
|
946
|
-
lockFileByteRange(path2, requestedLock) {
|
|
947
|
-
if (!this.locks.has(path2)) {
|
|
948
|
-
if (requestedLock.type === "unlocked") {
|
|
949
|
-
return true;
|
|
950
|
-
}
|
|
951
|
-
const maybeLock = FileLock.maybeCreate(
|
|
952
|
-
path2,
|
|
953
|
-
requestedLock.type,
|
|
954
|
-
this.nativeFlockSync
|
|
955
|
-
);
|
|
956
|
-
if (maybeLock === void 0) {
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
this.locks.set(path2, maybeLock);
|
|
960
|
-
}
|
|
961
|
-
const lock = this.locks.get(path2);
|
|
962
|
-
return lock.lockFileByteRange(requestedLock);
|
|
963
|
-
}
|
|
964
|
-
/**
|
|
965
|
-
* Find the first conflicting byte range lock.
|
|
966
|
-
*
|
|
967
|
-
* @param path The path to the file to find the conflicting lock for.
|
|
968
|
-
* @param desiredLock The desired byte range lock.
|
|
969
|
-
* @returns The first conflicting byte range lock, or undefined if no conflicting lock exists.
|
|
970
|
-
*/
|
|
971
|
-
findFirstConflictingByteRangeLock(path2, desiredLock) {
|
|
972
|
-
const lock = this.locks.get(path2);
|
|
973
|
-
if (lock === void 0) {
|
|
974
|
-
return void 0;
|
|
975
|
-
}
|
|
976
|
-
return lock.findFirstConflictingByteRangeLock(desiredLock);
|
|
977
|
-
}
|
|
978
|
-
/**
|
|
979
|
-
* Release all locks for the given process.
|
|
980
|
-
*
|
|
981
|
-
* @param pid The process ID to release locks for.
|
|
982
|
-
*/
|
|
983
|
-
releaseLocksForProcess(pid) {
|
|
984
|
-
for (const [path2, lock] of this.locks.entries()) {
|
|
985
|
-
lock.releaseLocksForProcess(pid);
|
|
986
|
-
this.forgetPathIfUnlocked(path2);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Release all locks for the given process and file descriptor.
|
|
991
|
-
*
|
|
992
|
-
* @param pid The process ID to release locks for.
|
|
993
|
-
* @param fd The file descriptor to release locks for.
|
|
994
|
-
* @param path The path to the file to release locks for.
|
|
995
|
-
*/
|
|
996
|
-
releaseLocksForProcessFd(pid, fd, nativePath) {
|
|
997
|
-
const lock = this.locks.get(nativePath);
|
|
998
|
-
if (!lock) {
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
lock.releaseLocksForProcessFd(pid, fd);
|
|
1002
|
-
this.forgetPathIfUnlocked(nativePath);
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Forget the path if it is unlocked.
|
|
1006
|
-
*
|
|
1007
|
-
* @param path The path to the file to forget.
|
|
1008
|
-
*/
|
|
1009
|
-
forgetPathIfUnlocked(path2) {
|
|
1010
|
-
const lock = this.locks.get(path2);
|
|
1011
|
-
if (!lock) {
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
if (lock.isUnlocked()) {
|
|
1015
|
-
lock.dispose();
|
|
1016
|
-
this.locks.delete(path2);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
};
|
|
1020
|
-
var FileLock = class _FileLock {
|
|
1021
|
-
/**
|
|
1022
|
-
* Create a new FileLock instance for the given file and mode.
|
|
1023
|
-
* Fail if the underlying native file lock cannot be acquired.
|
|
1024
|
-
*
|
|
1025
|
-
* @param path The path to the file to lock
|
|
1026
|
-
* @param mode The type of lock to acquire
|
|
1027
|
-
* @returns A FileLock instance if the lock was acquired, undefined otherwise
|
|
1028
|
-
*/
|
|
1029
|
-
static maybeCreate(path2, mode, nativeFlockSync) {
|
|
1030
|
-
let fd;
|
|
1031
|
-
try {
|
|
1032
|
-
fd = openSync(path2, "a+");
|
|
1033
|
-
const flockFlags = mode === "exclusive" ? "exnb" : "shnb";
|
|
1034
|
-
nativeFlockSync(fd, flockFlags);
|
|
1035
|
-
const nativeLock = { fd, mode, nativeFlockSync };
|
|
1036
|
-
return new _FileLock(nativeLock);
|
|
1037
|
-
} catch {
|
|
1038
|
-
if (fd !== void 0) {
|
|
1039
|
-
try {
|
|
1040
|
-
closeSync(fd);
|
|
1041
|
-
} catch (error) {
|
|
1042
|
-
logger2.error(
|
|
1043
|
-
"Error closing locking file descriptor",
|
|
1044
|
-
error
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
return void 0;
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
constructor(nativeLock) {
|
|
1052
|
-
this.nativeLock = nativeLock;
|
|
1053
|
-
this.rangeLocks = new FileLockIntervalTree();
|
|
1054
|
-
this.wholeFileLock = { type: "unlocked" };
|
|
1055
|
-
}
|
|
1056
|
-
/**
|
|
1057
|
-
* Close the file descriptor and release the native lock.
|
|
1058
|
-
*
|
|
1059
|
-
* @TODO Replace this with a Symbol.dispose property once supported by all JS runtimes.
|
|
1060
|
-
*/
|
|
1061
|
-
dispose() {
|
|
1062
|
-
try {
|
|
1063
|
-
closeSync(this.nativeLock.fd);
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
logger2.error("Error closing locking file descriptor", error);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* Lock the whole file.
|
|
1070
|
-
*
|
|
1071
|
-
* This method corresponds to the flock() function.
|
|
1072
|
-
*
|
|
1073
|
-
* @param op The whole file lock operation to perform.
|
|
1074
|
-
* @returns True if the lock was granted, false otherwise.
|
|
1075
|
-
*/
|
|
1076
|
-
lockWholeFile(op) {
|
|
1077
|
-
if (op.type === "unlock") {
|
|
1078
|
-
const originalType = this.wholeFileLock.type;
|
|
1079
|
-
if (originalType === "unlocked") {
|
|
1080
|
-
} else if (this.wholeFileLock.type === "exclusive" && this.wholeFileLock.pid === op.pid && this.wholeFileLock.fd === op.fd) {
|
|
1081
|
-
this.wholeFileLock = { type: "unlocked" };
|
|
1082
|
-
} else if (this.wholeFileLock.type === "shared" && this.wholeFileLock.pidFds.has(op.pid) && this.wholeFileLock.pidFds.get(op.pid).has(op.fd)) {
|
|
1083
|
-
this.wholeFileLock.pidFds.get(op.pid).delete(op.fd);
|
|
1084
|
-
if (this.wholeFileLock.pidFds.get(op.pid).size === 0) {
|
|
1085
|
-
this.wholeFileLock.pidFds.delete(op.pid);
|
|
1086
|
-
}
|
|
1087
|
-
if (this.wholeFileLock.pidFds.size === 0) {
|
|
1088
|
-
this.wholeFileLock = { type: "unlocked" };
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
if (!this.ensureCompatibleNativeLock()) {
|
|
1092
|
-
logger2.error(
|
|
1093
|
-
"Unable to update native lock after removing a whole file lock."
|
|
1094
|
-
);
|
|
1095
|
-
}
|
|
1096
|
-
return true;
|
|
1097
|
-
}
|
|
1098
|
-
if (this.isThereAConflictWithRequestedWholeFileLock(op)) {
|
|
1099
|
-
return false;
|
|
1100
|
-
}
|
|
1101
|
-
if (!this.ensureCompatibleNativeLock({
|
|
1102
|
-
overrideWholeFileLockType: op.type
|
|
1103
|
-
})) {
|
|
1104
|
-
return false;
|
|
1105
|
-
}
|
|
1106
|
-
if (op.type === "exclusive") {
|
|
1107
|
-
this.wholeFileLock = {
|
|
1108
|
-
type: "exclusive",
|
|
1109
|
-
pid: op.pid,
|
|
1110
|
-
fd: op.fd
|
|
1111
|
-
};
|
|
1112
|
-
return true;
|
|
1113
|
-
}
|
|
1114
|
-
if (op.type === "shared") {
|
|
1115
|
-
if (this.wholeFileLock.type !== "shared") {
|
|
1116
|
-
this.wholeFileLock = {
|
|
1117
|
-
type: "shared",
|
|
1118
|
-
pidFds: /* @__PURE__ */ new Map()
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
const sharedLock = this.wholeFileLock;
|
|
1122
|
-
if (!sharedLock.pidFds.has(op.pid)) {
|
|
1123
|
-
sharedLock.pidFds.set(op.pid, /* @__PURE__ */ new Set());
|
|
1124
|
-
}
|
|
1125
|
-
sharedLock.pidFds.get(op.pid).add(op.fd);
|
|
1126
|
-
return true;
|
|
1127
|
-
}
|
|
1128
|
-
throw new Error(`Unexpected wholeFileLock() op: '${op.type}'`);
|
|
1129
|
-
}
|
|
1130
|
-
/**
|
|
1131
|
-
* Lock a byte range.
|
|
1132
|
-
*
|
|
1133
|
-
* This method corresponds to the fcntl() F_SETLK command.
|
|
1134
|
-
*
|
|
1135
|
-
* @param requestedLock The byte range lock to perform.
|
|
1136
|
-
* @returns True if the lock was granted, false otherwise.
|
|
1137
|
-
*/
|
|
1138
|
-
lockFileByteRange(requestedLock) {
|
|
1139
|
-
if (requestedLock.start === requestedLock.end) {
|
|
1140
|
-
requestedLock = {
|
|
1141
|
-
...requestedLock,
|
|
1142
|
-
end: MAX_64BIT_OFFSET
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
if (requestedLock.type === "unlocked") {
|
|
1146
|
-
const overlappingLocksBySameProcess = this.rangeLocks.findOverlapping(requestedLock).filter((lock) => lock.pid === requestedLock.pid);
|
|
1147
|
-
for (const overlappingLock of overlappingLocksBySameProcess) {
|
|
1148
|
-
this.rangeLocks.remove(overlappingLock);
|
|
1149
|
-
if (overlappingLock.start < requestedLock.start) {
|
|
1150
|
-
this.rangeLocks.insert({
|
|
1151
|
-
...overlappingLock,
|
|
1152
|
-
end: requestedLock.start
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
if (overlappingLock.end > requestedLock.end) {
|
|
1156
|
-
this.rangeLocks.insert({
|
|
1157
|
-
...overlappingLock,
|
|
1158
|
-
start: requestedLock.end
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
if (!this.ensureCompatibleNativeLock()) {
|
|
1163
|
-
logger2.error(
|
|
1164
|
-
"Unable to update native lock after removing a byte range lock."
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
return true;
|
|
1168
|
-
}
|
|
1169
|
-
if (this.isThereAConflictWithRequestedRangeLock(requestedLock)) {
|
|
1170
|
-
return false;
|
|
1171
|
-
}
|
|
1172
|
-
if (!this.ensureCompatibleNativeLock({
|
|
1173
|
-
overrideRangeLockType: requestedLock.type
|
|
1174
|
-
})) {
|
|
1175
|
-
return false;
|
|
1176
|
-
}
|
|
1177
|
-
const overlappingLocksFromSameProcess = this.rangeLocks.findOverlapping(requestedLock).filter((lock) => lock.pid === requestedLock.pid);
|
|
1178
|
-
let minStart = requestedLock.start;
|
|
1179
|
-
let maxEnd = requestedLock.end;
|
|
1180
|
-
for (const overlappingLock of overlappingLocksFromSameProcess) {
|
|
1181
|
-
this.rangeLocks.remove(overlappingLock);
|
|
1182
|
-
if (overlappingLock.start < minStart) {
|
|
1183
|
-
minStart = overlappingLock.start;
|
|
1184
|
-
}
|
|
1185
|
-
if (overlappingLock.end > maxEnd) {
|
|
1186
|
-
maxEnd = overlappingLock.end;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
const mergedLock = {
|
|
1190
|
-
...requestedLock,
|
|
1191
|
-
start: minStart,
|
|
1192
|
-
end: maxEnd
|
|
1193
|
-
};
|
|
1194
|
-
this.rangeLocks.insert(mergedLock);
|
|
1195
|
-
return true;
|
|
1196
|
-
}
|
|
1197
|
-
/**
|
|
1198
|
-
* Find the first conflicting byte range lock.
|
|
1199
|
-
*
|
|
1200
|
-
* This method corresponds to the fcntl() F_GETLK command.
|
|
1201
|
-
*
|
|
1202
|
-
* @param desiredLock The desired byte range lock.
|
|
1203
|
-
* @returns The first conflicting byte range lock, or undefined if no conflicting lock exists.
|
|
1204
|
-
*/
|
|
1205
|
-
findFirstConflictingByteRangeLock(desiredLock) {
|
|
1206
|
-
const overlappingLocks = this.rangeLocks.findOverlapping(desiredLock);
|
|
1207
|
-
const firstConflictingRangeLock = overlappingLocks.find(
|
|
1208
|
-
(lock) => lock.pid !== desiredLock.pid && (desiredLock.type === "exclusive" || lock.type === "exclusive")
|
|
1209
|
-
);
|
|
1210
|
-
if (firstConflictingRangeLock) {
|
|
1211
|
-
return firstConflictingRangeLock;
|
|
1212
|
-
}
|
|
1213
|
-
if (this.wholeFileLock.type === "unlocked") {
|
|
1214
|
-
return void 0;
|
|
1215
|
-
}
|
|
1216
|
-
const wfl = this.wholeFileLock;
|
|
1217
|
-
if (wfl.type === "exclusive" || desiredLock.type === "exclusive") {
|
|
1218
|
-
return {
|
|
1219
|
-
type: this.wholeFileLock.type,
|
|
1220
|
-
start: 0n,
|
|
1221
|
-
end: 0n,
|
|
1222
|
-
pid: -1
|
|
1223
|
-
};
|
|
1224
|
-
}
|
|
1225
|
-
return void 0;
|
|
1226
|
-
}
|
|
1227
|
-
/**
|
|
1228
|
-
* Release all locks for the given process.
|
|
1229
|
-
*
|
|
1230
|
-
* @param pid The process ID to release locks for.
|
|
1231
|
-
*/
|
|
1232
|
-
releaseLocksForProcess(pid) {
|
|
1233
|
-
for (const rangeLock of this.rangeLocks.findLocksForProcess(pid)) {
|
|
1234
|
-
this.lockFileByteRange({
|
|
1235
|
-
...rangeLock,
|
|
1236
|
-
type: "unlocked"
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1239
|
-
if (this.wholeFileLock.type === "exclusive" && this.wholeFileLock.pid === pid) {
|
|
1240
|
-
this.lockWholeFile({
|
|
1241
|
-
pid,
|
|
1242
|
-
fd: this.wholeFileLock.fd,
|
|
1243
|
-
type: "unlock"
|
|
1244
|
-
});
|
|
1245
|
-
} else if (this.wholeFileLock.type === "shared" && this.wholeFileLock.pidFds.has(pid)) {
|
|
1246
|
-
for (const fd of this.wholeFileLock.pidFds.get(pid)) {
|
|
1247
|
-
this.lockWholeFile({
|
|
1248
|
-
pid,
|
|
1249
|
-
fd,
|
|
1250
|
-
type: "unlock"
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
/**
|
|
1256
|
-
* Release all locks for the given process and file descriptor.
|
|
1257
|
-
*
|
|
1258
|
-
* @param pid The process ID to release locks for.
|
|
1259
|
-
* @param fd The file descriptor to release locks for.
|
|
1260
|
-
*/
|
|
1261
|
-
releaseLocksForProcessFd(pid, fd) {
|
|
1262
|
-
for (const rangeLock of this.rangeLocks.findLocksForProcess(pid)) {
|
|
1263
|
-
this.lockFileByteRange({
|
|
1264
|
-
...rangeLock,
|
|
1265
|
-
type: "unlocked"
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
this.lockWholeFile({
|
|
1269
|
-
pid,
|
|
1270
|
-
fd,
|
|
1271
|
-
type: "unlock"
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
/**
|
|
1275
|
-
* Check if the file lock is unlocked.
|
|
1276
|
-
*
|
|
1277
|
-
* @returns True if the file lock is unlocked, false otherwise.
|
|
1278
|
-
*/
|
|
1279
|
-
isUnlocked() {
|
|
1280
|
-
return this.wholeFileLock.type === "unlocked" && this.rangeLocks.isEmpty();
|
|
1281
|
-
}
|
|
1282
|
-
/**
|
|
1283
|
-
* Ensure that the native lock is compatible with the php-wasm lock,
|
|
1284
|
-
* upgrading or downgrading as needed.
|
|
1285
|
-
*
|
|
1286
|
-
* @param overrideWholeFileLockType If provided, use this type for the whole file lock.
|
|
1287
|
-
* @param overrideRangeLockType If provided, use this type for the range lock.
|
|
1288
|
-
* @returns True if the native lock was upgraded or downgraded, false otherwise.
|
|
1289
|
-
*/
|
|
1290
|
-
ensureCompatibleNativeLock({
|
|
1291
|
-
overrideWholeFileLockType,
|
|
1292
|
-
overrideRangeLockType
|
|
1293
|
-
} = {}) {
|
|
1294
|
-
const wholeFileLockType = overrideWholeFileLockType ?? this.wholeFileLock.type;
|
|
1295
|
-
const rangeLockType = overrideRangeLockType ?? this.rangeLocks.findStrictestExistingLockType();
|
|
1296
|
-
let requiredNativeLockType;
|
|
1297
|
-
if (wholeFileLockType === "exclusive" || rangeLockType === "exclusive") {
|
|
1298
|
-
requiredNativeLockType = "exclusive";
|
|
1299
|
-
} else if (wholeFileLockType === "shared" || rangeLockType === "shared") {
|
|
1300
|
-
requiredNativeLockType = "shared";
|
|
1301
|
-
} else {
|
|
1302
|
-
requiredNativeLockType = "unlock";
|
|
1303
|
-
}
|
|
1304
|
-
if (this.nativeLock.mode === requiredNativeLockType) {
|
|
1305
|
-
return true;
|
|
1306
|
-
}
|
|
1307
|
-
const flockFlags = requiredNativeLockType === "exclusive" && "exnb" || requiredNativeLockType === "shared" && "shnb" || "un";
|
|
1308
|
-
try {
|
|
1309
|
-
this.nativeLock.nativeFlockSync(this.nativeLock.fd, flockFlags);
|
|
1310
|
-
this.nativeLock.mode = requiredNativeLockType;
|
|
1311
|
-
return true;
|
|
1312
|
-
} catch {
|
|
1313
|
-
return false;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
/**
|
|
1317
|
-
* Check if a lock exists that conflicts with the requested range lock.
|
|
1318
|
-
*
|
|
1319
|
-
* @param requestedLock The desired byte range lock.
|
|
1320
|
-
* @returns True if a conflicting lock exists, false otherwise.
|
|
1321
|
-
*/
|
|
1322
|
-
isThereAConflictWithRequestedRangeLock(requestedLock) {
|
|
1323
|
-
return this.findFirstConflictingByteRangeLock(requestedLock) !== void 0;
|
|
1324
|
-
}
|
|
1325
|
-
/**
|
|
1326
|
-
* Check if a lock exists that conflicts with the requested whole-file lock.
|
|
1327
|
-
*
|
|
1328
|
-
* @param requestedLock The desired whole-file lock.
|
|
1329
|
-
* @returns True if a conflicting lock exists, false otherwise.
|
|
1330
|
-
*/
|
|
1331
|
-
isThereAConflictWithRequestedWholeFileLock(requestedLock) {
|
|
1332
|
-
if (requestedLock.type === "exclusive") {
|
|
1333
|
-
if (this.wholeFileLock.type === "exclusive" && (this.wholeFileLock.fd !== requestedLock.fd || this.wholeFileLock.pid !== requestedLock.pid)) {
|
|
1334
|
-
return true;
|
|
1335
|
-
}
|
|
1336
|
-
if (this.wholeFileLock.type === "shared" && Array.from(this.wholeFileLock.pidFds).some(
|
|
1337
|
-
([pid]) => pid !== requestedLock.pid
|
|
1338
|
-
)) {
|
|
1339
|
-
return true;
|
|
1340
|
-
}
|
|
1341
|
-
const overlappingLocks = this.rangeLocks.findOverlapping({
|
|
1342
|
-
type: "unlocked",
|
|
1343
|
-
start: 0n,
|
|
1344
|
-
end: MAX_64BIT_OFFSET,
|
|
1345
|
-
pid: -1
|
|
1346
|
-
});
|
|
1347
|
-
if (overlappingLocks.length > 0) {
|
|
1348
|
-
return true;
|
|
1349
|
-
}
|
|
1350
|
-
return false;
|
|
1351
|
-
}
|
|
1352
|
-
if (requestedLock.type === "shared") {
|
|
1353
|
-
if (this.wholeFileLock.type === "exclusive" && this.wholeFileLock.pid !== requestedLock.pid) {
|
|
1354
|
-
return true;
|
|
1355
|
-
}
|
|
1356
|
-
const overlappingLocks = this.rangeLocks.findOverlapping({
|
|
1357
|
-
type: "unlocked",
|
|
1358
|
-
start: 0n,
|
|
1359
|
-
end: MAX_64BIT_OFFSET,
|
|
1360
|
-
pid: -1
|
|
1361
|
-
});
|
|
1362
|
-
const exclusiveRangeLocks = overlappingLocks.filter(
|
|
1363
|
-
(lock) => lock.type === "exclusive"
|
|
1364
|
-
);
|
|
1365
|
-
if (exclusiveRangeLocks.length > 0) {
|
|
1366
|
-
return true;
|
|
1367
|
-
}
|
|
1368
|
-
return false;
|
|
1369
|
-
}
|
|
1370
|
-
return false;
|
|
1371
|
-
}
|
|
1372
|
-
};
|
|
1373
|
-
var IntervalNode = class {
|
|
1374
|
-
constructor(range) {
|
|
1375
|
-
this.left = null;
|
|
1376
|
-
this.right = null;
|
|
1377
|
-
this.range = range;
|
|
1378
|
-
this.max = range.end;
|
|
1379
|
-
}
|
|
1380
|
-
};
|
|
1381
|
-
var FileLockIntervalTree = class {
|
|
1382
|
-
constructor() {
|
|
1383
|
-
this.root = null;
|
|
1384
|
-
}
|
|
1385
|
-
isEmpty() {
|
|
1386
|
-
return this.root === null;
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Insert a new locked range into the tree
|
|
1390
|
-
*/
|
|
1391
|
-
insert(range) {
|
|
1392
|
-
this.root = this.insertNode(this.root, range);
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
|
-
* Find all ranges that overlap with the given range
|
|
1396
|
-
*/
|
|
1397
|
-
findOverlapping(range) {
|
|
1398
|
-
const result = [];
|
|
1399
|
-
this.findOverlappingRanges(this.root, range, result);
|
|
1400
|
-
return result;
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* Remove a lock range from the tree
|
|
1404
|
-
*/
|
|
1405
|
-
remove(range) {
|
|
1406
|
-
this.root = this.removeNode(this.root, range);
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* Find all ranges locked by the given process.
|
|
1410
|
-
*
|
|
1411
|
-
* @param pid The process ID to find locks for.
|
|
1412
|
-
* @returns All locked ranges for the given process.
|
|
1413
|
-
*/
|
|
1414
|
-
findLocksForProcess(pid) {
|
|
1415
|
-
const result = [];
|
|
1416
|
-
this.findLocksForProcessInNode(this.root, pid, result);
|
|
1417
|
-
return result;
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Find the strictest existing lock type in the range lock tree.
|
|
1421
|
-
*
|
|
1422
|
-
* @returns The strictest existing lock type, or 'unlocked' if no locks exist.
|
|
1423
|
-
*/
|
|
1424
|
-
findStrictestExistingLockType() {
|
|
1425
|
-
let maxType = "unlocked";
|
|
1426
|
-
const traverse = (node) => {
|
|
1427
|
-
if (!node) {
|
|
1428
|
-
return;
|
|
1429
|
-
}
|
|
1430
|
-
if (node.range.type === "exclusive") {
|
|
1431
|
-
maxType = "exclusive";
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
if (node.range.type === "shared") {
|
|
1435
|
-
maxType = "shared";
|
|
1436
|
-
}
|
|
1437
|
-
traverse(node.left);
|
|
1438
|
-
traverse(node.right);
|
|
1439
|
-
};
|
|
1440
|
-
traverse(this.root);
|
|
1441
|
-
return maxType;
|
|
1442
|
-
}
|
|
1443
|
-
insertNode(node, range) {
|
|
1444
|
-
if (!node) {
|
|
1445
|
-
return new IntervalNode(range);
|
|
1446
|
-
}
|
|
1447
|
-
if (range.start < node.range.start) {
|
|
1448
|
-
node.left = this.insertNode(node.left, range);
|
|
1449
|
-
} else {
|
|
1450
|
-
node.right = this.insertNode(node.right, range);
|
|
1451
|
-
}
|
|
1452
|
-
node.max = this.bigintMax(node.max, range.end);
|
|
1453
|
-
return node;
|
|
1454
|
-
}
|
|
1455
|
-
bigintMax(...args) {
|
|
1456
|
-
return args.reduce((max, current) => {
|
|
1457
|
-
return current > max ? current : max;
|
|
1458
|
-
}, args[0]);
|
|
1459
|
-
}
|
|
1460
|
-
findOverlappingRanges(node, range, result) {
|
|
1461
|
-
if (!node) {
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
if (this.doRangesOverlap(node.range, range)) {
|
|
1465
|
-
result.push(node.range);
|
|
1466
|
-
}
|
|
1467
|
-
if (node.left && node.left.max >= range.start) {
|
|
1468
|
-
this.findOverlappingRanges(node.left, range, result);
|
|
1469
|
-
}
|
|
1470
|
-
if (node.right && node.range.start <= range.end) {
|
|
1471
|
-
this.findOverlappingRanges(node.right, range, result);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
doRangesOverlap(a, b) {
|
|
1475
|
-
return a.start < b.end && b.start < a.end;
|
|
1476
|
-
}
|
|
1477
|
-
removeNode(node, range) {
|
|
1478
|
-
if (!node) {
|
|
1479
|
-
return null;
|
|
1480
|
-
}
|
|
1481
|
-
if (this.areRangesEqual(node.range, range)) {
|
|
1482
|
-
if (!node.left) {
|
|
1483
|
-
return node.right;
|
|
1484
|
-
}
|
|
1485
|
-
if (!node.right) {
|
|
1486
|
-
return node.left;
|
|
1487
|
-
}
|
|
1488
|
-
const successor = this.findMin(node.right);
|
|
1489
|
-
node.range = successor.range;
|
|
1490
|
-
node.right = this.removeNode(node.right, successor.range);
|
|
1491
|
-
} else if (range.start < node.range.start) {
|
|
1492
|
-
node.left = this.removeNode(node.left, range);
|
|
1493
|
-
} else {
|
|
1494
|
-
node.right = this.removeNode(node.right, range);
|
|
1495
|
-
}
|
|
1496
|
-
node.max = node.range.end;
|
|
1497
|
-
if (node.left) {
|
|
1498
|
-
node.max = this.bigintMax(node.max, node.left.max);
|
|
1499
|
-
}
|
|
1500
|
-
if (node.right) {
|
|
1501
|
-
node.max = this.bigintMax(node.max, node.right.max);
|
|
1502
|
-
}
|
|
1503
|
-
return node;
|
|
1504
|
-
}
|
|
1505
|
-
findMin(node) {
|
|
1506
|
-
let current = node;
|
|
1507
|
-
while (current.left) {
|
|
1508
|
-
current = current.left;
|
|
1509
|
-
}
|
|
1510
|
-
return current;
|
|
1511
|
-
}
|
|
1512
|
-
areRangesEqual(a, b) {
|
|
1513
|
-
return a.start === b.start && a.end === b.end && a.pid === b.pid;
|
|
1514
|
-
}
|
|
1515
|
-
findLocksForProcessInNode(node, pid, result) {
|
|
1516
|
-
if (!node) {
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
if (node.range.pid === pid) {
|
|
1520
|
-
result.push(node.range);
|
|
1521
|
-
}
|
|
1522
|
-
this.findLocksForProcessInNode(node.left, pid, result);
|
|
1523
|
-
this.findLocksForProcessInNode(node.right, pid, result);
|
|
1524
|
-
}
|
|
1525
|
-
};
|
|
1526
2050
|
export {
|
|
1527
|
-
|
|
1528
|
-
|
|
2051
|
+
FileLockManagerForPosix,
|
|
2052
|
+
FileLockManagerForWindows,
|
|
2053
|
+
bindUserSpace,
|
|
1529
2054
|
createNodeFsMountHandler,
|
|
1530
2055
|
getPHPLoaderModule,
|
|
1531
2056
|
loadNodeRuntime,
|