@php-wasm/node 1.1.2 → 1.1.4

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.
Files changed (40) hide show
  1. package/asyncify/7_2_34/php_7_2.wasm +0 -0
  2. package/asyncify/7_3_33/php_7_3.wasm +0 -0
  3. package/asyncify/7_4_33/php_7_4.wasm +0 -0
  4. package/asyncify/8_0_30/php_8_0.wasm +0 -0
  5. package/asyncify/8_1_23/php_8_1.wasm +0 -0
  6. package/asyncify/8_2_10/php_8_2.wasm +0 -0
  7. package/asyncify/8_3_0/php_8_3.wasm +0 -0
  8. package/asyncify/8_4_0/php_8_4.wasm +0 -0
  9. package/asyncify/php_7_2.js +351 -157
  10. package/asyncify/php_7_3.js +350 -156
  11. package/asyncify/php_7_4.js +349 -155
  12. package/asyncify/php_8_0.js +353 -159
  13. package/asyncify/php_8_1.js +352 -158
  14. package/asyncify/php_8_2.js +353 -159
  15. package/asyncify/php_8_3.js +353 -159
  16. package/asyncify/php_8_4.js +352 -158
  17. package/fs_ext.node +0 -0
  18. package/index.cjs +9896 -2782
  19. package/index.js +9780 -2782
  20. package/jspi/7_2_34/php_7_2.wasm +0 -0
  21. package/jspi/7_3_33/php_7_3.wasm +0 -0
  22. package/jspi/7_4_33/php_7_4.wasm +0 -0
  23. package/jspi/8_0_30/php_8_0.wasm +0 -0
  24. package/jspi/8_1_23/php_8_1.wasm +0 -0
  25. package/jspi/8_2_10/php_8_2.wasm +0 -0
  26. package/jspi/8_3_0/php_8_3.wasm +0 -0
  27. package/jspi/8_4_0/php_8_4.wasm +0 -0
  28. package/jspi/php_7_2.js +1130 -340
  29. package/jspi/php_7_3.js +1130 -340
  30. package/jspi/php_7_4.js +1130 -340
  31. package/jspi/php_8_0.js +1130 -340
  32. package/jspi/php_8_1.js +1129 -339
  33. package/jspi/php_8_2.js +1129 -339
  34. package/jspi/php_8_3.js +1129 -339
  35. package/jspi/php_8_4.js +1129 -339
  36. package/lib/file-lock-manager-for-node.d.ts +149 -0
  37. package/lib/file-lock-manager.d.ts +96 -0
  38. package/lib/index.d.ts +2 -0
  39. package/lib/load-runtime.d.ts +31 -2
  40. package/package.json +10 -9
package/jspi/php_8_3.js CHANGED
@@ -8,7 +8,7 @@ import path from 'path';
8
8
 
9
9
  const dependencyFilename = path.join(__dirname, '8_3_0', 'php_8_3.wasm');
10
10
  export { dependencyFilename };
11
- export const dependenciesTotalSize = 17914907;
11
+ export const dependenciesTotalSize = 17917660;
12
12
  export function init(RuntimeName, PHPLoader) {
13
13
  // The rest of the code comes from the built php.js file and esm-suffix.js
14
14
  // include: shell.js
@@ -5227,7 +5227,360 @@ export function init(RuntimeName, PHPLoader) {
5227
5227
 
5228
5228
  var syscallGetVarargP = syscallGetVarargI;
5229
5229
 
5230
- function ___syscall_fcntl64(fd, cmd, varargs) {
5230
+ var stringToUTF8 = (str, outPtr, maxBytesToWrite) =>
5231
+ stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
5232
+
5233
+ Module['stringToUTF8'] = stringToUTF8;
5234
+
5235
+ var stackAlloc = (sz) => __emscripten_stack_alloc(sz);
5236
+
5237
+ /** @suppress {duplicate } */ var stringToUTF8OnStack = (str) => {
5238
+ var size = lengthBytesUTF8(str) + 1;
5239
+ var ret = stackAlloc(size);
5240
+ stringToUTF8(str, ret, size);
5241
+ return ret;
5242
+ };
5243
+
5244
+ var allocateUTF8OnStack = stringToUTF8OnStack;
5245
+
5246
+ var PHPWASM = {
5247
+ init: function () {
5248
+ // The /internal directory is required by the C module. It's where the
5249
+ // stdout, stderr, and headers information are written for the JavaScript
5250
+ // code to read later on.
5251
+ FS.mkdir('/internal');
5252
+ // The files from the shared directory are shared between all the
5253
+ // PHP processes managed by PHPProcessManager.
5254
+ FS.mkdir('/internal/shared');
5255
+ // The files from the preload directory are preloaded using the
5256
+ // auto_prepend_file php.ini directive.
5257
+ FS.mkdir('/internal/shared/preload');
5258
+ // Create stdout and stderr devices. We can't just use Emscripten's
5259
+ // default stdout and stderr devices because they stop processing data
5260
+ // on the first null byte. However, when dealing with binary data,
5261
+ // null bytes are valid and common.
5262
+ FS.registerDevice(FS.makedev(64, 0), {
5263
+ open: () => {},
5264
+ close: () => {},
5265
+ read: () => 0,
5266
+ write: (stream, buffer, offset, length, pos) => {
5267
+ const chunk = buffer.subarray(offset, offset + length);
5268
+ PHPWASM.onStdout(chunk);
5269
+ return length;
5270
+ },
5271
+ });
5272
+ FS.mkdev('/internal/stdout', FS.makedev(64, 0));
5273
+ FS.registerDevice(FS.makedev(63, 0), {
5274
+ open: () => {},
5275
+ close: () => {},
5276
+ read: () => 0,
5277
+ write: (stream, buffer, offset, length, pos) => {
5278
+ const chunk = buffer.subarray(offset, offset + length);
5279
+ PHPWASM.onStderr(chunk);
5280
+ return length;
5281
+ },
5282
+ });
5283
+ FS.mkdev('/internal/stderr', FS.makedev(63, 0));
5284
+ FS.registerDevice(FS.makedev(62, 0), {
5285
+ open: () => {},
5286
+ close: () => {},
5287
+ read: () => 0,
5288
+ write: (stream, buffer, offset, length, pos) => {
5289
+ const chunk = buffer.subarray(offset, offset + length);
5290
+ PHPWASM.onHeaders(chunk);
5291
+ return length;
5292
+ },
5293
+ });
5294
+ FS.mkdev('/internal/headers', FS.makedev(62, 0));
5295
+ // Handle events.
5296
+ PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE
5297
+ ? require('events').EventEmitter
5298
+ : class EventEmitter {
5299
+ constructor() {
5300
+ this.listeners = {};
5301
+ }
5302
+ emit(eventName, data) {
5303
+ if (this.listeners[eventName]) {
5304
+ this.listeners[eventName].forEach(
5305
+ (callback) => {
5306
+ callback(data);
5307
+ }
5308
+ );
5309
+ }
5310
+ }
5311
+ once(eventName, callback) {
5312
+ const self = this;
5313
+ function removedCallback() {
5314
+ callback(...arguments);
5315
+ self.removeListener(eventName, removedCallback);
5316
+ }
5317
+ this.on(eventName, removedCallback);
5318
+ }
5319
+ removeAllListeners(eventName) {
5320
+ if (eventName) {
5321
+ delete this.listeners[eventName];
5322
+ } else {
5323
+ this.listeners = {};
5324
+ }
5325
+ }
5326
+ removeListener(eventName, callback) {
5327
+ if (this.listeners[eventName]) {
5328
+ const idx =
5329
+ this.listeners[eventName].indexOf(callback);
5330
+ if (idx !== -1) {
5331
+ this.listeners[eventName].splice(idx, 1);
5332
+ }
5333
+ }
5334
+ }
5335
+ };
5336
+ // Clean up the fd -> childProcess mapping when the fd is closed:
5337
+ const originalClose = FS.close;
5338
+ FS.close = function (stream) {
5339
+ originalClose(stream);
5340
+ delete PHPWASM.child_proc_by_fd[stream.fd];
5341
+ };
5342
+ PHPWASM.child_proc_by_fd = {};
5343
+ PHPWASM.child_proc_by_pid = {};
5344
+ PHPWASM.input_devices = {};
5345
+ const originalWrite = TTY.stream_ops.write;
5346
+ TTY.stream_ops.write = function (stream, ...rest) {
5347
+ const retval = originalWrite(stream, ...rest);
5348
+ // Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event
5349
+ // @TODO: Fix this at the wasm level
5350
+ stream.tty.ops.fsync(stream.tty);
5351
+ return retval;
5352
+ };
5353
+ const originalPutChar = TTY.stream_ops.put_char;
5354
+ TTY.stream_ops.put_char = function (tty, val) {
5355
+ /**
5356
+ * Buffer newlines that Emscripten normally ignores.
5357
+ *
5358
+ * Emscripten doesn't do it by default because its default
5359
+ * print function is console.log that implicitly adds a newline. We are overwriting
5360
+ * it with an environment-specific function that outputs exaclty what it was given,
5361
+ * e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure
5362
+ * all the newlines make it to the output buffer.
5363
+ */ if (val === 10) tty.output.push(val);
5364
+ return originalPutChar(tty, val);
5365
+ };
5366
+ },
5367
+ onHeaders: function (chunk) {
5368
+ if (Module['onHeaders']) {
5369
+ Module['onHeaders'](chunk);
5370
+ return;
5371
+ }
5372
+ console.log('headers', {
5373
+ chunk,
5374
+ });
5375
+ },
5376
+ onStdout: function (chunk) {
5377
+ if (Module['onStdout']) {
5378
+ Module['onStdout'](chunk);
5379
+ return;
5380
+ }
5381
+ if (ENVIRONMENT_IS_NODE) {
5382
+ process.stdout.write(chunk);
5383
+ } else {
5384
+ console.log('stdout', {
5385
+ chunk,
5386
+ });
5387
+ }
5388
+ },
5389
+ onStderr: function (chunk) {
5390
+ if (Module['onStderr']) {
5391
+ Module['onStderr'](chunk);
5392
+ return;
5393
+ }
5394
+ if (ENVIRONMENT_IS_NODE) {
5395
+ process.stderr.write(chunk);
5396
+ } else {
5397
+ console.warn('stderr', {
5398
+ chunk,
5399
+ });
5400
+ }
5401
+ },
5402
+ getAllWebSockets: function (sock) {
5403
+ const webSockets = new Set();
5404
+ if (sock.server) {
5405
+ sock.server.clients.forEach((ws) => {
5406
+ webSockets.add(ws);
5407
+ });
5408
+ }
5409
+ for (const peer of PHPWASM.getAllPeers(sock)) {
5410
+ webSockets.add(peer.socket);
5411
+ }
5412
+ return Array.from(webSockets);
5413
+ },
5414
+ getAllPeers: function (sock) {
5415
+ const peers = new Set();
5416
+ if (sock.server) {
5417
+ sock.pending
5418
+ .filter((pending) => pending.peers)
5419
+ .forEach((pending) => {
5420
+ for (const peer of Object.values(pending.peers)) {
5421
+ peers.add(peer);
5422
+ }
5423
+ });
5424
+ }
5425
+ if (sock.peers) {
5426
+ for (const peer of Object.values(sock.peers)) {
5427
+ peers.add(peer);
5428
+ }
5429
+ }
5430
+ return Array.from(peers);
5431
+ },
5432
+ awaitData: function (ws) {
5433
+ return PHPWASM.awaitEvent(ws, 'message');
5434
+ },
5435
+ awaitConnection: function (ws) {
5436
+ if (ws.OPEN === ws.readyState) {
5437
+ return [Promise.resolve(), PHPWASM.noop];
5438
+ }
5439
+ return PHPWASM.awaitEvent(ws, 'open');
5440
+ },
5441
+ awaitClose: function (ws) {
5442
+ if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
5443
+ return [Promise.resolve(), PHPWASM.noop];
5444
+ }
5445
+ return PHPWASM.awaitEvent(ws, 'close');
5446
+ },
5447
+ awaitError: function (ws) {
5448
+ if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
5449
+ return [Promise.resolve(), PHPWASM.noop];
5450
+ }
5451
+ return PHPWASM.awaitEvent(ws, 'error');
5452
+ },
5453
+ awaitEvent: function (ws, event) {
5454
+ let resolve;
5455
+ const listener = () => {
5456
+ resolve();
5457
+ };
5458
+ const promise = new Promise(function (_resolve) {
5459
+ resolve = _resolve;
5460
+ ws.once(event, listener);
5461
+ });
5462
+ const cancel = () => {
5463
+ ws.removeListener(event, listener);
5464
+ // Rejecting the promises bubbles up and kills the entire
5465
+ // node process. Let's resolve them on the next tick instead
5466
+ // to give the caller some space to unbind any handlers.
5467
+ setTimeout(resolve);
5468
+ };
5469
+ return [promise, cancel];
5470
+ },
5471
+ noop: function () {},
5472
+ spawnProcess: function (command, args, options) {
5473
+ if (Module['spawnProcess']) {
5474
+ const spawnedPromise = Module['spawnProcess'](
5475
+ command,
5476
+ args,
5477
+ options
5478
+ );
5479
+ return Promise.resolve(spawnedPromise).then(function (spawned) {
5480
+ if (!spawned || !spawned.on) {
5481
+ throw new Error(
5482
+ 'spawnProcess() must return an EventEmitter but returned a different type.'
5483
+ );
5484
+ }
5485
+ return spawned;
5486
+ });
5487
+ }
5488
+ if (ENVIRONMENT_IS_NODE) {
5489
+ return require('child_process').spawn(command, args, {
5490
+ ...options,
5491
+ shell: true,
5492
+ stdio: ['pipe', 'pipe', 'pipe'],
5493
+ timeout: 100,
5494
+ });
5495
+ }
5496
+ const e = new Error(
5497
+ 'popen(), proc_open() etc. are unsupported in the browser. Call php.setSpawnHandler() ' +
5498
+ 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' +
5499
+ 'and similar functions via php.ini.'
5500
+ );
5501
+ e.code = 'SPAWN_UNSUPPORTED';
5502
+ throw e;
5503
+ },
5504
+ shutdownSocket: function (socketd, how) {
5505
+ // This implementation only supports websockets at the moment
5506
+ const sock = getSocketFromFD(socketd);
5507
+ const peer = Object.values(sock.peers)[0];
5508
+ if (!peer) {
5509
+ return -1;
5510
+ }
5511
+ try {
5512
+ peer.socket.close();
5513
+ SOCKFS.websocket_sock_ops.removePeer(sock, peer);
5514
+ return 0;
5515
+ } catch (e) {
5516
+ console.log('Socket shutdown error', e);
5517
+ return -1;
5518
+ }
5519
+ },
5520
+ };
5521
+
5522
+ function _js_getpid() {
5523
+ return PHPLoader.processId ?? 42;
5524
+ }
5525
+
5526
+ function _js_wasm_trace(format, ...args) {
5527
+ if (PHPLoader.trace instanceof Function) {
5528
+ PHPLoader.trace(_js_getpid(), format, ...args);
5529
+ }
5530
+ }
5531
+
5532
+ function _fd_close(fd) {
5533
+ _js_wasm_trace('fd_close(%d)', fd);
5534
+ const [vfsPath, pathResolutionErrno] = locking.get_vfs_path_from_fd(fd);
5535
+ if (pathResolutionErrno !== 0) {
5536
+ _js_wasm_trace(
5537
+ 'fd_close(%d) get_vfs_path_from_fd error %d',
5538
+ fd,
5539
+ pathResolutionErrno
5540
+ );
5541
+ return -ERRNO_CODES.EBADF;
5542
+ }
5543
+ const result = _builtin_fd_close(fd);
5544
+ if (result === 0 && locking.maybeLockedFds.has(fd)) {
5545
+ const nativeFilePath =
5546
+ locking.get_native_path_from_vfs_path(vfsPath);
5547
+ return PHPLoader.fileLockManager
5548
+ .releaseLocksForProcessFd(
5549
+ PHPLoader.processId,
5550
+ fd,
5551
+ nativeFilePath
5552
+ )
5553
+ .then(() => {
5554
+ _js_wasm_trace('fd_close(%d) release locks success', fd);
5555
+ })
5556
+ .catch((e) => {
5557
+ _js_wasm_trace("fd_close(%d) error '%s'", fd, e);
5558
+ })
5559
+ .then(() => {
5560
+ _js_wasm_trace('fd_close(%d) result %d', fd, result);
5561
+ return result;
5562
+ })
5563
+ .finally(() => {
5564
+ locking.maybeLockedFds.delete(fd);
5565
+ });
5566
+ } else {
5567
+ _js_wasm_trace('fd_close(%d) result %d', fd, result);
5568
+ return result;
5569
+ }
5570
+ }
5571
+
5572
+ function _builtin_fd_close(fd) {
5573
+ try {
5574
+ var stream = SYSCALLS.getStreamFromFD(fd);
5575
+ FS.close(stream);
5576
+ return 0;
5577
+ } catch (e) {
5578
+ if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;
5579
+ return e.errno;
5580
+ }
5581
+ }
5582
+
5583
+ function _builtin_fcntl64(fd, cmd, varargs) {
5231
5584
  SYSCALLS.varargs = varargs;
5232
5585
  try {
5233
5586
  var stream = SYSCALLS.getStreamFromFD(fd);
@@ -5253,35 +5606,468 @@ export function init(RuntimeName, PHPLoader) {
5253
5606
  case 3:
5254
5607
  return stream.flags;
5255
5608
 
5256
- case 4: {
5257
- var arg = syscallGetVarargI();
5258
- stream.flags |= arg;
5259
- return 0;
5609
+ case 4: {
5610
+ var arg = syscallGetVarargI();
5611
+ stream.flags |= arg;
5612
+ return 0;
5613
+ }
5614
+
5615
+ case 12: {
5616
+ var arg = syscallGetVarargP();
5617
+ var offset = 0;
5618
+ // We're always unlocked.
5619
+ HEAP16[(arg + offset) >> 1] = 2;
5620
+ return 0;
5621
+ }
5622
+
5623
+ case 13:
5624
+ case 14:
5625
+ // Pretend that the locking is successful. These are process-level locks,
5626
+ // and Emscripten programs are a single process. If we supported linking a
5627
+ // filesystem between programs, we'd need to do more here.
5628
+ // See https://github.com/emscripten-core/emscripten/issues/23697
5629
+ return 0;
5630
+ }
5631
+ return -28;
5632
+ } catch (e) {
5633
+ if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;
5634
+ return -e.errno;
5635
+ }
5636
+ }
5637
+
5638
+ var locking = {
5639
+ maybeLockedFds: new Set(),
5640
+ F_RDLCK: 0,
5641
+ F_WRLCK: 1,
5642
+ F_UNLCK: 2,
5643
+ lockStateToFcntl: {
5644
+ shared: 0,
5645
+ exclusive: 1,
5646
+ unlocked: 2,
5647
+ },
5648
+ fcntlToLockState: {
5649
+ 0: 'shared',
5650
+ 1: 'exclusive',
5651
+ 2: 'unlocked',
5652
+ },
5653
+ is_shared_fs_node(node) {
5654
+ if (node?.isSharedFS) {
5655
+ return true;
5656
+ }
5657
+ // Handle PROXYFS nodes which wrap other nodes.
5658
+ if (!node?.mount?.opts?.fs?.lookupPath) {
5659
+ return false;
5660
+ }
5661
+ const vfsPath = NODEFS.realPath(node);
5662
+ const underlyingNode = node.mount.opts.fs.lookupPath(vfsPath)?.node;
5663
+ return !!underlyingNode?.isSharedFS;
5664
+ },
5665
+ is_path_to_shared_fs(path) {
5666
+ const { node } = FS.lookupPath(path);
5667
+ return locking.is_shared_fs_node(node);
5668
+ },
5669
+ get_fd_access_mode(fd) {
5670
+ const emscripten_F_GETFL = Number('3');
5671
+ const emscripten_O_ACCMODE = Number('2097155');
5672
+ return (
5673
+ _builtin_fcntl64(fd, emscripten_F_GETFL) & emscripten_O_ACCMODE
5674
+ );
5675
+ },
5676
+ get_vfs_path_from_fd(fd) {
5677
+ try {
5678
+ return [FS.readlink(`/proc/self/fd/${fd}`), 0];
5679
+ } catch (error) {
5680
+ return [null, ERRNO_CODES.EBADF];
5681
+ }
5682
+ },
5683
+ get_native_path_from_vfs_path(vfsPath) {
5684
+ const { node } = FS.lookupPath(vfsPath);
5685
+ return NODEFS.realPath(node);
5686
+ },
5687
+ check_lock_params(fd, l_type) {
5688
+ const emscripten_O_RDONLY = Number('0');
5689
+ const emscripten_O_WRONLY = Number('1');
5690
+ const accessMode = locking.get_fd_access_mode(fd);
5691
+ if (
5692
+ (l_type === locking.F_WRLCK &&
5693
+ accessMode === emscripten_O_RDONLY) ||
5694
+ (l_type === locking.F_RDLCK &&
5695
+ accessMode === emscripten_O_WRONLY)
5696
+ ) {
5697
+ return ERRNO_CODES.EBADF;
5698
+ }
5699
+ return 0;
5700
+ },
5701
+ };
5702
+
5703
+ async function ___syscall_fcntl64(fd, cmd, varargs) {
5704
+ // Necessary to use varargs accessor
5705
+ SYSCALLS.varargs = varargs;
5706
+ // These constants are replaced by Emscripten during the build process
5707
+ const emscripten_F_GETLK = Number('12');
5708
+ const emscripten_F_SETLK = Number('13');
5709
+ const emscripten_F_SETLKW = Number('14');
5710
+ const emscripten_SEEK_SET = Number('0');
5711
+ // NOTE: With the exception of l_type, these offsets are not exposed to
5712
+ // JS by Emscripten, so we hardcode them here.
5713
+ const emscripten_flock_l_type_offset = 0;
5714
+ const emscripten_flock_l_whence_offset = 2;
5715
+ const emscripten_flock_l_start_offset = 8;
5716
+ const emscripten_flock_l_len_offset = 16;
5717
+ const emscripten_flock_l_pid_offset = 24;
5718
+ /**
5719
+ * Read the flock struct at the given address.
5720
+ *
5721
+ * @param {bigint} flockStructAddress - the address of the flock struct
5722
+ * @returns the flock struct
5723
+ */ function read_flock_struct(flockStructAddress) {
5724
+ /*
5725
+ * NOTE: Since we are using HEAP<WORD_SIZE> vars like HEAP16 and HEAP64,
5726
+ * we need to adjust offsets to address the word size of each HEAP.
5727
+ *
5728
+ * For example, an offset of 64 bytes is the following for each HEAP:
5729
+ * - HEAP8: 64 (the 64th byte)
5730
+ * - HEAP16: 32 (the 32nd 16-bit word)
5731
+ * - HEAP32: 16 (the 16th 32-bit word)
5732
+ * - HEAP64: 8 (the 8th 64-bit word)
5733
+ *
5734
+ * We get a word offset by dividing the byte offset by the word size.
5735
+ */ return {
5736
+ l_type: HEAP16[ // Shift right by 1 to divide by 2^1.
5737
+ (flockStructAddress + emscripten_flock_l_type_offset) >> 1
5738
+ ],
5739
+ l_whence:
5740
+ HEAP16[ // Shift right by 1 to divide by 2^1.
5741
+ (flockStructAddress +
5742
+ emscripten_flock_l_whence_offset) >>
5743
+ 1
5744
+ ],
5745
+ l_start:
5746
+ HEAP64[ // Shift right by 3 to divide by 2^3.
5747
+ (flockStructAddress +
5748
+ emscripten_flock_l_start_offset) >>
5749
+ 3
5750
+ ],
5751
+ l_len: HEAP64[ // Shift right by 3 to divide by 2^3.
5752
+ (flockStructAddress + emscripten_flock_l_len_offset) >> 3
5753
+ ],
5754
+ l_pid: HEAP32[ // Shift right by 2 to divide by 2^2.
5755
+ (flockStructAddress + emscripten_flock_l_pid_offset) >> 2
5756
+ ],
5757
+ };
5758
+ }
5759
+ /**
5760
+ * Update the flock struct at the given address with the given fields.
5761
+ *
5762
+ * @param {bigint} flockStructAddress - the address of the flock struct
5763
+ * @param {object} fields - the fields to update
5764
+ */ function update_flock_struct(flockStructAddress, fields) {
5765
+ /*
5766
+ * NOTE: Since we are using HEAP<WORD_SIZE> vars like HEAP16 and HEAP64,
5767
+ * we need to adjust offsets to address the word size of each HEAP.
5768
+ *
5769
+ * For example, an offset of 64 bytes is the following for each HEAP:
5770
+ * - HEAP8: 64 (the 64th byte)
5771
+ * - HEAP16: 32 (the 32nd 16-bit word)
5772
+ * - HEAP32: 16 (the 16th 32-bit word)
5773
+ * - HEAP64: 8 (the 8th 64-bit word)
5774
+ *
5775
+ * We get a word offset by dividing the byte offset by the word size.
5776
+ */ if (fields.l_type !== undefined) {
5777
+ HEAP16[ // Shift right by 1 to divide by 2^1.
5778
+ (flockStructAddress + emscripten_flock_l_type_offset) >> 1
5779
+ ] = fields.l_type;
5780
+ }
5781
+ if (fields.l_whence !== undefined) {
5782
+ HEAP16[ // Shift right by 1 to divide by 2^1.
5783
+ (flockStructAddress + emscripten_flock_l_whence_offset) >> 1
5784
+ ] = fields.l_whence;
5785
+ }
5786
+ if (fields.l_start !== undefined) {
5787
+ HEAP64[ // Shift right by 3 to divide by 2^3.
5788
+ (flockStructAddress + emscripten_flock_l_start_offset) >> 3
5789
+ ] = fields.l_start;
5790
+ }
5791
+ if (fields.l_len !== undefined) {
5792
+ HEAP64[ // Shift right by 3 to divide by 2^3.
5793
+ (flockStructAddress + emscripten_flock_l_len_offset) >> 3
5794
+ ] = fields.l_len;
5795
+ }
5796
+ if (fields.l_pid !== undefined) {
5797
+ HEAP32[ // Shift right by 2 to divide by 2^2.
5798
+ (flockStructAddress + emscripten_flock_l_pid_offset) >> 2
5799
+ ] = fields.l_pid;
5800
+ }
5801
+ }
5802
+ /**
5803
+ * Resolve the base address of the range depending on the whence and start offset.
5804
+ *
5805
+ * @param {number} fd - the file descriptor
5806
+ * @param {number} whence - what the start offset is relative to
5807
+ * @param {bigint} startOffset - the offset from the whence
5808
+ * @returns The resolved offset and the errno. If there is an error,
5809
+ * the resolved offset is null, and the errno is non-zero.
5810
+ */ function get_base_address(fd, whence, startOffset) {
5811
+ let baseAddress;
5812
+ switch (whence) {
5813
+ case emscripten_SEEK_SET:
5814
+ baseAddress = 0n;
5815
+ break;
5816
+
5817
+ case emscripten_SEEK_CUR:
5818
+ baseAddress = FS.lseek(fd, 0, whence);
5819
+ break;
5820
+
5821
+ case emscripten_SEEK_END:
5822
+ baseAddress = _wasm_get_end_offset(fd);
5823
+ break;
5824
+
5825
+ default:
5826
+ return [null, ERRNO_CODES.EINVAL];
5827
+ }
5828
+ if (baseAddress == -1) {
5829
+ // We cannot resolve the offset within the file.
5830
+ // Let's treat this as a problem with the file descriptor.
5831
+ return [null, ERRNO_CODES.EBADF];
5832
+ }
5833
+ const resolvedOffset = baseAddress + startOffset;
5834
+ if (resolvedOffset < 0) {
5835
+ // This is not a valid offset. Report args as invalid.
5836
+ return [null, ERRNO_CODES.EINVAL];
5837
+ }
5838
+ return [resolvedOffset, 0];
5839
+ }
5840
+ const pid = PHPLoader.processId;
5841
+ switch (cmd) {
5842
+ case emscripten_F_GETLK: {
5843
+ _js_wasm_trace('fcntl(%d, F_GETLK)', fd);
5844
+ let vfsPath;
5845
+ let errno;
5846
+ [vfsPath, errno] = locking.get_vfs_path_from_fd(fd);
5847
+ if (errno !== 0) {
5848
+ _js_wasm_trace(
5849
+ 'fcntl(%d, F_GETLK) %s get_vfs_path_from_fd errno %d',
5850
+ fd,
5851
+ vfsPath,
5852
+ errno
5853
+ );
5854
+ return -ERRNO_CODES.EBADF;
5855
+ }
5856
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
5857
+ _js_wasm_trace(
5858
+ "fcntl(%d, F_GETLK) locking is not implemented for non-NodeFS path '%s'",
5859
+ fd,
5860
+ vfsPath
5861
+ );
5862
+ // If not a NodeFS path, we can't lock it.
5863
+ // Default to succeeding as Emscripten does.
5864
+ update_flock_struct(flockStructAddr, {
5865
+ l_type: F_UNLCK,
5866
+ });
5867
+ return 0;
5868
+ }
5869
+ const flockStructAddr = syscallGetVarargP();
5870
+ const flockStruct = read_flock_struct(flockStructAddr);
5871
+ if (!(flockStruct.l_type in locking.fcntlToLockState)) {
5872
+ return -ERRNO_CODES.EINVAL;
5873
+ }
5874
+ errno = locking.check_lock_params(fd, flockStruct.l_type);
5875
+ if (errno !== 0) {
5876
+ _js_wasm_trace(
5877
+ 'fcntl(%d, F_GETLK) %s check_lock_params errno %d',
5878
+ fd,
5879
+ vfsPath,
5880
+ errno
5881
+ );
5882
+ return -ERRNO_CODES.EINVAL;
5883
+ }
5884
+ const requestedLockType =
5885
+ locking.fcntlToLockState[flockStruct.l_type];
5886
+ let absoluteStartOffset;
5887
+ [absoluteStartOffset, errno] = get_base_address(
5888
+ fd,
5889
+ flockStruct.l_whence,
5890
+ flockStruct.l_start
5891
+ );
5892
+ if (errno !== 0) {
5893
+ _js_wasm_trace(
5894
+ 'fcntl(%d, F_GETLK) %s get_base_address errno %d',
5895
+ fd,
5896
+ vfsPath,
5897
+ errno
5898
+ );
5899
+ return -ERRNO_CODES.EINVAL;
5900
+ }
5901
+ const nativeFilePath =
5902
+ locking.get_native_path_from_vfs_path(vfsPath);
5903
+ return PHPLoader.fileLockManager
5904
+ .findFirstConflictingByteRangeLock(nativeFilePath, {
5905
+ type: requestedLockType,
5906
+ start: absoluteStartOffset,
5907
+ end: absoluteStartOffset + flockStruct.l_len,
5908
+ pid,
5909
+ })
5910
+ .then((conflictingLock) => {
5911
+ if (conflictingLock === undefined) {
5912
+ _js_wasm_trace(
5913
+ 'fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=unlocked start=0x%x end=0x%x',
5914
+ fd,
5915
+ vfsPath,
5916
+ absoluteStartOffset,
5917
+ absoluteStartOffset + flockStruct.l_len
5918
+ );
5919
+ update_flock_struct(flockStructAddr, {
5920
+ l_type: F_UNLCK,
5921
+ });
5922
+ return 0;
5923
+ }
5924
+ _js_wasm_trace(
5925
+ 'fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=%s start=0x%x end=0x%x conflictingLock %d',
5926
+ fd,
5927
+ vfsPath,
5928
+ conflictingLock.type,
5929
+ conflictingLock.start,
5930
+ conflictingLock.end,
5931
+ conflictingLock.pid
5932
+ );
5933
+ const fcntlLockState =
5934
+ locking.lockStateToFcntl[conflictingLock.type];
5935
+ update_flock_struct(flockStructAddr, {
5936
+ l_type: fcntlLockState,
5937
+ l_whence: emscripten_SEEK_SET,
5938
+ l_start: conflictingLock.start,
5939
+ l_len: conflictingLock.end - conflictingLock.start,
5940
+ l_pid: conflictingLock.pid,
5941
+ });
5942
+ return 0;
5943
+ })
5944
+ .catch((e) => {
5945
+ _js_wasm_trace(
5946
+ 'fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock error %s',
5947
+ fd,
5948
+ vfsPath,
5949
+ e
5950
+ );
5951
+ return -ERRNO_CODES.EINVAL;
5952
+ });
5953
+ }
5954
+
5955
+ case emscripten_F_SETLK: {
5956
+ _js_wasm_trace('fcntl(%d, F_SETLK)', fd);
5957
+ let vfsPath;
5958
+ let errno;
5959
+ [vfsPath, errno] = locking.get_vfs_path_from_fd(fd);
5960
+ if (errno !== 0) {
5961
+ _js_wasm_trace(
5962
+ 'fcntl(%d, F_SETLK) %s get_vfs_path_from_fd errno %d',
5963
+ fd,
5964
+ vfsPath,
5965
+ errno
5966
+ );
5967
+ return -errno;
5260
5968
  }
5261
-
5262
- case 12: {
5263
- var arg = syscallGetVarargP();
5264
- var offset = 0;
5265
- // We're always unlocked.
5266
- HEAP16[(arg + offset) >> 1] = 2;
5969
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
5970
+ _js_wasm_trace(
5971
+ 'fcntl(%d, F_SETLK) locking is not implemented for non-NodeFS path %s',
5972
+ fd,
5973
+ vfsPath
5974
+ );
5975
+ // If not a NodeFS path, we can't lock it.
5976
+ // Default to succeeding as Emscripten does.
5267
5977
  return 0;
5268
5978
  }
5979
+ var flockStructAddr = syscallGetVarargP();
5980
+ const flockStruct = read_flock_struct(flockStructAddr);
5981
+ let absoluteStartOffset;
5982
+ [absoluteStartOffset, errno] = get_base_address(
5983
+ fd,
5984
+ flockStruct.l_whence,
5985
+ flockStruct.l_start
5986
+ );
5987
+ if (errno !== 0) {
5988
+ _js_wasm_trace(
5989
+ 'fcntl(%d, F_SETLK) %s get_base_address errno %d',
5990
+ fd,
5991
+ vfsPath,
5992
+ errno
5993
+ );
5994
+ return -errno;
5995
+ }
5996
+ if (!(flockStruct.l_type in locking.fcntlToLockState)) {
5997
+ _js_wasm_trace(
5998
+ 'fcntl(%d, F_SETLK) %s invalid lock type %d',
5999
+ fd,
6000
+ vfsPath,
6001
+ flockStruct.l_type
6002
+ );
6003
+ return -ERRNO_CODES.EINVAL;
6004
+ }
6005
+ errno = locking.check_lock_params(fd, flockStruct.l_type);
6006
+ if (errno !== 0) {
6007
+ _js_wasm_trace(
6008
+ 'fcntl(%d, F_SETLK) %s check_lock_params errno %d',
6009
+ fd,
6010
+ vfsPath,
6011
+ errno
6012
+ );
6013
+ return -errno;
6014
+ }
6015
+ locking.maybeLockedFds.add(fd);
6016
+ const requestedLockType =
6017
+ locking.fcntlToLockState[flockStruct.l_type];
6018
+ const rangeLock = {
6019
+ type: requestedLockType,
6020
+ start: absoluteStartOffset,
6021
+ end: absoluteStartOffset + flockStruct.l_len,
6022
+ pid,
6023
+ };
6024
+ const nativeFilePath =
6025
+ locking.get_native_path_from_vfs_path(vfsPath);
6026
+ _js_wasm_trace(
6027
+ 'fcntl(%d, F_SETLK) %s calling lockFileByteRange for range lock %s',
6028
+ fd,
6029
+ vfsPath,
6030
+ rangeLock
6031
+ );
6032
+ return PHPLoader.fileLockManager
6033
+ .lockFileByteRange(nativeFilePath, rangeLock)
6034
+ .then((succeeded) => {
6035
+ _js_wasm_trace(
6036
+ 'fcntl(%d, F_SETLK) %s lockFileByteRange returned %d for range lock %s',
6037
+ fd,
6038
+ vfsPath,
6039
+ succeeded,
6040
+ rangeLock
6041
+ );
6042
+ return succeeded ? 0 : -ERRNO_CODES.EAGAIN;
6043
+ })
6044
+ .catch((e) => {
6045
+ _js_wasm_trace(
6046
+ 'fcntl(%d, F_SETLK) %s lockFileByteRange error %s for range lock %s',
6047
+ fd,
6048
+ vfsPath,
6049
+ e,
6050
+ rangeLock
6051
+ );
6052
+ return -ERRNO_CODES.EINVAL;
6053
+ });
6054
+ }
5269
6055
 
5270
- case 13:
5271
- case 14:
5272
- // Pretend that the locking is successful. These are process-level locks,
5273
- // and Emscripten programs are a single process. If we supported linking a
5274
- // filesystem between programs, we'd need to do more here.
5275
- // See https://github.com/emscripten-core/emscripten/issues/23697
5276
- return 0;
6056
+ // @TODO: Implement waiting for lock
6057
+ case emscripten_F_SETLKW: {
6058
+ // We do not yet support the blocking form of flock().
6059
+ // We respond with EDEADLK to indicate failure
6060
+ // because it is a known errno for a failed F_SETLKW command.
6061
+ return -ERRNO_CODES.EDEADLK;
5277
6062
  }
5278
- return -28;
5279
- } catch (e) {
5280
- if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;
5281
- return -e.errno;
6063
+
6064
+ default:
6065
+ return _builtin_fcntl64(fd, cmd, varargs);
5282
6066
  }
5283
6067
  }
5284
6068
 
6069
+ ___syscall_fcntl64.isAsync = true;
6070
+
5285
6071
  function ___syscall_fdatasync(fd) {
5286
6072
  try {
5287
6073
  var stream = SYSCALLS.getStreamFromFD(fd);
@@ -5313,11 +6099,6 @@ export function init(RuntimeName, PHPLoader) {
5313
6099
  }
5314
6100
  }
5315
6101
 
5316
- var stringToUTF8 = (str, outPtr, maxBytesToWrite) =>
5317
- stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
5318
-
5319
- Module['stringToUTF8'] = stringToUTF8;
5320
-
5321
6102
  function ___syscall_getcwd(buf, size) {
5322
6103
  try {
5323
6104
  if (size === 0) return -28;
@@ -6577,17 +7358,6 @@ export function init(RuntimeName, PHPLoader) {
6577
7358
  return 0;
6578
7359
  };
6579
7360
 
6580
- function _fd_close(fd) {
6581
- try {
6582
- var stream = SYSCALLS.getStreamFromFD(fd);
6583
- FS.close(stream);
6584
- return 0;
6585
- } catch (e) {
6586
- if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;
6587
- return e.errno;
6588
- }
6589
- }
6590
-
6591
7361
  function _fd_fdstat_get(fd, pbuf) {
6592
7362
  try {
6593
7363
  var rightsBase = 0;
@@ -6875,291 +7645,104 @@ export function init(RuntimeName, PHPLoader) {
6875
7645
 
6876
7646
  var _getcontext = () => abort('missing function: ${name}');
6877
7647
 
6878
- var _getdtablesize = () => abort('missing function: ${name}');
6879
-
6880
- var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => {
6881
- var info = readSockaddr(sa, salen);
6882
- if (info.errno) {
6883
- return -6;
6884
- }
6885
- var port = info.port;
6886
- var addr = info.addr;
6887
- var overflowed = false;
6888
- if (node && nodelen) {
6889
- var lookup;
6890
- if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) {
6891
- if (flags & 8) {
6892
- return -2;
6893
- }
6894
- } else {
6895
- addr = lookup;
6896
- }
6897
- var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen);
6898
- if (numBytesWrittenExclNull + 1 >= nodelen) {
6899
- overflowed = true;
6900
- }
6901
- }
6902
- if (serv && servlen) {
6903
- port = '' + port;
6904
- var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen);
6905
- if (numBytesWrittenExclNull + 1 >= servlen) {
6906
- overflowed = true;
6907
- }
6908
- }
6909
- if (overflowed) {
6910
- // Note: even when we overflow, getnameinfo() is specced to write out the truncated results.
6911
- return -12;
6912
- }
6913
- return 0;
6914
- };
6915
-
6916
- var Protocols = {
6917
- list: [],
6918
- map: {},
6919
- };
6920
-
6921
- var _setprotoent = (stayopen) => {
6922
- // void setprotoent(int stayopen);
6923
- // Allocate and populate a protoent structure given a name, protocol number and array of aliases
6924
- function allocprotoent(name, proto, aliases) {
6925
- // write name into buffer
6926
- var nameBuf = _malloc(name.length + 1);
6927
- stringToAscii(name, nameBuf);
6928
- // write aliases into buffer
6929
- var j = 0;
6930
- var length = aliases.length;
6931
- var aliasListBuf = _malloc((length + 1) * 4);
6932
- // Use length + 1 so we have space for the terminating NULL ptr.
6933
- for (var i = 0; i < length; i++, j += 4) {
6934
- var alias = aliases[i];
6935
- var aliasBuf = _malloc(alias.length + 1);
6936
- stringToAscii(alias, aliasBuf);
6937
- HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf;
6938
- }
6939
- HEAPU32[(aliasListBuf + j) >> 2] = 0;
6940
- // Terminating NULL pointer.
6941
- // generate protoent
6942
- var pe = _malloc(12);
6943
- HEAPU32[pe >> 2] = nameBuf;
6944
- HEAPU32[(pe + 4) >> 2] = aliasListBuf;
6945
- HEAP32[(pe + 8) >> 2] = proto;
6946
- return pe;
6947
- }
6948
- // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial
6949
- // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful.
6950
- var list = Protocols.list;
6951
- var map = Protocols.map;
6952
- if (list.length === 0) {
6953
- var entry = allocprotoent('tcp', 6, ['TCP']);
6954
- list.push(entry);
6955
- map['tcp'] = map['6'] = entry;
6956
- entry = allocprotoent('udp', 17, ['UDP']);
6957
- list.push(entry);
6958
- map['udp'] = map['17'] = entry;
6959
- }
6960
- _setprotoent.index = 0;
6961
- };
6962
-
6963
- var _getprotobyname = (name) => {
6964
- // struct protoent *getprotobyname(const char *);
6965
- name = UTF8ToString(name);
6966
- _setprotoent(true);
6967
- var result = Protocols.map[name];
6968
- return result;
6969
- };
6970
-
6971
- var _getprotobynumber = (number) => {
6972
- // struct protoent *getprotobynumber(int proto);
6973
- _setprotoent(true);
6974
- var result = Protocols.map[number];
6975
- return result;
6976
- };
6977
-
6978
- var stackAlloc = (sz) => __emscripten_stack_alloc(sz);
6979
-
6980
- /** @suppress {duplicate } */ var stringToUTF8OnStack = (str) => {
6981
- var size = lengthBytesUTF8(str) + 1;
6982
- var ret = stackAlloc(size);
6983
- stringToUTF8(str, ret, size);
6984
- return ret;
6985
- };
6986
-
6987
- var allocateUTF8OnStack = stringToUTF8OnStack;
6988
-
6989
- var PHPWASM = {
6990
- init: function () {
6991
- // The /internal directory is required by the C module. It's where the
6992
- // stdout, stderr, and headers information are written for the JavaScript
6993
- // code to read later on.
6994
- FS.mkdir('/internal');
6995
- // The files from the shared directory are shared between all the
6996
- // PHP processes managed by PHPProcessManager.
6997
- FS.mkdir('/internal/shared');
6998
- // The files from the preload directory are preloaded using the
6999
- // auto_prepend_file php.ini directive.
7000
- FS.mkdir('/internal/shared/preload');
7001
- PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE
7002
- ? require('events').EventEmitter
7003
- : class EventEmitter {
7004
- constructor() {
7005
- this.listeners = {};
7006
- }
7007
- emit(eventName, data) {
7008
- if (this.listeners[eventName]) {
7009
- this.listeners[eventName].forEach(
7010
- (callback) => {
7011
- callback(data);
7012
- }
7013
- );
7014
- }
7015
- }
7016
- once(eventName, callback) {
7017
- const self = this;
7018
- function removedCallback() {
7019
- callback(...arguments);
7020
- self.removeListener(eventName, removedCallback);
7021
- }
7022
- this.on(eventName, removedCallback);
7023
- }
7024
- removeAllListeners(eventName) {
7025
- if (eventName) {
7026
- delete this.listeners[eventName];
7027
- } else {
7028
- this.listeners = {};
7029
- }
7030
- }
7031
- removeListener(eventName, callback) {
7032
- if (this.listeners[eventName]) {
7033
- const idx =
7034
- this.listeners[eventName].indexOf(callback);
7035
- if (idx !== -1) {
7036
- this.listeners[eventName].splice(idx, 1);
7037
- }
7038
- }
7039
- }
7040
- };
7041
- PHPWASM.child_proc_by_fd = {};
7042
- PHPWASM.child_proc_by_pid = {};
7043
- PHPWASM.input_devices = {};
7044
- },
7045
- getAllWebSockets: function (sock) {
7046
- const webSockets = new Set();
7047
- if (sock.server) {
7048
- sock.server.clients.forEach((ws) => {
7049
- webSockets.add(ws);
7050
- });
7051
- }
7052
- for (const peer of PHPWASM.getAllPeers(sock)) {
7053
- webSockets.add(peer.socket);
7054
- }
7055
- return Array.from(webSockets);
7056
- },
7057
- getAllPeers: function (sock) {
7058
- const peers = new Set();
7059
- if (sock.server) {
7060
- sock.pending
7061
- .filter((pending) => pending.peers)
7062
- .forEach((pending) => {
7063
- for (const peer of Object.values(pending.peers)) {
7064
- peers.add(peer);
7065
- }
7066
- });
7067
- }
7068
- if (sock.peers) {
7069
- for (const peer of Object.values(sock.peers)) {
7070
- peers.add(peer);
7648
+ var _getdtablesize = () => abort('missing function: ${name}');
7649
+
7650
+ var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => {
7651
+ var info = readSockaddr(sa, salen);
7652
+ if (info.errno) {
7653
+ return -6;
7654
+ }
7655
+ var port = info.port;
7656
+ var addr = info.addr;
7657
+ var overflowed = false;
7658
+ if (node && nodelen) {
7659
+ var lookup;
7660
+ if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) {
7661
+ if (flags & 8) {
7662
+ return -2;
7071
7663
  }
7664
+ } else {
7665
+ addr = lookup;
7072
7666
  }
7073
- return Array.from(peers);
7074
- },
7075
- awaitData: function (ws) {
7076
- return PHPWASM.awaitEvent(ws, 'message');
7077
- },
7078
- awaitConnection: function (ws) {
7079
- if (ws.OPEN === ws.readyState) {
7080
- return [Promise.resolve(), PHPWASM.noop];
7081
- }
7082
- return PHPWASM.awaitEvent(ws, 'open');
7083
- },
7084
- awaitClose: function (ws) {
7085
- if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
7086
- return [Promise.resolve(), PHPWASM.noop];
7087
- }
7088
- return PHPWASM.awaitEvent(ws, 'close');
7089
- },
7090
- awaitError: function (ws) {
7091
- if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
7092
- return [Promise.resolve(), PHPWASM.noop];
7093
- }
7094
- return PHPWASM.awaitEvent(ws, 'error');
7095
- },
7096
- awaitEvent: function (ws, event) {
7097
- let resolve;
7098
- const listener = () => {
7099
- resolve();
7100
- };
7101
- const promise = new Promise(function (_resolve) {
7102
- resolve = _resolve;
7103
- ws.once(event, listener);
7104
- });
7105
- const cancel = () => {
7106
- ws.removeListener(event, listener);
7107
- // Rejecting the promises bubbles up and kills the entire
7108
- // node process. Let's resolve them on the next tick instead
7109
- // to give the caller some space to unbind any handlers.
7110
- setTimeout(resolve);
7111
- };
7112
- return [promise, cancel];
7113
- },
7114
- noop: function () {},
7115
- spawnProcess: function (command, args, options) {
7116
- if (Module['spawnProcess']) {
7117
- const spawnedPromise = Module['spawnProcess'](
7118
- command,
7119
- args,
7120
- options
7121
- );
7122
- return Promise.resolve(spawnedPromise).then(function (spawned) {
7123
- if (!spawned || !spawned.on) {
7124
- throw new Error(
7125
- 'spawnProcess() must return an EventEmitter but returned a different type.'
7126
- );
7127
- }
7128
- return spawned;
7129
- });
7130
- }
7131
- if (ENVIRONMENT_IS_NODE) {
7132
- return require('child_process').spawn(command, args, {
7133
- ...options,
7134
- shell: true,
7135
- stdio: ['pipe', 'pipe', 'pipe'],
7136
- timeout: 100,
7137
- });
7667
+ var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen);
7668
+ if (numBytesWrittenExclNull + 1 >= nodelen) {
7669
+ overflowed = true;
7138
7670
  }
7139
- const e = new Error(
7140
- 'popen(), proc_open() etc. are unsupported in the browser. Call php.setSpawnHandler() ' +
7141
- 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' +
7142
- 'and similar functions via php.ini.'
7143
- );
7144
- e.code = 'SPAWN_UNSUPPORTED';
7145
- throw e;
7146
- },
7147
- shutdownSocket: function (socketd, how) {
7148
- // This implementation only supports websockets at the moment
7149
- const sock = getSocketFromFD(socketd);
7150
- const peer = Object.values(sock.peers)[0];
7151
- if (!peer) {
7152
- return -1;
7671
+ }
7672
+ if (serv && servlen) {
7673
+ port = '' + port;
7674
+ var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen);
7675
+ if (numBytesWrittenExclNull + 1 >= servlen) {
7676
+ overflowed = true;
7153
7677
  }
7154
- try {
7155
- peer.socket.close();
7156
- SOCKFS.websocket_sock_ops.removePeer(sock, peer);
7157
- return 0;
7158
- } catch (e) {
7159
- console.log('Socket shutdown error', e);
7160
- return -1;
7678
+ }
7679
+ if (overflowed) {
7680
+ // Note: even when we overflow, getnameinfo() is specced to write out the truncated results.
7681
+ return -12;
7682
+ }
7683
+ return 0;
7684
+ };
7685
+
7686
+ var Protocols = {
7687
+ list: [],
7688
+ map: {},
7689
+ };
7690
+
7691
+ var _setprotoent = (stayopen) => {
7692
+ // void setprotoent(int stayopen);
7693
+ // Allocate and populate a protoent structure given a name, protocol number and array of aliases
7694
+ function allocprotoent(name, proto, aliases) {
7695
+ // write name into buffer
7696
+ var nameBuf = _malloc(name.length + 1);
7697
+ stringToAscii(name, nameBuf);
7698
+ // write aliases into buffer
7699
+ var j = 0;
7700
+ var length = aliases.length;
7701
+ var aliasListBuf = _malloc((length + 1) * 4);
7702
+ // Use length + 1 so we have space for the terminating NULL ptr.
7703
+ for (var i = 0; i < length; i++, j += 4) {
7704
+ var alias = aliases[i];
7705
+ var aliasBuf = _malloc(alias.length + 1);
7706
+ stringToAscii(alias, aliasBuf);
7707
+ HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf;
7161
7708
  }
7162
- },
7709
+ HEAPU32[(aliasListBuf + j) >> 2] = 0;
7710
+ // Terminating NULL pointer.
7711
+ // generate protoent
7712
+ var pe = _malloc(12);
7713
+ HEAPU32[pe >> 2] = nameBuf;
7714
+ HEAPU32[(pe + 4) >> 2] = aliasListBuf;
7715
+ HEAP32[(pe + 8) >> 2] = proto;
7716
+ return pe;
7717
+ }
7718
+ // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial
7719
+ // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful.
7720
+ var list = Protocols.list;
7721
+ var map = Protocols.map;
7722
+ if (list.length === 0) {
7723
+ var entry = allocprotoent('tcp', 6, ['TCP']);
7724
+ list.push(entry);
7725
+ map['tcp'] = map['6'] = entry;
7726
+ entry = allocprotoent('udp', 17, ['UDP']);
7727
+ list.push(entry);
7728
+ map['udp'] = map['17'] = entry;
7729
+ }
7730
+ _setprotoent.index = 0;
7731
+ };
7732
+
7733
+ var _getprotobyname = (name) => {
7734
+ // struct protoent *getprotobyname(const char *);
7735
+ name = UTF8ToString(name);
7736
+ _setprotoent(true);
7737
+ var result = Protocols.map[name];
7738
+ return result;
7739
+ };
7740
+
7741
+ var _getprotobynumber = (number) => {
7742
+ // struct protoent *getprotobynumber(int proto);
7743
+ _setprotoent(true);
7744
+ var result = Protocols.map[number];
7745
+ return result;
7163
7746
  };
7164
7747
 
7165
7748
  function _js_create_input_device(deviceId) {
@@ -7197,6 +7780,98 @@ export function init(RuntimeName, PHPLoader) {
7197
7780
  return allocateUTF8OnStack(devicePath);
7198
7781
  }
7199
7782
 
7783
+ async function _js_flock(fd, op) {
7784
+ _js_wasm_trace('js_flock(%d, %d)', fd, op);
7785
+ // Emscripten does not expose these constants to JS, so we hardcode them here.
7786
+ // Based on
7787
+ // https://github.com/emscripten-core/emscripten/blob/76860cc47cef67f5712a7a03a247bc1baabf7ba4/system/lib/libc/musl/include/sys/file.h#L7-L10
7788
+ const emscripten_LOCK_SH = 1;
7789
+ const emscripten_LOCK_EX = 2;
7790
+ const emscripten_LOCK_NB = 4;
7791
+ const emscripten_LOCK_UN = 8;
7792
+ const flockToLockOpType = {
7793
+ [emscripten_LOCK_SH]: 'shared',
7794
+ [emscripten_LOCK_EX]: 'exclusive',
7795
+ [emscripten_LOCK_UN]: 'unlocked',
7796
+ };
7797
+ let vfsPath;
7798
+ let errno;
7799
+ [vfsPath, errno] = locking.get_vfs_path_from_fd(fd);
7800
+ if (errno !== 0) {
7801
+ _js_wasm_trace(
7802
+ 'js_flock(%d, %d) get_vfs_path_from_fd errno %d',
7803
+ fd,
7804
+ op,
7805
+ vfsPath,
7806
+ errno
7807
+ );
7808
+ return -errno;
7809
+ }
7810
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
7811
+ _js_wasm_trace(
7812
+ 'flock(%d, %d) locking is not implemented for non-NodeFS path %s',
7813
+ fd,
7814
+ op,
7815
+ vfsPath
7816
+ );
7817
+ // If not a NodeFS path, we can't lock it.
7818
+ // Default to succeeding as Emscripten does.
7819
+ return 0;
7820
+ }
7821
+ errno = locking.check_lock_params(fd, op);
7822
+ if (errno !== 0) {
7823
+ _js_wasm_trace(
7824
+ 'js_flock(%d, %d) check_lock_params errno %d',
7825
+ fd,
7826
+ op,
7827
+ errno
7828
+ );
7829
+ return -errno;
7830
+ }
7831
+ // @TODO: Consider supporting blocking mode of flock()
7832
+ if (op & (emscripten_LOCK_NB === 0)) {
7833
+ _js_wasm_trace(
7834
+ 'js_flock(%d, %d) blocking mode of flock() is not implemented',
7835
+ fd,
7836
+ op
7837
+ );
7838
+ // We do not yet support the blocking form of flock().
7839
+ // We respond with EINVAL to indicate failure
7840
+ // because it is a known errno for a failed blocking flock().
7841
+ return -ERRNO_CODES.EINVAL;
7842
+ }
7843
+ const maskedOp =
7844
+ op & (emscripten_LOCK_SH | emscripten_LOCK_EX | emscripten_LOCK_UN);
7845
+ const lockOpType = flockToLockOpType[maskedOp];
7846
+ if (lockOpType === undefined) {
7847
+ _js_wasm_trace(
7848
+ 'js_flock(%d, %d) invalid flock() operation',
7849
+ fd,
7850
+ op
7851
+ );
7852
+ return -ERRNO_CODES.EINVAL;
7853
+ }
7854
+ const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
7855
+ const obtainedLock = await PHPLoader.fileLockManager.lockWholeFile(
7856
+ nativeFilePath,
7857
+ {
7858
+ type: lockOpType,
7859
+ pid: PHPLoader.processId,
7860
+ fd,
7861
+ }
7862
+ );
7863
+ _js_wasm_trace(
7864
+ 'js_flock(%d, %d) lockWholeFile %s returned %d',
7865
+ fd,
7866
+ op,
7867
+ vfsPath,
7868
+ obtainedLock
7869
+ );
7870
+ return obtainedLock ? 0 : -ERRNO_CODES.EWOULDBLOCK;
7871
+ }
7872
+
7873
+ _js_flock.isAsync = true;
7874
+
7200
7875
  function _js_open_process(
7201
7876
  command,
7202
7877
  argsPtr,
@@ -7222,7 +7897,7 @@ export function init(RuntimeName, PHPLoader) {
7222
7897
  argsArray.push(UTF8ToString(HEAPU32[charPointer >> 2]));
7223
7898
  }
7224
7899
  }
7225
- const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : null;
7900
+ const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : FS.cwd();
7226
7901
  let envObject = null;
7227
7902
  if (envLength) {
7228
7903
  envObject = {};
@@ -7295,6 +7970,15 @@ export function init(RuntimeName, PHPLoader) {
7295
7970
  PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo;
7296
7971
  PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo;
7297
7972
  cp.on('exit', function (code) {
7973
+ for (const fd of [
7974
+ // The child process exited. Let's clean up its output streams:
7975
+ ProcInfo.stdoutChildFd,
7976
+ ProcInfo.stderrChildFd,
7977
+ ]) {
7978
+ if (FS.streams[fd] && !FS.isClosed(FS.streams[fd])) {
7979
+ FS.close(FS.streams[fd]);
7980
+ }
7981
+ }
7298
7982
  ProcInfo.exitCode = code;
7299
7983
  ProcInfo.exited = true;
7300
7984
  // Emit events for the wasm_poll_socket function.
@@ -7345,12 +8029,52 @@ export function init(RuntimeName, PHPLoader) {
7345
8029
  * listen to the 'exit' event.
7346
8030
  */ try {
7347
8031
  await new Promise((resolve, reject) => {
7348
- cp.on('spawn', resolve);
7349
- cp.on('error', reject);
8032
+ /**
8033
+ * There was no `await` between the `spawnProcess` call
8034
+ * and the `await` below so the process haven't had a chance
8035
+ * to run any of the exit-related callbacks yet.
8036
+ *
8037
+ * Good.
8038
+ *
8039
+ * Let's listen to all the lifecycle events and resolve
8040
+ * the promise when the process starts or immediately crashes.
8041
+ */ let resolved = false;
8042
+ cp.on('spawn', () => {
8043
+ if (resolved) return;
8044
+ resolved = true;
8045
+ resolve();
8046
+ });
8047
+ cp.on('error', (e) => {
8048
+ if (resolved) return;
8049
+ resolved = true;
8050
+ reject(e);
8051
+ });
8052
+ cp.on('exit', function (code) {
8053
+ if (resolved) return;
8054
+ resolved = true;
8055
+ if (code === 0) {
8056
+ resolve();
8057
+ } else {
8058
+ reject(
8059
+ new Error(`Process exited with code ${code}`)
8060
+ );
8061
+ }
8062
+ });
8063
+ /**
8064
+ * If the process haven't even started after 5 seconds, something
8065
+ * is wrong. Perhaps we're missing an event listener, or perhaps
8066
+ * the `spawnProcess` implementation failed to dispatch the relevant
8067
+ * event. Either way, let's crash to avoid blocking the proc_open()
8068
+ * call indefinitely.
8069
+ */ setTimeout(() => {
8070
+ if (resolved) return;
8071
+ resolved = true;
8072
+ reject(new Error('Process timed out'));
8073
+ }, 5e3);
7350
8074
  });
7351
8075
  } catch (e) {
7352
8076
  console.error(e);
7353
- wakeUp(1);
8077
+ wakeUp(ProcInfo.pid);
7354
8078
  return;
7355
8079
  }
7356
8080
  // Now we want to pass data from the STDIN source supplied by PHP
@@ -7420,6 +8144,21 @@ export function init(RuntimeName, PHPLoader) {
7420
8144
  return 0;
7421
8145
  }
7422
8146
 
8147
+ var _js_release_file_locks = async function js_release_file_locks() {
8148
+ _js_wasm_trace('js_release_file_locks()');
8149
+ const pid = PHPLoader.processId;
8150
+ return await PHPLoader.fileLockManager
8151
+ .releaseLocksForProcess(pid)
8152
+ .then(() => {
8153
+ _js_wasm_trace('js_release_file_locks succeeded');
8154
+ })
8155
+ .catch((e) => {
8156
+ _js_wasm_trace('js_release_file_locks error %s', e);
8157
+ });
8158
+ };
8159
+
8160
+ _js_release_file_locks.isAsync = true;
8161
+
7423
8162
  function _js_waitpid(pid, exitCodePtr) {
7424
8163
  if (!PHPWASM.child_proc_by_pid[pid]) {
7425
8164
  return -1;
@@ -8064,14 +8803,7 @@ export function init(RuntimeName, PHPLoader) {
8064
8803
  const POLLNVAL = 32;
8065
8804
  return returnCallback((wakeUp) => {
8066
8805
  const polls = [];
8067
- if (socketd in PHPWASM.child_proc_by_fd) {
8068
- const procInfo = PHPWASM.child_proc_by_fd[socketd];
8069
- if (procInfo.exited) {
8070
- wakeUp(0);
8071
- return;
8072
- }
8073
- polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data'));
8074
- } else if (FS.isSocket(FS.getStream(socketd)?.node.mode)) {
8806
+ if (FS.isSocket(FS.getStream(socketd)?.node.mode)) {
8075
8807
  const sock = getSocketFromFD(socketd);
8076
8808
  if (!sock) {
8077
8809
  wakeUp(0);
@@ -8105,7 +8837,12 @@ export function init(RuntimeName, PHPLoader) {
8105
8837
  polls.push(PHPWASM.awaitConnection(ws));
8106
8838
  lookingFor.add('POLLOUT');
8107
8839
  }
8108
- if (events & POLLHUP) {
8840
+ if (
8841
+ events & POLLHUP ||
8842
+ events & POLLIN ||
8843
+ events & POLLOUT ||
8844
+ events & POLLERR
8845
+ ) {
8109
8846
  polls.push(PHPWASM.awaitClose(ws));
8110
8847
  lookingFor.add('POLLHUP');
8111
8848
  }
@@ -8114,6 +8851,13 @@ export function init(RuntimeName, PHPLoader) {
8114
8851
  lookingFor.add('POLLERR');
8115
8852
  }
8116
8853
  }
8854
+ } else if (socketd in PHPWASM.child_proc_by_fd) {
8855
+ const procInfo = PHPWASM.child_proc_by_fd[socketd];
8856
+ if (procInfo.exited) {
8857
+ wakeUp(0);
8858
+ return;
8859
+ }
8860
+ polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data'));
8117
8861
  } else {
8118
8862
  setTimeout(function () {
8119
8863
  wakeUp(1);
@@ -8348,9 +9092,13 @@ export function init(RuntimeName, PHPLoader) {
8348
9092
  /** @export */ getprotobyname: _getprotobyname,
8349
9093
  /** @export */ getprotobynumber: _getprotobynumber,
8350
9094
  /** @export */ js_create_input_device: _js_create_input_device,
9095
+ /** @export */ js_flock: _js_flock,
9096
+ /** @export */ js_getpid: _js_getpid,
8351
9097
  /** @export */ js_open_process: _js_open_process,
8352
9098
  /** @export */ js_process_status: _js_process_status,
9099
+ /** @export */ js_release_file_locks: _js_release_file_locks,
8353
9100
  /** @export */ js_waitpid: _js_waitpid,
9101
+ /** @export */ js_wasm_trace: _js_wasm_trace,
8354
9102
  /** @export */ makecontext: _makecontext,
8355
9103
  /** @export */ proc_exit: _proc_exit,
8356
9104
  /** @export */ strptime: _strptime,
@@ -8369,6 +9117,9 @@ export function init(RuntimeName, PHPLoader) {
8369
9117
 
8370
9118
  var _malloc = (a0) => (_malloc = wasmExports['malloc'])(a0);
8371
9119
 
9120
+ var _getpid = (Module['_getpid'] = () =>
9121
+ (_getpid = Module['_getpid'] = wasmExports['getpid'])());
9122
+
8372
9123
  var _wasm_popen = (Module['_wasm_popen'] = (a0, a1) =>
8373
9124
  (_wasm_popen = Module['_wasm_popen'] = wasmExports['wasm_popen'])(
8374
9125
  a0,
@@ -8394,6 +9145,9 @@ export function init(RuntimeName, PHPLoader) {
8394
9145
 
8395
9146
  var _fflush = (a0) => (_fflush = wasmExports['fflush'])(a0);
8396
9147
 
9148
+ var _flock = (Module['_flock'] = (a0, a1) =>
9149
+ (_flock = Module['_flock'] = wasmExports['flock'])(a0, a1));
9150
+
8397
9151
  var _wasm_read = (Module['_wasm_read'] = (a0, a1, a2) =>
8398
9152
  (_wasm_read = Module['_wasm_read'] = wasmExports['wasm_read'])(
8399
9153
  a0,
@@ -8405,13 +9159,6 @@ export function init(RuntimeName, PHPLoader) {
8405
9159
  (___wrap_select = Module['___wrap_select'] =
8406
9160
  wasmExports['__wrap_select'])(a0, a1, a2, a3, a4));
8407
9161
 
8408
- var _wasm_add_cli_arg = (Module['_wasm_add_cli_arg'] = (a0) =>
8409
- (_wasm_add_cli_arg = Module['_wasm_add_cli_arg'] =
8410
- wasmExports['wasm_add_cli_arg'])(a0));
8411
-
8412
- var _run_cli = (Module['_run_cli'] = () =>
8413
- (_run_cli = Module['_run_cli'] = wasmExports['run_cli'])());
8414
-
8415
9162
  var _wasm_set_sapi_name = (Module['_wasm_set_sapi_name'] = (a0) =>
8416
9163
  (_wasm_set_sapi_name = Module['_wasm_set_sapi_name'] =
8417
9164
  wasmExports['wasm_set_sapi_name'])(a0));
@@ -8420,6 +9167,13 @@ export function init(RuntimeName, PHPLoader) {
8420
9167
  (_wasm_set_phpini_path = Module['_wasm_set_phpini_path'] =
8421
9168
  wasmExports['wasm_set_phpini_path'])(a0));
8422
9169
 
9170
+ var _wasm_add_cli_arg = (Module['_wasm_add_cli_arg'] = (a0) =>
9171
+ (_wasm_add_cli_arg = Module['_wasm_add_cli_arg'] =
9172
+ wasmExports['wasm_add_cli_arg'])(a0));
9173
+
9174
+ var _run_cli = (Module['_run_cli'] = () =>
9175
+ (_run_cli = Module['_run_cli'] = wasmExports['run_cli'])());
9176
+
8423
9177
  var _wasm_add_SERVER_entry = (Module['_wasm_add_SERVER_entry'] = (a0, a1) =>
8424
9178
  (_wasm_add_SERVER_entry = Module['_wasm_add_SERVER_entry'] =
8425
9179
  wasmExports['wasm_add_SERVER_entry'])(a0, a1));
@@ -8492,6 +9246,16 @@ export function init(RuntimeName, PHPLoader) {
8492
9246
  var _wasm_free = (Module['_wasm_free'] = (a0) =>
8493
9247
  (_wasm_free = Module['_wasm_free'] = wasmExports['wasm_free'])(a0));
8494
9248
 
9249
+ var _wasm_get_end_offset = (Module['_wasm_get_end_offset'] = (a0) =>
9250
+ (_wasm_get_end_offset = Module['_wasm_get_end_offset'] =
9251
+ wasmExports['wasm_get_end_offset'])(a0));
9252
+
9253
+ var _wasm_trace = (Module['_wasm_trace'] = (a0, a1) =>
9254
+ (_wasm_trace = Module['_wasm_trace'] = wasmExports['wasm_trace'])(
9255
+ a0,
9256
+ a1
9257
+ ));
9258
+
8495
9259
  var ___funcs_on_exit = () =>
8496
9260
  (___funcs_on_exit = wasmExports['__funcs_on_exit'])();
8497
9261
 
@@ -8639,6 +9403,32 @@ export function init(RuntimeName, PHPLoader) {
8639
9403
  PHPLoader['free'] =
8640
9404
  typeof _free === 'function' ? _free : PHPLoader['_wasm_free'];
8641
9405
 
9406
+ if (typeof NODEFS === 'object') {
9407
+ // We override NODEFS.createNode() to add an `isSharedFS` flag to all NODEFS
9408
+ // nodes. This way we can tell whether file-locking is needed and possible
9409
+ // for an FS node, even if wrapped with PROXYFS.
9410
+ const originalCreateNode = NODEFS.createNode;
9411
+ NODEFS.createNode = function createNodeWithSharedFlag() {
9412
+ const node = originalCreateNode.apply(NODEFS, arguments);
9413
+ node.isSharedFS = true;
9414
+ return node;
9415
+ };
9416
+
9417
+ var originalHashAddNode = FS.hashAddNode;
9418
+ FS.hashAddNode = function hashAddNodeIfNotSharedFS(node) {
9419
+ if (
9420
+ typeof locking === 'object' &&
9421
+ locking?.is_shared_fs_node(node)
9422
+ ) {
9423
+ // Avoid caching shared VFS nodes so multiple instances
9424
+ // can access the same underlying filesystem without
9425
+ // conflicting caches.
9426
+ return;
9427
+ }
9428
+ return originalHashAddNode.apply(FS, arguments);
9429
+ };
9430
+ }
9431
+
8642
9432
  return PHPLoader;
8643
9433
 
8644
9434
  // Close the opening bracket from esm-prefix.js: