@php-wasm/node 3.0.54 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -30,8 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // packages/php-wasm/node/src/index.ts
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
- FileLock: () => FileLock,
34
- FileLockManagerForNode: () => FileLockManagerForNode,
33
+ FileLockManagerForPosix: () => FileLockManagerForPosix,
34
+ FileLockManagerForWindows: () => FileLockManagerForWindows,
35
+ bindUserSpace: () => bindUserSpace,
35
36
  createNodeFsMountHandler: () => createNodeFsMountHandler,
36
37
  getPHPLoaderModule: () => getPHPLoaderModule,
37
38
  loadNodeRuntime: () => loadNodeRuntime,
@@ -423,16 +424,1152 @@ async function withNetworking(phpModuleArgs = {}) {
423
424
  }
424
425
 
425
426
  // packages/php-wasm/node/src/lib/load-runtime.ts
426
- var import_universal10 = require("@php-wasm/universal");
427
+ var import_universal12 = require("@php-wasm/universal");
428
+
429
+ // packages/php-wasm/node/src/lib/wasm-user-space.ts
430
+ var import_promises = require("dns/promises");
431
+ function bindUserSpace({ fileLockManager }, {
432
+ pid,
433
+ memory: { HEAP16, HEAP64, HEAP32 },
434
+ constants: {
435
+ F_RDLCK,
436
+ F_WRLCK,
437
+ F_UNLCK,
438
+ F_GETFL,
439
+ O_ACCMODE,
440
+ O_RDONLY,
441
+ O_WRONLY,
442
+ O_APPEND,
443
+ O_NONBLOCK,
444
+ F_SETFL,
445
+ F_GETLK,
446
+ F_SETLK,
447
+ F_SETLKW,
448
+ SEEK_SET,
449
+ SEEK_CUR,
450
+ SEEK_END,
451
+ LOCK_SH,
452
+ LOCK_EX,
453
+ LOCK_NB,
454
+ LOCK_UN
455
+ },
456
+ errnoCodes: { EBADF, EINVAL, EAGAIN, EWOULDBLOCK },
457
+ wasmImports: { builtin_fcntl64, builtin_fd_close, js_wasm_trace },
458
+ wasmExports: { wasm_get_end_offset },
459
+ syscalls: { getStreamFromFD },
460
+ FS,
461
+ PROXYFS,
462
+ NODEFS
463
+ }) {
464
+ class VarArgsAccessor {
465
+ constructor(argsAddr) {
466
+ this.argsAddr = argsAddr;
467
+ }
468
+ getNextAsPointer() {
469
+ return this.getNextAsInt();
470
+ }
471
+ getNextAsInt() {
472
+ const fourByteOffset = this.argsAddr >> 2;
473
+ const value = HEAP32[fourByteOffset];
474
+ this.argsAddr += 4;
475
+ return value;
476
+ }
477
+ }
478
+ const locking = {
479
+ /*
480
+ * This is a set of possibly locked file descriptors.
481
+ *
482
+ * When a file descriptor is closed, we need to release any associated held by this process.
483
+ * Instead of trying remember and forget file descriptors as they are locked and unlocked,
484
+ * we just track file descriptors we have locked before and try an unlock when they are closed.
485
+ */
486
+ maybeLockedFds: /* @__PURE__ */ new Set(),
487
+ lockStateToFcntl: {
488
+ shared: F_RDLCK,
489
+ exclusive: F_WRLCK,
490
+ unlocked: F_UNLCK
491
+ },
492
+ fcntlToLockState: {
493
+ [F_RDLCK]: "shared",
494
+ [F_WRLCK]: "exclusive",
495
+ [F_UNLCK]: "unlocked"
496
+ },
497
+ is_path_to_shared_fs(path2) {
498
+ const { node } = FS.lookupPath(path2, { noent_okay: true });
499
+ if (!node) {
500
+ return false;
501
+ }
502
+ if (node.mount.type !== PROXYFS) {
503
+ return !!node.isSharedFS;
504
+ }
505
+ const nodePath = PROXYFS.realPath(node);
506
+ const backingFs = node?.mount?.opts?.["fs"];
507
+ if (backingFs) {
508
+ const { node: backingNode } = backingFs.lookupPath(nodePath, {
509
+ noent_okay: true
510
+ });
511
+ return !!backingNode?.isSharedFS;
512
+ }
513
+ return false;
514
+ },
515
+ get_fd_access_mode(fd) {
516
+ return builtin_fcntl64(fd, F_GETFL) & O_ACCMODE;
517
+ },
518
+ get_vfs_path_from_fd(fd) {
519
+ try {
520
+ return [FS.readlink(`/proc/self/fd/${fd}`), 0];
521
+ } catch {
522
+ return [null, EBADF];
523
+ }
524
+ },
525
+ get_native_path_from_vfs_path(vfsPath) {
526
+ const { node } = FS.lookupPath(vfsPath, {
527
+ noent_okay: true
528
+ });
529
+ if (!node) {
530
+ throw new Error(`No node found for VFS path ${vfsPath}`);
531
+ }
532
+ if (node.mount.type === NODEFS) {
533
+ return NODEFS.realPath(node);
534
+ } else if (node.mount.type === PROXYFS) {
535
+ const { node: backingNode, path: backingPath } = node.mount.opts["fs"].lookupPath(vfsPath);
536
+ js_wasm_trace(
537
+ "backingNode for %s: %s",
538
+ vfsPath,
539
+ backingPath,
540
+ backingNode
541
+ );
542
+ return backingNode.mount.type.realPath(backingNode);
543
+ } else {
544
+ throw new Error(
545
+ `Unsupported filesystem type for path ${vfsPath}`
546
+ );
547
+ }
548
+ },
549
+ get_native_fd_from_emscripten_fd(fd) {
550
+ try {
551
+ const stream = getStreamFromFD(fd);
552
+ if (stream.nfd === void 0) {
553
+ return [null, EBADF];
554
+ }
555
+ return [stream.nfd, 0];
556
+ } catch {
557
+ return [null, EBADF];
558
+ }
559
+ },
560
+ check_lock_params(fd, l_type) {
561
+ const accessMode = locking.get_fd_access_mode(fd);
562
+ if (l_type === F_WRLCK && accessMode === O_RDONLY || l_type === F_RDLCK && accessMode === O_WRONLY) {
563
+ js_wasm_trace(
564
+ "check_lock_params(%d, %d, %d) EBADF",
565
+ fd,
566
+ l_type,
567
+ accessMode
568
+ );
569
+ return EBADF;
570
+ }
571
+ return 0;
572
+ }
573
+ };
574
+ const emscripten_flock_l_type_offset = 0;
575
+ const emscripten_flock_l_whence_offset = 2;
576
+ const emscripten_flock_l_start_offset = 8;
577
+ const emscripten_flock_l_len_offset = 16;
578
+ const emscripten_flock_l_pid_offset = 24;
579
+ function readFlockStruct(flockStructAddress) {
580
+ return {
581
+ l_type: HEAP16[
582
+ // Shift right by 1 to divide by 2^1.
583
+ flockStructAddress + emscripten_flock_l_type_offset >> 1
584
+ ],
585
+ l_whence: HEAP16[
586
+ // Shift right by 1 to divide by 2^1.
587
+ flockStructAddress + emscripten_flock_l_whence_offset >> 1
588
+ ],
589
+ l_start: HEAP64[
590
+ // Shift right by 3 to divide by 2^3.
591
+ flockStructAddress + emscripten_flock_l_start_offset >> 3
592
+ ],
593
+ l_len: HEAP64[
594
+ // Shift right by 3 to divide by 2^3.
595
+ flockStructAddress + emscripten_flock_l_len_offset >> 3
596
+ ],
597
+ l_pid: HEAP32[
598
+ // Shift right by 2 to divide by 2^2.
599
+ flockStructAddress + emscripten_flock_l_pid_offset >> 2
600
+ ]
601
+ };
602
+ }
603
+ function updateFlockStruct(flockStructAddress, fields) {
604
+ if (fields.l_type !== void 0) {
605
+ HEAP16[
606
+ // Shift right by 1 to divide by 2^1.
607
+ flockStructAddress + emscripten_flock_l_type_offset >> 1
608
+ ] = fields.l_type;
609
+ }
610
+ if (fields.l_whence !== void 0) {
611
+ HEAP16[
612
+ // Shift right by 1 to divide by 2^1.
613
+ flockStructAddress + emscripten_flock_l_whence_offset >> 1
614
+ ] = fields.l_whence;
615
+ }
616
+ if (fields.l_start !== void 0) {
617
+ HEAP64[
618
+ // Shift right by 3 to divide by 2^3.
619
+ flockStructAddress + emscripten_flock_l_start_offset >> 3
620
+ ] = fields.l_start;
621
+ }
622
+ if (fields.l_len !== void 0) {
623
+ HEAP64[
624
+ // Shift right by 3 to divide by 2^3.
625
+ flockStructAddress + emscripten_flock_l_len_offset >> 3
626
+ ] = fields.l_len;
627
+ }
628
+ if (fields.l_pid !== void 0) {
629
+ HEAP32[
630
+ // Shift right by 2 to divide by 2^2.
631
+ flockStructAddress + emscripten_flock_l_pid_offset >> 2
632
+ ] = fields.l_pid;
633
+ }
634
+ }
635
+ function getBaseAddress(fd, whence, startOffset) {
636
+ let baseAddress;
637
+ switch (whence) {
638
+ case SEEK_SET:
639
+ baseAddress = 0n;
640
+ break;
641
+ case SEEK_CUR:
642
+ try {
643
+ const stream = getStreamFromFD(fd);
644
+ baseAddress = FS.llseek(stream, 0, whence);
645
+ } catch (e) {
646
+ js_wasm_trace(
647
+ "get_base_address(%d, %d, %d) getStreamFromFD error %s",
648
+ fd,
649
+ whence,
650
+ startOffset,
651
+ e
652
+ );
653
+ return [null, EINVAL];
654
+ }
655
+ break;
656
+ case SEEK_END:
657
+ baseAddress = wasm_get_end_offset(fd);
658
+ break;
659
+ default:
660
+ return [null, EINVAL];
661
+ }
662
+ if (baseAddress == -1) {
663
+ return [null, EBADF];
664
+ }
665
+ const resolvedOffset = baseAddress + startOffset;
666
+ if (resolvedOffset < 0) {
667
+ return [null, EINVAL];
668
+ }
669
+ return [resolvedOffset, 0];
670
+ }
671
+ function fcntl64(fd, cmd, varargs) {
672
+ js_wasm_trace("fcntl64(%d, %d)", fd, cmd);
673
+ if (!fileLockManager) {
674
+ js_wasm_trace(
675
+ "fcntl64(%d, %d) file lock manager is not available. delegate to Emscripten builtin fcntl64.",
676
+ fd,
677
+ cmd
678
+ );
679
+ return builtin_fcntl64(fd, cmd, varargs);
680
+ }
681
+ switch (cmd) {
682
+ case F_GETLK: {
683
+ const reportUnlockedFileByDefault = function reportUnlockedFileByDefault2() {
684
+ updateFlockStruct(flockStructAddr, {
685
+ l_type: F_UNLCK
686
+ });
687
+ return 0;
688
+ };
689
+ js_wasm_trace("fcntl(%d, F_GETLK)", fd);
690
+ const [vfsPath, vfsPathErrno] = locking.get_vfs_path_from_fd(fd);
691
+ if (vfsPathErrno !== 0) {
692
+ js_wasm_trace(
693
+ "fcntl(%d, F_GETLK) %s get_vfs_path_from_fd errno %d",
694
+ fd,
695
+ vfsPath,
696
+ vfsPathErrno
697
+ );
698
+ return -EBADF;
699
+ }
700
+ const varArgsAccessor = new VarArgsAccessor(varargs);
701
+ const flockStructAddr = varArgsAccessor.getNextAsPointer();
702
+ if (!locking.is_path_to_shared_fs(vfsPath) || fileLockManager === void 0) {
703
+ js_wasm_trace(
704
+ "fcntl(%d, F_GETLK) locking is not implemented for non-NodeFS path '%s'",
705
+ fd,
706
+ vfsPath
707
+ );
708
+ return reportUnlockedFileByDefault();
709
+ }
710
+ const flockStruct = readFlockStruct(flockStructAddr);
711
+ if (!(flockStruct.l_type in locking.fcntlToLockState)) {
712
+ return -EINVAL;
713
+ }
714
+ const paramsCheckErrno = locking.check_lock_params(
715
+ fd,
716
+ flockStruct.l_type
717
+ );
718
+ if (paramsCheckErrno !== 0) {
719
+ js_wasm_trace(
720
+ "fcntl(%d, F_GETLK) %s check_lock_params errno %d",
721
+ fd,
722
+ vfsPath,
723
+ paramsCheckErrno
724
+ );
725
+ return -EINVAL;
726
+ }
727
+ const requestedLockType = locking.fcntlToLockState[flockStruct.l_type];
728
+ const [absoluteStartOffset, baseAddressErrno] = getBaseAddress(
729
+ fd,
730
+ flockStruct.l_whence,
731
+ flockStruct.l_start
732
+ );
733
+ if (baseAddressErrno !== 0) {
734
+ js_wasm_trace(
735
+ "fcntl(%d, F_GETLK) %s get_base_address errno %d",
736
+ fd,
737
+ vfsPath,
738
+ baseAddressErrno
739
+ );
740
+ return -EINVAL;
741
+ }
742
+ const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
743
+ if (nativeFdErrno !== 0) {
744
+ js_wasm_trace(
745
+ "fcntl(%d, F_GETLK) get_native_fd_from_emscripten_fd errno %d",
746
+ fd,
747
+ nativeFdErrno
748
+ );
749
+ return -nativeFdErrno;
750
+ }
751
+ try {
752
+ const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
753
+ const conflictingLock = fileLockManager.findFirstConflictingByteRangeLock(
754
+ nativeFilePath,
755
+ {
756
+ type: requestedLockType,
757
+ start: absoluteStartOffset,
758
+ end: absoluteStartOffset + flockStruct.l_len,
759
+ pid,
760
+ fd: nativeFd
761
+ }
762
+ );
763
+ if (conflictingLock === void 0) {
764
+ js_wasm_trace(
765
+ "fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=unlocked start=0x%x end=0x%x",
766
+ fd,
767
+ vfsPath,
768
+ absoluteStartOffset,
769
+ absoluteStartOffset + flockStruct.l_len
770
+ );
771
+ updateFlockStruct(flockStructAddr, {
772
+ l_type: F_UNLCK
773
+ });
774
+ return 0;
775
+ }
776
+ js_wasm_trace(
777
+ "fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=%s start=0x%x end=0x%x conflictingLock %d",
778
+ fd,
779
+ vfsPath,
780
+ conflictingLock.type,
781
+ conflictingLock.start,
782
+ conflictingLock.end,
783
+ conflictingLock.pid
784
+ );
785
+ const fcntlLockState = locking.lockStateToFcntl[conflictingLock.type];
786
+ updateFlockStruct(flockStructAddr, {
787
+ l_type: fcntlLockState,
788
+ l_whence: SEEK_SET,
789
+ l_start: conflictingLock.start,
790
+ l_len: BigInt(
791
+ conflictingLock.end - conflictingLock.start
792
+ ),
793
+ l_pid: conflictingLock.pid
794
+ });
795
+ return 0;
796
+ } catch (e) {
797
+ js_wasm_trace(
798
+ "fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock error %s",
799
+ fd,
800
+ vfsPath,
801
+ e
802
+ );
803
+ return -EINVAL;
804
+ }
805
+ }
806
+ case F_SETLKW:
807
+ case F_SETLK: {
808
+ js_wasm_trace("fcntl(%d, F_SETLK)", fd);
809
+ const [vfsPath, vfsPathErrno] = locking.get_vfs_path_from_fd(fd);
810
+ if (vfsPathErrno !== 0) {
811
+ js_wasm_trace(
812
+ "fcntl(%d, F_SETLK) %s get_vfs_path_from_fd errno %d",
813
+ fd,
814
+ vfsPath,
815
+ vfsPathErrno
816
+ );
817
+ return -vfsPathErrno;
818
+ }
819
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
820
+ js_wasm_trace(
821
+ "fcntl(%d, F_SETLK) locking is not implemented for non-NodeFS path %s",
822
+ fd,
823
+ vfsPath
824
+ );
825
+ return 0;
826
+ }
827
+ const varArgsAccessor = new VarArgsAccessor(varargs);
828
+ const flockStructAddr = varArgsAccessor.getNextAsPointer();
829
+ const flockStruct = readFlockStruct(flockStructAddr);
830
+ const [absoluteStartOffset, baseAddressErrno] = getBaseAddress(
831
+ fd,
832
+ flockStruct.l_whence,
833
+ flockStruct.l_start
834
+ );
835
+ if (baseAddressErrno !== 0) {
836
+ js_wasm_trace(
837
+ "fcntl(%d, F_SETLK) %s get_base_address errno %d",
838
+ fd,
839
+ vfsPath,
840
+ baseAddressErrno
841
+ );
842
+ return -EINVAL;
843
+ }
844
+ if (!(flockStruct.l_type in locking.fcntlToLockState)) {
845
+ js_wasm_trace(
846
+ "fcntl(%d, F_SETLK) %s invalid lock type %d",
847
+ fd,
848
+ vfsPath,
849
+ flockStruct.l_type
850
+ );
851
+ return -EINVAL;
852
+ }
853
+ const paramsCheckErrno = locking.check_lock_params(
854
+ fd,
855
+ flockStruct.l_type
856
+ );
857
+ if (paramsCheckErrno !== 0) {
858
+ js_wasm_trace(
859
+ "fcntl(%d, F_SETLK) %s check_lock_params errno %d",
860
+ fd,
861
+ vfsPath,
862
+ paramsCheckErrno
863
+ );
864
+ return -paramsCheckErrno;
865
+ }
866
+ const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
867
+ if (nativeFdErrno !== 0) {
868
+ js_wasm_trace(
869
+ "fcntl(%d, F_SETLK) get_native_fd_from_emscripten_fd errno %d",
870
+ fd,
871
+ nativeFdErrno
872
+ );
873
+ return -nativeFdErrno;
874
+ }
875
+ const requestedLockType = locking.fcntlToLockState[flockStruct.l_type];
876
+ const rangeLock = {
877
+ type: requestedLockType,
878
+ start: absoluteStartOffset,
879
+ end: absoluteStartOffset + flockStruct.l_len,
880
+ pid,
881
+ fd: nativeFd
882
+ };
883
+ try {
884
+ const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
885
+ js_wasm_trace(
886
+ "fcntl(%d, F_SETLK) %s calling lockFileByteRange for range lock %s",
887
+ fd,
888
+ vfsPath,
889
+ rangeLock
890
+ );
891
+ const waitForLock = cmd === F_SETLKW;
892
+ const succeeded = fileLockManager.lockFileByteRange(
893
+ nativeFilePath,
894
+ rangeLock,
895
+ waitForLock
896
+ );
897
+ if (succeeded) {
898
+ locking.maybeLockedFds.add(nativeFd);
899
+ }
900
+ js_wasm_trace(
901
+ "fcntl(%d, F_SETLK) %s lockFileByteRange returned %d for range lock %s",
902
+ fd,
903
+ vfsPath,
904
+ succeeded,
905
+ rangeLock
906
+ );
907
+ return succeeded ? 0 : -EAGAIN;
908
+ } catch (e) {
909
+ js_wasm_trace(
910
+ "fcntl(%d, F_SETLK) %s lockFileByteRange error %s for range lock %s",
911
+ fd,
912
+ vfsPath,
913
+ e,
914
+ rangeLock
915
+ );
916
+ return -EINVAL;
917
+ }
918
+ }
919
+ case F_SETFL: {
920
+ let arg = 0;
921
+ if (varargs !== void 0) {
922
+ const varArgsAccessor = new VarArgsAccessor(varargs);
923
+ arg = varArgsAccessor.getNextAsInt();
924
+ }
925
+ const stream = getStreamFromFD(fd);
926
+ const SETFL_MASK = O_APPEND | O_NONBLOCK;
927
+ stream.flags = arg & SETFL_MASK | stream.flags & ~SETFL_MASK;
928
+ return 0;
929
+ }
930
+ default:
931
+ return builtin_fcntl64(fd, cmd, varargs);
932
+ }
933
+ }
934
+ function flock(fd, op) {
935
+ js_wasm_trace("flock(%d, %d)", fd, op);
936
+ if (!fileLockManager) {
937
+ js_wasm_trace(
938
+ "flock(%d, %d) file lock manager is not available. succeed by default as Emscripten does.",
939
+ fd,
940
+ op
941
+ );
942
+ return 0;
943
+ }
944
+ const flockToLockOpType = {
945
+ [LOCK_SH]: "shared",
946
+ [LOCK_EX]: "exclusive",
947
+ [LOCK_UN]: "unlock"
948
+ };
949
+ const [vfsPath, vfsPathErrno] = locking.get_vfs_path_from_fd(fd);
950
+ if (vfsPathErrno !== 0) {
951
+ js_wasm_trace(
952
+ "flock(%d, %d) get_vfs_path_from_fd errno %d",
953
+ fd,
954
+ op,
955
+ vfsPath,
956
+ vfsPathErrno
957
+ );
958
+ return -vfsPathErrno;
959
+ }
960
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
961
+ js_wasm_trace(
962
+ "flock(%d, %d) locking is not implemented for non-NodeFS path %s",
963
+ fd,
964
+ op,
965
+ vfsPath
966
+ );
967
+ return 0;
968
+ }
969
+ const paramsCheckErrno = locking.check_lock_params(fd, op);
970
+ if (paramsCheckErrno !== 0) {
971
+ js_wasm_trace(
972
+ "flock(%d, %d) %s check_lock_params errno %d",
973
+ fd,
974
+ op,
975
+ vfsPath,
976
+ paramsCheckErrno
977
+ );
978
+ return -paramsCheckErrno;
979
+ }
980
+ const maskedOp = op & (LOCK_SH | LOCK_EX | LOCK_UN);
981
+ const waitForLock = (op & LOCK_NB) === 0;
982
+ if (maskedOp === 0) {
983
+ js_wasm_trace("flock(%d, %d) invalid flock() operation", fd, op);
984
+ return -EINVAL;
985
+ }
986
+ const lockOpType = flockToLockOpType[maskedOp];
987
+ if (lockOpType === void 0) {
988
+ js_wasm_trace("flock(%d, %d) invalid flock() operation", fd, op);
989
+ return -EINVAL;
990
+ }
991
+ const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
992
+ if (nativeFdErrno !== 0) {
993
+ js_wasm_trace(
994
+ "js_flock(%d, %d) get_native_fd_from_emscripten_fd errno %d",
995
+ fd,
996
+ op,
997
+ nativeFdErrno
998
+ );
999
+ return -nativeFdErrno;
1000
+ }
1001
+ try {
1002
+ const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
1003
+ const succeeded = fileLockManager.lockWholeFile(nativeFilePath, {
1004
+ type: lockOpType,
1005
+ pid,
1006
+ fd: nativeFd,
1007
+ waitForLock
1008
+ });
1009
+ js_wasm_trace(
1010
+ "flock(%d, %d) lockWholeFile %s returned %d",
1011
+ fd,
1012
+ op,
1013
+ vfsPath,
1014
+ succeeded
1015
+ );
1016
+ if (succeeded) {
1017
+ locking.maybeLockedFds.add(nativeFd);
1018
+ }
1019
+ return succeeded ? 0 : -EWOULDBLOCK;
1020
+ } catch (e) {
1021
+ js_wasm_trace("flock(%d, %d) lockWholeFile error %s", fd, op, e);
1022
+ return -EINVAL;
1023
+ }
1024
+ }
1025
+ function fd_close(fd) {
1026
+ if (!fileLockManager) {
1027
+ js_wasm_trace(
1028
+ "fd_close(%d) file lock manager is not available. delegate to Emscripten builtin fd_close.",
1029
+ fd
1030
+ );
1031
+ return builtin_fd_close(fd);
1032
+ }
1033
+ const [vfsPath, vfsPathResolutionErrno] = locking.get_vfs_path_from_fd(fd);
1034
+ const [nativeFd, nativeFdErrno] = locking.get_native_fd_from_emscripten_fd(fd);
1035
+ const fdCloseResult = builtin_fd_close(fd);
1036
+ if (fdCloseResult !== 0) {
1037
+ js_wasm_trace(
1038
+ "fd_close(%d) %s result %d",
1039
+ fd,
1040
+ vfsPath,
1041
+ fdCloseResult
1042
+ );
1043
+ return fdCloseResult;
1044
+ }
1045
+ if (!locking.maybeLockedFds.has(nativeFd)) {
1046
+ js_wasm_trace(
1047
+ "fd_close(%d) not in maybe-locked-list %s result %d",
1048
+ fd,
1049
+ vfsPath,
1050
+ fdCloseResult
1051
+ );
1052
+ return fdCloseResult;
1053
+ }
1054
+ if (vfsPathResolutionErrno !== 0) {
1055
+ js_wasm_trace(
1056
+ "fd_close(%d) get_vfs_path_from_fd error %d",
1057
+ fd,
1058
+ vfsPathResolutionErrno
1059
+ );
1060
+ return fdCloseResult;
1061
+ }
1062
+ if (nativeFdErrno !== 0) {
1063
+ js_wasm_trace(
1064
+ "fd_close(%d) %s get_native_fd_from_emscripten_fd error %d",
1065
+ fd,
1066
+ vfsPath,
1067
+ nativeFdErrno
1068
+ );
1069
+ return fdCloseResult;
1070
+ }
1071
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
1072
+ return fdCloseResult;
1073
+ }
1074
+ try {
1075
+ js_wasm_trace("fd_close(%d) %s release locks", fd, vfsPath);
1076
+ const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
1077
+ fileLockManager.releaseLocksOnFdClose(
1078
+ pid,
1079
+ nativeFd,
1080
+ nativeFilePath
1081
+ );
1082
+ js_wasm_trace("fd_close(%d) %s release locks success", fd, vfsPath);
1083
+ } catch (e) {
1084
+ js_wasm_trace("fd_close(%d) %s error '%s'", fd, vfsPath, e);
1085
+ }
1086
+ return fdCloseResult;
1087
+ }
1088
+ function js_release_file_locks() {
1089
+ js_wasm_trace("js_release_file_locks()");
1090
+ if (pid === void 0) {
1091
+ js_wasm_trace("js_release_file_locks pid is undefined");
1092
+ return;
1093
+ }
1094
+ if (fileLockManager === void 0) {
1095
+ js_wasm_trace(
1096
+ "js_release_file_locks file lock manager is undefined"
1097
+ );
1098
+ return;
1099
+ }
1100
+ try {
1101
+ fileLockManager.releaseLocksForProcess(pid);
1102
+ js_wasm_trace("js_release_file_locks succeeded");
1103
+ } catch (e) {
1104
+ js_wasm_trace("js_release_file_locks error %s", e);
1105
+ }
1106
+ }
1107
+ async function gethostbyname(hostname) {
1108
+ const { address } = await (0, import_promises.lookup)(hostname, {
1109
+ family: 4,
1110
+ verbatim: false
1111
+ });
1112
+ return address;
1113
+ }
1114
+ return {
1115
+ fcntl64,
1116
+ flock,
1117
+ fd_close,
1118
+ js_release_file_locks,
1119
+ gethostbyname
1120
+ };
1121
+ }
1122
+
1123
+ // packages/php-wasm/node/src/lib/load-runtime.ts
427
1124
  var import_fs5 = __toESM(require("fs"), 1);
428
1125
 
1126
+ // packages/php-wasm/node/src/lib/file-lock-manager-for-posix.ts
1127
+ var import_universal2 = require("@php-wasm/universal");
1128
+ var import_fs_ext_extra_prebuilt = require("fs-ext-extra-prebuilt");
1129
+ var import_logger2 = require("@php-wasm/logger");
1130
+ function isLockDenialError(e) {
1131
+ if (e && typeof e === "object" && "code" in e) {
1132
+ const code = e.code;
1133
+ return code === "EWOULDBLOCK" || code === "EAGAIN" || code === "EACCES";
1134
+ }
1135
+ return false;
1136
+ }
1137
+ var FileLockManagerForPosix = class {
1138
+ constructor() {
1139
+ this.wholeFileLockMap = /* @__PURE__ */ new Map();
1140
+ this.rangeLockedFds = /* @__PURE__ */ new Map();
1141
+ }
1142
+ lockWholeFile(path2, op) {
1143
+ const opType = op.type === "unlock" ? "un" : op.waitForLock ? op.type === "exclusive" ? "ex" : "sh" : op.type === "exclusive" ? "exnb" : "shnb";
1144
+ try {
1145
+ (0, import_fs_ext_extra_prebuilt.flockSync)(op.fd, opType);
1146
+ if (op.type === "unlock") {
1147
+ this.wholeFileLockMap.get(op.pid)?.delete(op.fd);
1148
+ } else {
1149
+ if (!this.wholeFileLockMap.has(op.pid)) {
1150
+ this.wholeFileLockMap.set(op.pid, /* @__PURE__ */ new Map());
1151
+ }
1152
+ this.wholeFileLockMap.get(op.pid).set(op.fd, {
1153
+ ...op,
1154
+ path: path2
1155
+ });
1156
+ }
1157
+ return true;
1158
+ } catch (e) {
1159
+ if (!isLockDenialError(e)) {
1160
+ import_logger2.logger.warn("flock(): unexpected error", e);
1161
+ }
1162
+ return false;
1163
+ }
1164
+ }
1165
+ lockFileByteRange(path2, op, waitForLock) {
1166
+ if (op.start === op.end) {
1167
+ op = {
1168
+ ...op,
1169
+ end: import_universal2.MAX_ADDRESSABLE_FILE_OFFSET
1170
+ };
1171
+ }
1172
+ const fcntlCmd = waitForLock ? "setlkw" : "setlk";
1173
+ const fcntlOp = op.type === "unlocked" ? import_fs_ext_extra_prebuilt.constants.F_UNLCK : op.type === "exclusive" ? import_fs_ext_extra_prebuilt.constants.F_WRLCK : import_fs_ext_extra_prebuilt.constants.F_RDLCK;
1174
+ try {
1175
+ (0, import_fs_ext_extra_prebuilt.fcntlSync)(
1176
+ op.fd,
1177
+ fcntlCmd,
1178
+ fcntlOp,
1179
+ Number(op.start),
1180
+ Number(op.end - op.start)
1181
+ );
1182
+ if (!this.rangeLockedFds.has(op.pid)) {
1183
+ this.rangeLockedFds.set(op.pid, /* @__PURE__ */ new Map());
1184
+ }
1185
+ const pidMap = this.rangeLockedFds.get(op.pid);
1186
+ if (!pidMap.has(path2)) {
1187
+ pidMap.set(path2, /* @__PURE__ */ new Set());
1188
+ }
1189
+ pidMap.get(path2).add(op.fd);
1190
+ return true;
1191
+ } catch (e) {
1192
+ if (!isLockDenialError(e)) {
1193
+ import_logger2.logger.warn("fcntl(): unexpected error", e);
1194
+ }
1195
+ return false;
1196
+ }
1197
+ }
1198
+ findFirstConflictingByteRangeLock(path2, op) {
1199
+ if (op.type === "unlocked") {
1200
+ return void 0;
1201
+ }
1202
+ const obtainedLock = this.lockFileByteRange(path2, op, false);
1203
+ if (obtainedLock) {
1204
+ this.lockFileByteRange(path2, { ...op, type: "unlocked" }, true);
1205
+ return void 0;
1206
+ }
1207
+ return {
1208
+ type: "exclusive",
1209
+ start: 0n,
1210
+ end: 0xffffffffffffffffn,
1211
+ pid: -1
1212
+ };
1213
+ }
1214
+ releaseLocksForProcess(targetPid) {
1215
+ const fdMap = this.wholeFileLockMap.get(targetPid);
1216
+ if (fdMap) {
1217
+ for (const storedLock of fdMap.values()) {
1218
+ try {
1219
+ this.lockWholeFile(storedLock.path, {
1220
+ ...storedLock,
1221
+ type: "unlock"
1222
+ });
1223
+ } catch (e) {
1224
+ import_logger2.logger.error(
1225
+ `releaseLocksForProcess: failed to unlock whole-file lock for pid=${targetPid} fd=${storedLock.fd}`,
1226
+ e
1227
+ );
1228
+ }
1229
+ }
1230
+ this.wholeFileLockMap.delete(targetPid);
1231
+ }
1232
+ for (const [path2, fdSet] of this.rangeLockedFds.get(targetPid) ?? []) {
1233
+ for (const fd of fdSet) {
1234
+ try {
1235
+ this.lockFileByteRange(
1236
+ path2,
1237
+ {
1238
+ pid: targetPid,
1239
+ fd,
1240
+ type: "unlocked",
1241
+ start: 0n,
1242
+ end: import_universal2.MAX_ADDRESSABLE_FILE_OFFSET
1243
+ },
1244
+ false
1245
+ );
1246
+ } catch (e) {
1247
+ import_logger2.logger.error(
1248
+ `releaseLocksForProcess: failed to unlock byte range for pid=${targetPid} fd=${fd} path=${path2}`,
1249
+ e
1250
+ );
1251
+ }
1252
+ }
1253
+ }
1254
+ this.rangeLockedFds.delete(targetPid);
1255
+ }
1256
+ releaseLocksOnFdClose(targetPid, targetFd, targetPath) {
1257
+ this.wholeFileLockMap.get(targetPid)?.delete(targetFd);
1258
+ this.rangeLockedFds.get(targetPid)?.delete(targetPath);
1259
+ }
1260
+ };
1261
+
1262
+ // packages/php-wasm/node/src/lib/file-lock-manager-for-windows.ts
1263
+ var import_fs_ext_extra_prebuilt2 = require("fs-ext-extra-prebuilt");
1264
+ var import_logger3 = require("@php-wasm/logger");
1265
+ var import_universal3 = require("@php-wasm/universal");
1266
+ function toLowAndHigh32BitNumbers(num) {
1267
+ const low = Number(num & 0xffffffffn);
1268
+ const high = Number(num >> 32n & 0xffffffffn);
1269
+ return [low, high];
1270
+ }
1271
+ function isErrnoError(e) {
1272
+ return e !== null && typeof e === "object" && ("errno" in e || "code" in e || "syscall" in e);
1273
+ }
1274
+ function tryLockFileExSync(fd, flags, start, end) {
1275
+ const [offsetLow, offsetHigh] = toLowAndHigh32BitNumbers(start);
1276
+ const [lengthLow, lengthHigh] = toLowAndHigh32BitNumbers(end - start);
1277
+ try {
1278
+ (0, import_fs_ext_extra_prebuilt2.lockFileExSync)(fd, flags, offsetLow, offsetHigh, lengthLow, lengthHigh);
1279
+ return true;
1280
+ } catch (e) {
1281
+ if (!isErrnoError(e)) {
1282
+ throw e;
1283
+ }
1284
+ return false;
1285
+ }
1286
+ }
1287
+ function tryUnlockFileExSync(fd, start, end) {
1288
+ const [offsetLow, offsetHigh] = toLowAndHigh32BitNumbers(start);
1289
+ const [lengthLow, lengthHigh] = toLowAndHigh32BitNumbers(end - start);
1290
+ try {
1291
+ (0, import_fs_ext_extra_prebuilt2.unlockFileExSync)(fd, offsetLow, offsetHigh, lengthLow, lengthHigh);
1292
+ return true;
1293
+ } catch (e) {
1294
+ if (!isErrnoError(e)) {
1295
+ throw e;
1296
+ }
1297
+ return false;
1298
+ }
1299
+ }
1300
+ var FileLockManagerForWindows = class {
1301
+ constructor() {
1302
+ this.wholeFileLockMap = /* @__PURE__ */ new Map();
1303
+ this.rangeLockedFds = /* @__PURE__ */ new Map();
1304
+ }
1305
+ lockWholeFile(path2, op) {
1306
+ const start = 0n;
1307
+ const end = 2n ** 64n - 1n;
1308
+ if (op.type === "unlock") {
1309
+ const success2 = tryUnlockFileExSync(op.fd, start, end);
1310
+ if (success2) {
1311
+ this.wholeFileLockMap.get(op.pid)?.delete(op.fd);
1312
+ if (this.wholeFileLockMap.get(op.pid)?.size === 0) {
1313
+ this.wholeFileLockMap.delete(op.pid);
1314
+ }
1315
+ } else {
1316
+ import_logger3.logger.warn(
1317
+ `lockWholeFile: unlock failed for pid=${op.pid} fd=${op.fd} path=${path2}`
1318
+ );
1319
+ }
1320
+ return success2;
1321
+ }
1322
+ const preexistingLock = this.wholeFileLockMap.get(op.pid)?.get(op.fd);
1323
+ if (op.type === preexistingLock?.type) {
1324
+ return true;
1325
+ }
1326
+ let flags = 0;
1327
+ if (!op.waitForLock) {
1328
+ flags |= import_fs_ext_extra_prebuilt2.constants.LOCKFILE_FAIL_IMMEDIATELY;
1329
+ }
1330
+ let success = false;
1331
+ if (op.type === "shared") {
1332
+ success = tryLockFileExSync(op.fd, flags, start, end);
1333
+ if (success && preexistingLock?.type === "exclusive") {
1334
+ const exclusiveUnlockSuccess = tryUnlockFileExSync(
1335
+ op.fd,
1336
+ start,
1337
+ end
1338
+ );
1339
+ if (!exclusiveUnlockSuccess) {
1340
+ const message = "Failed to unlock preexisting exclusive lock after failing to obtain shared lock";
1341
+ import_logger3.logger.error(message);
1342
+ throw new Error(message);
1343
+ }
1344
+ }
1345
+ } else if (op.type === "exclusive") {
1346
+ flags |= import_fs_ext_extra_prebuilt2.constants.LOCKFILE_EXCLUSIVE_LOCK;
1347
+ let sharedUnlockSuccess;
1348
+ if (preexistingLock?.type === "shared") {
1349
+ sharedUnlockSuccess = tryUnlockFileExSync(op.fd, start, end);
1350
+ if (!sharedUnlockSuccess) {
1351
+ import_logger3.logger.warn(
1352
+ `lockWholeFile: failed to release shared lock before exclusive upgrade for pid=${op.pid} fd=${op.fd}`
1353
+ );
1354
+ }
1355
+ }
1356
+ success = tryLockFileExSync(op.fd, flags, start, end);
1357
+ if (!success) {
1358
+ import_logger3.logger.debug(
1359
+ `lockWholeFile: failed to obtain exclusive lock for pid=${op.pid} fd=${op.fd}`
1360
+ );
1361
+ }
1362
+ if (!success && sharedUnlockSuccess) {
1363
+ const sharedReLockResult = tryLockFileExSync(
1364
+ op.fd,
1365
+ // Wait to restore the shared lock.
1366
+ 0,
1367
+ start,
1368
+ end
1369
+ );
1370
+ if (!sharedReLockResult) {
1371
+ const message = "Failed to re-lock preexisting shared lock after failing to obtain exclusive lock";
1372
+ import_logger3.logger.error(message);
1373
+ throw new Error(message);
1374
+ }
1375
+ }
1376
+ } else {
1377
+ throw new Error(`Unexpected wholeFileLock() op: '${op.type}'`);
1378
+ }
1379
+ if (success) {
1380
+ if (!this.wholeFileLockMap.has(op.pid)) {
1381
+ this.wholeFileLockMap.set(op.pid, /* @__PURE__ */ new Map());
1382
+ }
1383
+ this.wholeFileLockMap.get(op.pid).set(op.fd, {
1384
+ ...op,
1385
+ path: path2
1386
+ });
1387
+ }
1388
+ return success;
1389
+ }
1390
+ lockFileByteRange(path2, op, waitForLock) {
1391
+ if (op.start === op.end) {
1392
+ op = {
1393
+ ...op,
1394
+ end: import_universal3.MAX_ADDRESSABLE_FILE_OFFSET
1395
+ };
1396
+ }
1397
+ if (!this.rangeLockedFds.has(path2)) {
1398
+ this.rangeLockedFds.set(path2, new import_universal3.FileLockIntervalTree());
1399
+ }
1400
+ const lockedRangeTree = this.rangeLockedFds.get(path2);
1401
+ const overlappingLocks = lockedRangeTree.findOverlapping(op);
1402
+ let preexistingLock;
1403
+ 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,
1404
+ // but it matters for Windows where locks are fd-specific.
1405
+ overlappingLocks[0].fd === op.fd && overlappingLocks[0].start === op.start && overlappingLocks[0].end === op.end) {
1406
+ preexistingLock = overlappingLocks[0];
1407
+ }
1408
+ if (op.type === preexistingLock?.type) {
1409
+ return true;
1410
+ }
1411
+ let flags = 0;
1412
+ if (!waitForLock) {
1413
+ flags |= import_fs_ext_extra_prebuilt2.constants.LOCKFILE_FAIL_IMMEDIATELY;
1414
+ }
1415
+ if (op.type === "shared") {
1416
+ const success = tryLockFileExSync(op.fd, flags, op.start, op.end);
1417
+ if (!success) {
1418
+ return false;
1419
+ }
1420
+ if (preexistingLock?.type === "exclusive") {
1421
+ const releasedPreexistingExclusiveLock = tryUnlockFileExSync(
1422
+ preexistingLock.fd,
1423
+ preexistingLock.start,
1424
+ preexistingLock.end
1425
+ );
1426
+ if (!releasedPreexistingExclusiveLock) {
1427
+ const message = "Failed to unlock preexisting exclusive lock after obtaining a shared lock";
1428
+ import_logger3.logger.error(message);
1429
+ throw new Error(message);
1430
+ }
1431
+ lockedRangeTree.remove(preexistingLock);
1432
+ }
1433
+ lockedRangeTree.insert(op);
1434
+ return true;
1435
+ } else if (op.type === "exclusive") {
1436
+ let sharedUnlockSuccess;
1437
+ if (preexistingLock?.type === "shared") {
1438
+ sharedUnlockSuccess = tryUnlockFileExSync(
1439
+ op.fd,
1440
+ op.start,
1441
+ op.end
1442
+ );
1443
+ }
1444
+ if (op.type === "exclusive") {
1445
+ flags |= import_fs_ext_extra_prebuilt2.constants.LOCKFILE_EXCLUSIVE_LOCK;
1446
+ }
1447
+ const success = tryLockFileExSync(op.fd, flags, op.start, op.end);
1448
+ if (!success) {
1449
+ if (preexistingLock && sharedUnlockSuccess) {
1450
+ const sharedRelockSuccess = tryLockFileExSync(
1451
+ op.fd,
1452
+ 0,
1453
+ op.start,
1454
+ op.end
1455
+ );
1456
+ if (!sharedRelockSuccess) {
1457
+ const message = "Failed to re-lock preexisting shared lock after failing to obtain exclusive lock";
1458
+ import_logger3.logger.error(message);
1459
+ throw new Error(message);
1460
+ }
1461
+ }
1462
+ return false;
1463
+ }
1464
+ lockedRangeTree.insert(op);
1465
+ return true;
1466
+ } else {
1467
+ const intersectingLocksForThisProcess = overlappingLocks.filter((lock) => lock.pid === op.pid).filter((lock) => lock.start >= op.start && lock.end <= op.end);
1468
+ for (const lock of intersectingLocksForThisProcess) {
1469
+ const success = tryUnlockFileExSync(
1470
+ lock.fd,
1471
+ lock.start,
1472
+ lock.end
1473
+ );
1474
+ if (!success) {
1475
+ import_logger3.logger.warn(
1476
+ `lockFileByteRange: unlock failed for pid=${op.pid} fd=${lock.fd} range=[${lock.start},${lock.end}]`
1477
+ );
1478
+ return false;
1479
+ }
1480
+ lockedRangeTree.remove(lock);
1481
+ }
1482
+ return true;
1483
+ }
1484
+ }
1485
+ findFirstConflictingByteRangeLock(path2, op) {
1486
+ if (op.type === "unlocked") {
1487
+ return void 0;
1488
+ }
1489
+ const obtainedLock = !!this.lockFileByteRange(path2, op, false);
1490
+ this.lockFileByteRange(path2, { ...op, type: "unlocked" }, false);
1491
+ if (obtainedLock) {
1492
+ return void 0;
1493
+ }
1494
+ return {
1495
+ type: "exclusive",
1496
+ start: 0n,
1497
+ end: 0xffffffffffffffffn,
1498
+ pid: -1
1499
+ };
1500
+ }
1501
+ releaseLocksForProcess(targetPid) {
1502
+ const fdMap = this.wholeFileLockMap.get(targetPid);
1503
+ if (fdMap) {
1504
+ for (const storedLock of fdMap.values()) {
1505
+ try {
1506
+ this.lockWholeFile(storedLock.path, {
1507
+ ...storedLock,
1508
+ type: "unlock"
1509
+ });
1510
+ } catch (e) {
1511
+ import_logger3.logger.error(
1512
+ `releaseLocksForProcess: failed to unlock whole-file lock for pid=${targetPid} fd=${storedLock.fd}`,
1513
+ e
1514
+ );
1515
+ }
1516
+ }
1517
+ this.wholeFileLockMap.delete(targetPid);
1518
+ }
1519
+ for (const [path2, lockedRangeTree] of this.rangeLockedFds.entries()) {
1520
+ const rangesLockedByTargetPid = lockedRangeTree.findLocksForProcess(targetPid);
1521
+ for (const op of rangesLockedByTargetPid) {
1522
+ try {
1523
+ this.lockFileByteRange(
1524
+ path2,
1525
+ { ...op, type: "unlocked" },
1526
+ false
1527
+ );
1528
+ } catch (e) {
1529
+ import_logger3.logger.error(
1530
+ `releaseLocksForProcess: failed to unlock byte range for pid=${targetPid} fd=${op.fd} path=${path2}`,
1531
+ e
1532
+ );
1533
+ }
1534
+ lockedRangeTree.remove(op);
1535
+ }
1536
+ }
1537
+ }
1538
+ releaseLocksOnFdClose(targetPid, targetFd, targetPath) {
1539
+ const storedLock = this.wholeFileLockMap.get(targetPid)?.get(targetFd);
1540
+ if (storedLock) {
1541
+ this.lockWholeFile(storedLock.path, {
1542
+ ...storedLock,
1543
+ type: "unlock"
1544
+ });
1545
+ }
1546
+ this.wholeFileLockMap.get(targetPid)?.delete(targetFd);
1547
+ const lockedRangeTree = this.rangeLockedFds.get(targetPath);
1548
+ for (const op of lockedRangeTree?.findLocksForProcess(targetPid) ?? []) {
1549
+ this.lockFileByteRange(
1550
+ targetPath,
1551
+ {
1552
+ ...op,
1553
+ type: "unlocked",
1554
+ // Use a dummy FD because we're releasing locks for
1555
+ // all FDs on this file (POSIX semantics), not just
1556
+ // the one being closed.
1557
+ fd: -1
1558
+ },
1559
+ false
1560
+ );
1561
+ lockedRangeTree.remove(op);
1562
+ }
1563
+ }
1564
+ };
1565
+
429
1566
  // packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts
430
- var import_universal3 = require("@php-wasm/universal");
1567
+ var import_universal5 = require("@php-wasm/universal");
431
1568
  var import_fs = __toESM(require("fs"), 1);
432
1569
 
433
1570
  // packages/php-wasm/node/src/lib/extensions/xdebug/get-xdebug-extension-module.ts
434
- var import_universal2 = require("@php-wasm/universal");
435
- async function getXdebugExtensionModule(version = import_universal2.LatestSupportedPHPVersion) {
1571
+ var import_universal4 = require("@php-wasm/universal");
1572
+ async function getXdebugExtensionModule(version = import_universal4.LatestSupportedPHPVersion) {
436
1573
  switch (version) {
437
1574
  case "8.5":
438
1575
  return (await import("@php-wasm/node-8-5")).getXdebugExtensionPath();
@@ -453,7 +1590,7 @@ async function getXdebugExtensionModule(version = import_universal2.LatestSuppor
453
1590
  }
454
1591
 
455
1592
  // packages/php-wasm/node/src/lib/extensions/xdebug/with-xdebug.ts
456
- async function withXdebug(version = import_universal3.LatestSupportedPHPVersion, options, xdebugOptions) {
1593
+ async function withXdebug(version = import_universal5.LatestSupportedPHPVersion, options, xdebugOptions) {
457
1594
  const fileName = "xdebug.so";
458
1595
  const filePath = await getXdebugExtensionModule(version);
459
1596
  const extension = import_fs.default.readFileSync(filePath);
@@ -467,13 +1604,13 @@ async function withXdebug(version = import_universal3.LatestSupportedPHPVersion,
467
1604
  if (options.onRuntimeInitialized) {
468
1605
  options.onRuntimeInitialized(phpRuntime);
469
1606
  }
470
- if (!import_universal3.FSHelpers.fileExists(
1607
+ if (!import_universal5.FSHelpers.fileExists(
471
1608
  phpRuntime.FS,
472
1609
  "/internal/shared/extensions"
473
1610
  )) {
474
1611
  phpRuntime.FS.mkdirTree("/internal/shared/extensions");
475
1612
  }
476
- if (!import_universal3.FSHelpers.fileExists(
1613
+ if (!import_universal5.FSHelpers.fileExists(
477
1614
  phpRuntime.FS,
478
1615
  `/internal/shared/extensions/${fileName}`
479
1616
  )) {
@@ -482,7 +1619,7 @@ async function withXdebug(version = import_universal3.LatestSupportedPHPVersion,
482
1619
  new Uint8Array(extension)
483
1620
  );
484
1621
  }
485
- if (!import_universal3.FSHelpers.fileExists(
1622
+ if (!import_universal5.FSHelpers.fileExists(
486
1623
  phpRuntime.FS,
487
1624
  "/internal/shared/extensions/xdebug.ini"
488
1625
  )) {
@@ -512,13 +1649,13 @@ async function withXdebug(version = import_universal3.LatestSupportedPHPVersion,
512
1649
  }
513
1650
 
514
1651
  // packages/php-wasm/node/src/lib/extensions/intl/with-intl.ts
515
- var import_universal5 = require("@php-wasm/universal");
1652
+ var import_universal7 = require("@php-wasm/universal");
516
1653
  var import_fs2 = __toESM(require("fs"), 1);
517
1654
  var import_path = __toESM(require("path"), 1);
518
1655
 
519
1656
  // packages/php-wasm/node/src/lib/extensions/intl/get-intl-extension-module.ts
520
- var import_universal4 = require("@php-wasm/universal");
521
- async function getIntlExtensionModule(version = import_universal4.LatestSupportedPHPVersion) {
1657
+ var import_universal6 = require("@php-wasm/universal");
1658
+ async function getIntlExtensionModule(version = import_universal6.LatestSupportedPHPVersion) {
522
1659
  switch (version) {
523
1660
  case "8.5":
524
1661
  return (await import("@php-wasm/node-8-5")).getIntlExtensionPath();
@@ -540,7 +1677,7 @@ async function getIntlExtensionModule(version = import_universal4.LatestSupporte
540
1677
 
541
1678
  // packages/php-wasm/node/src/lib/extensions/intl/with-intl.ts
542
1679
  var import_meta = {};
543
- async function withIntl(version = import_universal5.LatestSupportedPHPVersion, options) {
1680
+ async function withIntl(version = import_universal7.LatestSupportedPHPVersion, options) {
544
1681
  const extensionName = "intl.so";
545
1682
  const extensionPath = await getIntlExtensionModule(version);
546
1683
  const extension = import_fs2.default.readFileSync(extensionPath);
@@ -559,13 +1696,13 @@ async function withIntl(version = import_universal5.LatestSupportedPHPVersion, o
559
1696
  if (options.onRuntimeInitialized) {
560
1697
  options.onRuntimeInitialized(phpRuntime);
561
1698
  }
562
- if (!import_universal5.FSHelpers.fileExists(
1699
+ if (!import_universal7.FSHelpers.fileExists(
563
1700
  phpRuntime.FS,
564
1701
  "/internal/shared/extensions"
565
1702
  )) {
566
1703
  phpRuntime.FS.mkdirTree("/internal/shared/extensions");
567
1704
  }
568
- if (!import_universal5.FSHelpers.fileExists(
1705
+ if (!import_universal7.FSHelpers.fileExists(
569
1706
  phpRuntime.FS,
570
1707
  `/internal/shared/extensions/${extensionName}`
571
1708
  )) {
@@ -574,7 +1711,7 @@ async function withIntl(version = import_universal5.LatestSupportedPHPVersion, o
574
1711
  new Uint8Array(extension)
575
1712
  );
576
1713
  }
577
- if (!import_universal5.FSHelpers.fileExists(
1714
+ if (!import_universal7.FSHelpers.fileExists(
578
1715
  phpRuntime.FS,
579
1716
  "/internal/shared/extensions/intl.ini"
580
1717
  )) {
@@ -585,7 +1722,7 @@ async function withIntl(version = import_universal5.LatestSupportedPHPVersion, o
585
1722
  ].join("\n")
586
1723
  );
587
1724
  }
588
- if (!import_universal5.FSHelpers.fileExists(
1725
+ if (!import_universal7.FSHelpers.fileExists(
589
1726
  phpRuntime.FS,
590
1727
  `${phpRuntime.ENV.ICU_DATA}/${dataName}`
591
1728
  )) {
@@ -600,12 +1737,12 @@ async function withIntl(version = import_universal5.LatestSupportedPHPVersion, o
600
1737
  }
601
1738
 
602
1739
  // packages/php-wasm/node/src/lib/extensions/redis/with-redis.ts
603
- var import_universal7 = require("@php-wasm/universal");
1740
+ var import_universal9 = require("@php-wasm/universal");
604
1741
  var import_fs3 = __toESM(require("fs"), 1);
605
1742
 
606
1743
  // packages/php-wasm/node/src/lib/extensions/redis/get-redis-extension-module.ts
607
- var import_universal6 = require("@php-wasm/universal");
608
- async function getRedisExtensionModule(version = import_universal6.LatestSupportedPHPVersion) {
1744
+ var import_universal8 = require("@php-wasm/universal");
1745
+ async function getRedisExtensionModule(version = import_universal8.LatestSupportedPHPVersion) {
609
1746
  switch (version) {
610
1747
  case "8.5":
611
1748
  return (await import("@php-wasm/node-8-5")).getRedisExtensionPath();
@@ -626,7 +1763,7 @@ async function getRedisExtensionModule(version = import_universal6.LatestSupport
626
1763
  }
627
1764
 
628
1765
  // packages/php-wasm/node/src/lib/extensions/redis/with-redis.ts
629
- async function withRedis(version = import_universal7.LatestSupportedPHPVersion, options) {
1766
+ async function withRedis(version = import_universal9.LatestSupportedPHPVersion, options) {
630
1767
  const extensionName = "redis.so";
631
1768
  const extensionPath = await getRedisExtensionModule(version);
632
1769
  const extension = import_fs3.default.readFileSync(extensionPath);
@@ -640,13 +1777,13 @@ async function withRedis(version = import_universal7.LatestSupportedPHPVersion,
640
1777
  if (options.onRuntimeInitialized) {
641
1778
  options.onRuntimeInitialized(phpRuntime);
642
1779
  }
643
- if (!import_universal7.FSHelpers.fileExists(
1780
+ if (!import_universal9.FSHelpers.fileExists(
644
1781
  phpRuntime.FS,
645
1782
  "/internal/shared/extensions"
646
1783
  )) {
647
1784
  phpRuntime.FS.mkdirTree("/internal/shared/extensions");
648
1785
  }
649
- if (!import_universal7.FSHelpers.fileExists(
1786
+ if (!import_universal9.FSHelpers.fileExists(
650
1787
  phpRuntime.FS,
651
1788
  `/internal/shared/extensions/${extensionName}`
652
1789
  )) {
@@ -655,7 +1792,7 @@ async function withRedis(version = import_universal7.LatestSupportedPHPVersion,
655
1792
  new Uint8Array(extension)
656
1793
  );
657
1794
  }
658
- if (!import_universal7.FSHelpers.fileExists(
1795
+ if (!import_universal9.FSHelpers.fileExists(
659
1796
  phpRuntime.FS,
660
1797
  "/internal/shared/extensions/redis.ini"
661
1798
  )) {
@@ -671,12 +1808,12 @@ async function withRedis(version = import_universal7.LatestSupportedPHPVersion,
671
1808
  }
672
1809
 
673
1810
  // packages/php-wasm/node/src/lib/extensions/memcached/with-memcached.ts
674
- var import_universal9 = require("@php-wasm/universal");
1811
+ var import_universal11 = require("@php-wasm/universal");
675
1812
  var import_fs4 = __toESM(require("fs"), 1);
676
1813
 
677
1814
  // packages/php-wasm/node/src/lib/extensions/memcached/get-memcached-extension-module.ts
678
- var import_universal8 = require("@php-wasm/universal");
679
- async function getMemcachedExtensionModule(version = import_universal8.LatestSupportedPHPVersion) {
1815
+ var import_universal10 = require("@php-wasm/universal");
1816
+ async function getMemcachedExtensionModule(version = import_universal10.LatestSupportedPHPVersion) {
680
1817
  switch (version) {
681
1818
  case "8.5":
682
1819
  return (await import("@php-wasm/node-8-5")).getMemcachedExtensionPath();
@@ -697,7 +1834,7 @@ async function getMemcachedExtensionModule(version = import_universal8.LatestSup
697
1834
  }
698
1835
 
699
1836
  // packages/php-wasm/node/src/lib/extensions/memcached/with-memcached.ts
700
- async function withMemcached(version = import_universal9.LatestSupportedPHPVersion, options) {
1837
+ async function withMemcached(version = import_universal11.LatestSupportedPHPVersion, options) {
701
1838
  const extensionName = "memcached.so";
702
1839
  const extensionPath = await getMemcachedExtensionModule(version);
703
1840
  const extension = import_fs4.default.readFileSync(extensionPath);
@@ -711,13 +1848,13 @@ async function withMemcached(version = import_universal9.LatestSupportedPHPVersi
711
1848
  if (options.onRuntimeInitialized) {
712
1849
  options.onRuntimeInitialized(phpRuntime);
713
1850
  }
714
- if (!import_universal9.FSHelpers.fileExists(
1851
+ if (!import_universal11.FSHelpers.fileExists(
715
1852
  phpRuntime.FS,
716
1853
  "/internal/shared/extensions"
717
1854
  )) {
718
1855
  phpRuntime.FS.mkdirTree("/internal/shared/extensions");
719
1856
  }
720
- if (!import_universal9.FSHelpers.fileExists(
1857
+ if (!import_universal11.FSHelpers.fileExists(
721
1858
  phpRuntime.FS,
722
1859
  `/internal/shared/extensions/${extensionName}`
723
1860
  )) {
@@ -726,7 +1863,7 @@ async function withMemcached(version = import_universal9.LatestSupportedPHPVersi
726
1863
  new Uint8Array(extension)
727
1864
  );
728
1865
  }
729
- if (!import_universal9.FSHelpers.fileExists(
1866
+ if (!import_universal11.FSHelpers.fileExists(
730
1867
  phpRuntime.FS,
731
1868
  "/internal/shared/extensions/memcached.ini"
732
1869
  )) {
@@ -743,6 +1880,7 @@ async function withMemcached(version = import_universal9.LatestSupportedPHPVersi
743
1880
 
744
1881
  // packages/php-wasm/node/src/lib/load-runtime.ts
745
1882
  var import_util = require("@php-wasm/util");
1883
+ var import_os = require("os");
746
1884
  async function loadNodeRuntime(phpVersion, options = {}) {
747
1885
  let emscriptenOptions = {
748
1886
  /**
@@ -753,6 +1891,14 @@ async function loadNodeRuntime(phpVersion, options = {}) {
753
1891
  quit: function(code, error) {
754
1892
  throw error;
755
1893
  },
1894
+ bindUserSpace: (userSpaceContext) => {
1895
+ const nativeFileLockManager = (0, import_os.platform)() === "win32" ? new FileLockManagerForWindows() : new FileLockManagerForPosix();
1896
+ const fileLockManager = options.fileLockManager ? new import_universal12.FileLockManagerComposite({
1897
+ nativeLockManager: nativeFileLockManager,
1898
+ wasmLockManager: options.fileLockManager
1899
+ }) : nativeFileLockManager;
1900
+ return bindUserSpace({ fileLockManager }, userSpaceContext);
1901
+ },
756
1902
  ...options.emscriptenOptions || {},
757
1903
  onRuntimeInitialized: (phpRuntime) => {
758
1904
  if (options?.followSymlinks === true) {
@@ -768,7 +1914,7 @@ async function loadNodeRuntime(phpVersion, options = {}) {
768
1914
  normalizedPath
769
1915
  );
770
1916
  if (import_fs5.default.existsSync(absoluteSourcePath)) {
771
- if (!import_universal10.FSHelpers.fileExists(
1917
+ if (!import_universal12.FSHelpers.fileExists(
772
1918
  phpRuntime.FS,
773
1919
  symlinkMountPath
774
1920
  )) {
@@ -824,7 +1970,7 @@ async function loadNodeRuntime(phpVersion, options = {}) {
824
1970
  }
825
1971
  emscriptenOptions = await withNetworking(emscriptenOptions);
826
1972
  const phpLoaderModule = await getPHPLoaderModule(phpVersion);
827
- const runtimeId = await (0, import_universal10.loadPHPRuntime)(phpLoaderModule, emscriptenOptions);
1973
+ const runtimeId = await (0, import_universal12.loadPHPRuntime)(phpLoaderModule, emscriptenOptions);
828
1974
  return runtimeId;
829
1975
  }
830
1976
 
@@ -832,14 +1978,14 @@ async function loadNodeRuntime(phpVersion, options = {}) {
832
1978
  var import_node_fs = require("node:fs");
833
1979
 
834
1980
  // packages/php-wasm/node/src/lib/node-fs-mount.ts
835
- var import_universal11 = require("@php-wasm/universal");
1981
+ var import_universal13 = require("@php-wasm/universal");
836
1982
  var import_util2 = require("@php-wasm/util");
837
1983
  var import_fs6 = require("fs");
838
1984
  var import_path2 = require("path");
839
1985
  function createNodeFsMountHandler(localPath) {
840
1986
  return function(php, FS, vfsMountPoint) {
841
1987
  let removeVfsNode = false;
842
- if (!import_universal11.FSHelpers.fileExists(FS, vfsMountPoint)) {
1988
+ if (!import_universal13.FSHelpers.fileExists(FS, vfsMountPoint)) {
843
1989
  const lstat = (0, import_fs6.lstatSync)(localPath);
844
1990
  if (lstat.isFile() || lstat.isSymbolicLink()) {
845
1991
  FS.mkdirTree((0, import_path2.dirname)(vfsMountPoint));
@@ -853,9 +1999,9 @@ function createNodeFsMountHandler(localPath) {
853
1999
  }
854
2000
  removeVfsNode = true;
855
2001
  }
856
- let lookup2;
2002
+ let lookup3;
857
2003
  try {
858
- lookup2 = FS.lookupPath(vfsMountPoint);
2004
+ lookup3 = FS.lookupPath(vfsMountPoint);
859
2005
  } catch (e) {
860
2006
  const error = e;
861
2007
  if (error.errno === 44) {
@@ -869,7 +2015,7 @@ function createNodeFsMountHandler(localPath) {
869
2015
  return () => {
870
2016
  FS.unmount(vfsMountPoint);
871
2017
  if (removeVfsNode) {
872
- if (FS.isDir(lookup2.node.mode)) {
2018
+ if (FS.isDir(lookup3.node.mode)) {
873
2019
  if ((0, import_util2.isParentOf)(vfsMountPoint, FS.cwd())) {
874
2020
  throw new Error(
875
2021
  `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}).`
@@ -923,642 +2069,11 @@ function statPathFollowSymlinks(path2) {
923
2069
  }
924
2070
  return stat;
925
2071
  }
926
-
927
- // packages/php-wasm/node/src/lib/file-lock-manager-for-node.ts
928
- var import_logger2 = require("@php-wasm/logger");
929
- var import_fs7 = require("fs");
930
- var MAX_64BIT_OFFSET = BigInt(2n ** 64n - 1n);
931
- var FileLockManagerForNode = class {
932
- /**
933
- * Create a new FileLockManagerForNode instance.
934
- *
935
- * @param nativeFlockSync A synchronous flock() function to lock files via the host OS.
936
- */
937
- constructor(nativeFlockSync = function flockSyncNoOp() {
938
- }) {
939
- this.nativeFlockSync = nativeFlockSync;
940
- this.locks = /* @__PURE__ */ new Map();
941
- }
942
- /**
943
- * Lock the whole file.
944
- *
945
- * @param path The path to the file to lock. This should be the path
946
- * of the file in the native filesystem.
947
- * @param op The whole file lock operation to perform.
948
- * @returns True if the lock was granted, false otherwise.
949
- */
950
- lockWholeFile(path2, op) {
951
- if (this.locks.get(path2) === void 0) {
952
- if (op.type === "unlock") {
953
- return true;
954
- }
955
- const maybeLock = FileLock.maybeCreate(
956
- path2,
957
- op.type,
958
- this.nativeFlockSync
959
- );
960
- if (maybeLock === void 0) {
961
- return false;
962
- }
963
- this.locks.set(path2, maybeLock);
964
- }
965
- const lock = this.locks.get(path2);
966
- const result = lock.lockWholeFile(op);
967
- this.forgetPathIfUnlocked(path2);
968
- return result;
969
- }
970
- /**
971
- * Lock a byte range.
972
- *
973
- * @param path The path to the file to lock. This should be the path
974
- * of the file in the native filesystem.
975
- * @param requestedLock The byte range lock to perform.
976
- * @returns True if the lock was granted, false otherwise.
977
- */
978
- lockFileByteRange(path2, requestedLock) {
979
- if (!this.locks.has(path2)) {
980
- if (requestedLock.type === "unlocked") {
981
- return true;
982
- }
983
- const maybeLock = FileLock.maybeCreate(
984
- path2,
985
- requestedLock.type,
986
- this.nativeFlockSync
987
- );
988
- if (maybeLock === void 0) {
989
- return false;
990
- }
991
- this.locks.set(path2, maybeLock);
992
- }
993
- const lock = this.locks.get(path2);
994
- return lock.lockFileByteRange(requestedLock);
995
- }
996
- /**
997
- * Find the first conflicting byte range lock.
998
- *
999
- * @param path The path to the file to find the conflicting lock for.
1000
- * @param desiredLock The desired byte range lock.
1001
- * @returns The first conflicting byte range lock, or undefined if no conflicting lock exists.
1002
- */
1003
- findFirstConflictingByteRangeLock(path2, desiredLock) {
1004
- const lock = this.locks.get(path2);
1005
- if (lock === void 0) {
1006
- return void 0;
1007
- }
1008
- return lock.findFirstConflictingByteRangeLock(desiredLock);
1009
- }
1010
- /**
1011
- * Release all locks for the given process.
1012
- *
1013
- * @param pid The process ID to release locks for.
1014
- */
1015
- releaseLocksForProcess(pid) {
1016
- for (const [path2, lock] of this.locks.entries()) {
1017
- lock.releaseLocksForProcess(pid);
1018
- this.forgetPathIfUnlocked(path2);
1019
- }
1020
- }
1021
- /**
1022
- * Release all locks for the given process and file descriptor.
1023
- *
1024
- * @param pid The process ID to release locks for.
1025
- * @param fd The file descriptor to release locks for.
1026
- * @param path The path to the file to release locks for.
1027
- */
1028
- releaseLocksForProcessFd(pid, fd, nativePath) {
1029
- const lock = this.locks.get(nativePath);
1030
- if (!lock) {
1031
- return;
1032
- }
1033
- lock.releaseLocksForProcessFd(pid, fd);
1034
- this.forgetPathIfUnlocked(nativePath);
1035
- }
1036
- /**
1037
- * Forget the path if it is unlocked.
1038
- *
1039
- * @param path The path to the file to forget.
1040
- */
1041
- forgetPathIfUnlocked(path2) {
1042
- const lock = this.locks.get(path2);
1043
- if (!lock) {
1044
- return;
1045
- }
1046
- if (lock.isUnlocked()) {
1047
- lock.dispose();
1048
- this.locks.delete(path2);
1049
- }
1050
- }
1051
- };
1052
- var FileLock = class _FileLock {
1053
- /**
1054
- * Create a new FileLock instance for the given file and mode.
1055
- * Fail if the underlying native file lock cannot be acquired.
1056
- *
1057
- * @param path The path to the file to lock
1058
- * @param mode The type of lock to acquire
1059
- * @returns A FileLock instance if the lock was acquired, undefined otherwise
1060
- */
1061
- static maybeCreate(path2, mode, nativeFlockSync) {
1062
- let fd;
1063
- try {
1064
- fd = (0, import_fs7.openSync)(path2, "a+");
1065
- const flockFlags = mode === "exclusive" ? "exnb" : "shnb";
1066
- nativeFlockSync(fd, flockFlags);
1067
- const nativeLock = { fd, mode, nativeFlockSync };
1068
- return new _FileLock(nativeLock);
1069
- } catch {
1070
- if (fd !== void 0) {
1071
- try {
1072
- (0, import_fs7.closeSync)(fd);
1073
- } catch (error) {
1074
- import_logger2.logger.error(
1075
- "Error closing locking file descriptor",
1076
- error
1077
- );
1078
- }
1079
- }
1080
- return void 0;
1081
- }
1082
- }
1083
- constructor(nativeLock) {
1084
- this.nativeLock = nativeLock;
1085
- this.rangeLocks = new FileLockIntervalTree();
1086
- this.wholeFileLock = { type: "unlocked" };
1087
- }
1088
- /**
1089
- * Close the file descriptor and release the native lock.
1090
- *
1091
- * @TODO Replace this with a Symbol.dispose property once supported by all JS runtimes.
1092
- */
1093
- dispose() {
1094
- try {
1095
- (0, import_fs7.closeSync)(this.nativeLock.fd);
1096
- } catch (error) {
1097
- import_logger2.logger.error("Error closing locking file descriptor", error);
1098
- }
1099
- }
1100
- /**
1101
- * Lock the whole file.
1102
- *
1103
- * This method corresponds to the flock() function.
1104
- *
1105
- * @param op The whole file lock operation to perform.
1106
- * @returns True if the lock was granted, false otherwise.
1107
- */
1108
- lockWholeFile(op) {
1109
- if (op.type === "unlock") {
1110
- const originalType = this.wholeFileLock.type;
1111
- if (originalType === "unlocked") {
1112
- } else if (this.wholeFileLock.type === "exclusive" && this.wholeFileLock.pid === op.pid && this.wholeFileLock.fd === op.fd) {
1113
- this.wholeFileLock = { type: "unlocked" };
1114
- } else if (this.wholeFileLock.type === "shared" && this.wholeFileLock.pidFds.has(op.pid) && this.wholeFileLock.pidFds.get(op.pid).has(op.fd)) {
1115
- this.wholeFileLock.pidFds.get(op.pid).delete(op.fd);
1116
- if (this.wholeFileLock.pidFds.get(op.pid).size === 0) {
1117
- this.wholeFileLock.pidFds.delete(op.pid);
1118
- }
1119
- if (this.wholeFileLock.pidFds.size === 0) {
1120
- this.wholeFileLock = { type: "unlocked" };
1121
- }
1122
- }
1123
- if (!this.ensureCompatibleNativeLock()) {
1124
- import_logger2.logger.error(
1125
- "Unable to update native lock after removing a whole file lock."
1126
- );
1127
- }
1128
- return true;
1129
- }
1130
- if (this.isThereAConflictWithRequestedWholeFileLock(op)) {
1131
- return false;
1132
- }
1133
- if (!this.ensureCompatibleNativeLock({
1134
- overrideWholeFileLockType: op.type
1135
- })) {
1136
- return false;
1137
- }
1138
- if (op.type === "exclusive") {
1139
- this.wholeFileLock = {
1140
- type: "exclusive",
1141
- pid: op.pid,
1142
- fd: op.fd
1143
- };
1144
- return true;
1145
- }
1146
- if (op.type === "shared") {
1147
- if (this.wholeFileLock.type !== "shared") {
1148
- this.wholeFileLock = {
1149
- type: "shared",
1150
- pidFds: /* @__PURE__ */ new Map()
1151
- };
1152
- }
1153
- const sharedLock = this.wholeFileLock;
1154
- if (!sharedLock.pidFds.has(op.pid)) {
1155
- sharedLock.pidFds.set(op.pid, /* @__PURE__ */ new Set());
1156
- }
1157
- sharedLock.pidFds.get(op.pid).add(op.fd);
1158
- return true;
1159
- }
1160
- throw new Error(`Unexpected wholeFileLock() op: '${op.type}'`);
1161
- }
1162
- /**
1163
- * Lock a byte range.
1164
- *
1165
- * This method corresponds to the fcntl() F_SETLK command.
1166
- *
1167
- * @param requestedLock The byte range lock to perform.
1168
- * @returns True if the lock was granted, false otherwise.
1169
- */
1170
- lockFileByteRange(requestedLock) {
1171
- if (requestedLock.start === requestedLock.end) {
1172
- requestedLock = {
1173
- ...requestedLock,
1174
- end: MAX_64BIT_OFFSET
1175
- };
1176
- }
1177
- if (requestedLock.type === "unlocked") {
1178
- const overlappingLocksBySameProcess = this.rangeLocks.findOverlapping(requestedLock).filter((lock) => lock.pid === requestedLock.pid);
1179
- for (const overlappingLock of overlappingLocksBySameProcess) {
1180
- this.rangeLocks.remove(overlappingLock);
1181
- if (overlappingLock.start < requestedLock.start) {
1182
- this.rangeLocks.insert({
1183
- ...overlappingLock,
1184
- end: requestedLock.start
1185
- });
1186
- }
1187
- if (overlappingLock.end > requestedLock.end) {
1188
- this.rangeLocks.insert({
1189
- ...overlappingLock,
1190
- start: requestedLock.end
1191
- });
1192
- }
1193
- }
1194
- if (!this.ensureCompatibleNativeLock()) {
1195
- import_logger2.logger.error(
1196
- "Unable to update native lock after removing a byte range lock."
1197
- );
1198
- }
1199
- return true;
1200
- }
1201
- if (this.isThereAConflictWithRequestedRangeLock(requestedLock)) {
1202
- return false;
1203
- }
1204
- if (!this.ensureCompatibleNativeLock({
1205
- overrideRangeLockType: requestedLock.type
1206
- })) {
1207
- return false;
1208
- }
1209
- const overlappingLocksFromSameProcess = this.rangeLocks.findOverlapping(requestedLock).filter((lock) => lock.pid === requestedLock.pid);
1210
- let minStart = requestedLock.start;
1211
- let maxEnd = requestedLock.end;
1212
- for (const overlappingLock of overlappingLocksFromSameProcess) {
1213
- this.rangeLocks.remove(overlappingLock);
1214
- if (overlappingLock.start < minStart) {
1215
- minStart = overlappingLock.start;
1216
- }
1217
- if (overlappingLock.end > maxEnd) {
1218
- maxEnd = overlappingLock.end;
1219
- }
1220
- }
1221
- const mergedLock = {
1222
- ...requestedLock,
1223
- start: minStart,
1224
- end: maxEnd
1225
- };
1226
- this.rangeLocks.insert(mergedLock);
1227
- return true;
1228
- }
1229
- /**
1230
- * Find the first conflicting byte range lock.
1231
- *
1232
- * This method corresponds to the fcntl() F_GETLK command.
1233
- *
1234
- * @param desiredLock The desired byte range lock.
1235
- * @returns The first conflicting byte range lock, or undefined if no conflicting lock exists.
1236
- */
1237
- findFirstConflictingByteRangeLock(desiredLock) {
1238
- const overlappingLocks = this.rangeLocks.findOverlapping(desiredLock);
1239
- const firstConflictingRangeLock = overlappingLocks.find(
1240
- (lock) => lock.pid !== desiredLock.pid && (desiredLock.type === "exclusive" || lock.type === "exclusive")
1241
- );
1242
- if (firstConflictingRangeLock) {
1243
- return firstConflictingRangeLock;
1244
- }
1245
- if (this.wholeFileLock.type === "unlocked") {
1246
- return void 0;
1247
- }
1248
- const wfl = this.wholeFileLock;
1249
- if (wfl.type === "exclusive" || desiredLock.type === "exclusive") {
1250
- return {
1251
- type: this.wholeFileLock.type,
1252
- start: 0n,
1253
- end: 0n,
1254
- pid: -1
1255
- };
1256
- }
1257
- return void 0;
1258
- }
1259
- /**
1260
- * Release all locks for the given process.
1261
- *
1262
- * @param pid The process ID to release locks for.
1263
- */
1264
- releaseLocksForProcess(pid) {
1265
- for (const rangeLock of this.rangeLocks.findLocksForProcess(pid)) {
1266
- this.lockFileByteRange({
1267
- ...rangeLock,
1268
- type: "unlocked"
1269
- });
1270
- }
1271
- if (this.wholeFileLock.type === "exclusive" && this.wholeFileLock.pid === pid) {
1272
- this.lockWholeFile({
1273
- pid,
1274
- fd: this.wholeFileLock.fd,
1275
- type: "unlock"
1276
- });
1277
- } else if (this.wholeFileLock.type === "shared" && this.wholeFileLock.pidFds.has(pid)) {
1278
- for (const fd of this.wholeFileLock.pidFds.get(pid)) {
1279
- this.lockWholeFile({
1280
- pid,
1281
- fd,
1282
- type: "unlock"
1283
- });
1284
- }
1285
- }
1286
- }
1287
- /**
1288
- * Release all locks for the given process and file descriptor.
1289
- *
1290
- * @param pid The process ID to release locks for.
1291
- * @param fd The file descriptor to release locks for.
1292
- */
1293
- releaseLocksForProcessFd(pid, fd) {
1294
- for (const rangeLock of this.rangeLocks.findLocksForProcess(pid)) {
1295
- this.lockFileByteRange({
1296
- ...rangeLock,
1297
- type: "unlocked"
1298
- });
1299
- }
1300
- this.lockWholeFile({
1301
- pid,
1302
- fd,
1303
- type: "unlock"
1304
- });
1305
- }
1306
- /**
1307
- * Check if the file lock is unlocked.
1308
- *
1309
- * @returns True if the file lock is unlocked, false otherwise.
1310
- */
1311
- isUnlocked() {
1312
- return this.wholeFileLock.type === "unlocked" && this.rangeLocks.isEmpty();
1313
- }
1314
- /**
1315
- * Ensure that the native lock is compatible with the php-wasm lock,
1316
- * upgrading or downgrading as needed.
1317
- *
1318
- * @param overrideWholeFileLockType If provided, use this type for the whole file lock.
1319
- * @param overrideRangeLockType If provided, use this type for the range lock.
1320
- * @returns True if the native lock was upgraded or downgraded, false otherwise.
1321
- */
1322
- ensureCompatibleNativeLock({
1323
- overrideWholeFileLockType,
1324
- overrideRangeLockType
1325
- } = {}) {
1326
- const wholeFileLockType = overrideWholeFileLockType ?? this.wholeFileLock.type;
1327
- const rangeLockType = overrideRangeLockType ?? this.rangeLocks.findStrictestExistingLockType();
1328
- let requiredNativeLockType;
1329
- if (wholeFileLockType === "exclusive" || rangeLockType === "exclusive") {
1330
- requiredNativeLockType = "exclusive";
1331
- } else if (wholeFileLockType === "shared" || rangeLockType === "shared") {
1332
- requiredNativeLockType = "shared";
1333
- } else {
1334
- requiredNativeLockType = "unlock";
1335
- }
1336
- if (this.nativeLock.mode === requiredNativeLockType) {
1337
- return true;
1338
- }
1339
- const flockFlags = requiredNativeLockType === "exclusive" && "exnb" || requiredNativeLockType === "shared" && "shnb" || "un";
1340
- try {
1341
- this.nativeLock.nativeFlockSync(this.nativeLock.fd, flockFlags);
1342
- this.nativeLock.mode = requiredNativeLockType;
1343
- return true;
1344
- } catch {
1345
- return false;
1346
- }
1347
- }
1348
- /**
1349
- * Check if a lock exists that conflicts with the requested range lock.
1350
- *
1351
- * @param requestedLock The desired byte range lock.
1352
- * @returns True if a conflicting lock exists, false otherwise.
1353
- */
1354
- isThereAConflictWithRequestedRangeLock(requestedLock) {
1355
- return this.findFirstConflictingByteRangeLock(requestedLock) !== void 0;
1356
- }
1357
- /**
1358
- * Check if a lock exists that conflicts with the requested whole-file lock.
1359
- *
1360
- * @param requestedLock The desired whole-file lock.
1361
- * @returns True if a conflicting lock exists, false otherwise.
1362
- */
1363
- isThereAConflictWithRequestedWholeFileLock(requestedLock) {
1364
- if (requestedLock.type === "exclusive") {
1365
- if (this.wholeFileLock.type === "exclusive" && (this.wholeFileLock.fd !== requestedLock.fd || this.wholeFileLock.pid !== requestedLock.pid)) {
1366
- return true;
1367
- }
1368
- if (this.wholeFileLock.type === "shared" && Array.from(this.wholeFileLock.pidFds).some(
1369
- ([pid]) => pid !== requestedLock.pid
1370
- )) {
1371
- return true;
1372
- }
1373
- const overlappingLocks = this.rangeLocks.findOverlapping({
1374
- type: "unlocked",
1375
- start: 0n,
1376
- end: MAX_64BIT_OFFSET,
1377
- pid: -1
1378
- });
1379
- if (overlappingLocks.length > 0) {
1380
- return true;
1381
- }
1382
- return false;
1383
- }
1384
- if (requestedLock.type === "shared") {
1385
- if (this.wholeFileLock.type === "exclusive" && this.wholeFileLock.pid !== requestedLock.pid) {
1386
- return true;
1387
- }
1388
- const overlappingLocks = this.rangeLocks.findOverlapping({
1389
- type: "unlocked",
1390
- start: 0n,
1391
- end: MAX_64BIT_OFFSET,
1392
- pid: -1
1393
- });
1394
- const exclusiveRangeLocks = overlappingLocks.filter(
1395
- (lock) => lock.type === "exclusive"
1396
- );
1397
- if (exclusiveRangeLocks.length > 0) {
1398
- return true;
1399
- }
1400
- return false;
1401
- }
1402
- return false;
1403
- }
1404
- };
1405
- var IntervalNode = class {
1406
- constructor(range) {
1407
- this.left = null;
1408
- this.right = null;
1409
- this.range = range;
1410
- this.max = range.end;
1411
- }
1412
- };
1413
- var FileLockIntervalTree = class {
1414
- constructor() {
1415
- this.root = null;
1416
- }
1417
- isEmpty() {
1418
- return this.root === null;
1419
- }
1420
- /**
1421
- * Insert a new locked range into the tree
1422
- */
1423
- insert(range) {
1424
- this.root = this.insertNode(this.root, range);
1425
- }
1426
- /**
1427
- * Find all ranges that overlap with the given range
1428
- */
1429
- findOverlapping(range) {
1430
- const result = [];
1431
- this.findOverlappingRanges(this.root, range, result);
1432
- return result;
1433
- }
1434
- /**
1435
- * Remove a lock range from the tree
1436
- */
1437
- remove(range) {
1438
- this.root = this.removeNode(this.root, range);
1439
- }
1440
- /**
1441
- * Find all ranges locked by the given process.
1442
- *
1443
- * @param pid The process ID to find locks for.
1444
- * @returns All locked ranges for the given process.
1445
- */
1446
- findLocksForProcess(pid) {
1447
- const result = [];
1448
- this.findLocksForProcessInNode(this.root, pid, result);
1449
- return result;
1450
- }
1451
- /**
1452
- * Find the strictest existing lock type in the range lock tree.
1453
- *
1454
- * @returns The strictest existing lock type, or 'unlocked' if no locks exist.
1455
- */
1456
- findStrictestExistingLockType() {
1457
- let maxType = "unlocked";
1458
- const traverse = (node) => {
1459
- if (!node) {
1460
- return;
1461
- }
1462
- if (node.range.type === "exclusive") {
1463
- maxType = "exclusive";
1464
- return;
1465
- }
1466
- if (node.range.type === "shared") {
1467
- maxType = "shared";
1468
- }
1469
- traverse(node.left);
1470
- traverse(node.right);
1471
- };
1472
- traverse(this.root);
1473
- return maxType;
1474
- }
1475
- insertNode(node, range) {
1476
- if (!node) {
1477
- return new IntervalNode(range);
1478
- }
1479
- if (range.start < node.range.start) {
1480
- node.left = this.insertNode(node.left, range);
1481
- } else {
1482
- node.right = this.insertNode(node.right, range);
1483
- }
1484
- node.max = this.bigintMax(node.max, range.end);
1485
- return node;
1486
- }
1487
- bigintMax(...args) {
1488
- return args.reduce((max, current) => {
1489
- return current > max ? current : max;
1490
- }, args[0]);
1491
- }
1492
- findOverlappingRanges(node, range, result) {
1493
- if (!node) {
1494
- return;
1495
- }
1496
- if (this.doRangesOverlap(node.range, range)) {
1497
- result.push(node.range);
1498
- }
1499
- if (node.left && node.left.max >= range.start) {
1500
- this.findOverlappingRanges(node.left, range, result);
1501
- }
1502
- if (node.right && node.range.start <= range.end) {
1503
- this.findOverlappingRanges(node.right, range, result);
1504
- }
1505
- }
1506
- doRangesOverlap(a, b) {
1507
- return a.start < b.end && b.start < a.end;
1508
- }
1509
- removeNode(node, range) {
1510
- if (!node) {
1511
- return null;
1512
- }
1513
- if (this.areRangesEqual(node.range, range)) {
1514
- if (!node.left) {
1515
- return node.right;
1516
- }
1517
- if (!node.right) {
1518
- return node.left;
1519
- }
1520
- const successor = this.findMin(node.right);
1521
- node.range = successor.range;
1522
- node.right = this.removeNode(node.right, successor.range);
1523
- } else if (range.start < node.range.start) {
1524
- node.left = this.removeNode(node.left, range);
1525
- } else {
1526
- node.right = this.removeNode(node.right, range);
1527
- }
1528
- node.max = node.range.end;
1529
- if (node.left) {
1530
- node.max = this.bigintMax(node.max, node.left.max);
1531
- }
1532
- if (node.right) {
1533
- node.max = this.bigintMax(node.max, node.right.max);
1534
- }
1535
- return node;
1536
- }
1537
- findMin(node) {
1538
- let current = node;
1539
- while (current.left) {
1540
- current = current.left;
1541
- }
1542
- return current;
1543
- }
1544
- areRangesEqual(a, b) {
1545
- return a.start === b.start && a.end === b.end && a.pid === b.pid;
1546
- }
1547
- findLocksForProcessInNode(node, pid, result) {
1548
- if (!node) {
1549
- return;
1550
- }
1551
- if (node.range.pid === pid) {
1552
- result.push(node.range);
1553
- }
1554
- this.findLocksForProcessInNode(node.left, pid, result);
1555
- this.findLocksForProcessInNode(node.right, pid, result);
1556
- }
1557
- };
1558
2072
  // Annotate the CommonJS export names for ESM import in node:
1559
2073
  0 && (module.exports = {
1560
- FileLock,
1561
- FileLockManagerForNode,
2074
+ FileLockManagerForPosix,
2075
+ FileLockManagerForWindows,
2076
+ bindUserSpace,
1562
2077
  createNodeFsMountHandler,
1563
2078
  getPHPLoaderModule,
1564
2079
  loadNodeRuntime,