@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_7_2.js CHANGED
@@ -8,7 +8,7 @@ import path from 'path';
8
8
 
9
9
  const dependencyFilename = path.join(__dirname, '7_2_34', 'php_7_2.wasm');
10
10
  export { dependencyFilename };
11
- export const dependenciesTotalSize = 17819046;
11
+ export const dependenciesTotalSize = 17821497;
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
@@ -5209,7 +5209,360 @@ export function init(RuntimeName, PHPLoader) {
5209
5209
 
5210
5210
  var syscallGetVarargP = syscallGetVarargI;
5211
5211
 
5212
- function ___syscall_fcntl64(fd, cmd, varargs) {
5212
+ var stringToUTF8 = (str, outPtr, maxBytesToWrite) =>
5213
+ stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
5214
+
5215
+ Module['stringToUTF8'] = stringToUTF8;
5216
+
5217
+ var stackAlloc = (sz) => __emscripten_stack_alloc(sz);
5218
+
5219
+ /** @suppress {duplicate } */ var stringToUTF8OnStack = (str) => {
5220
+ var size = lengthBytesUTF8(str) + 1;
5221
+ var ret = stackAlloc(size);
5222
+ stringToUTF8(str, ret, size);
5223
+ return ret;
5224
+ };
5225
+
5226
+ var allocateUTF8OnStack = stringToUTF8OnStack;
5227
+
5228
+ var PHPWASM = {
5229
+ init: function () {
5230
+ // The /internal directory is required by the C module. It's where the
5231
+ // stdout, stderr, and headers information are written for the JavaScript
5232
+ // code to read later on.
5233
+ FS.mkdir('/internal');
5234
+ // The files from the shared directory are shared between all the
5235
+ // PHP processes managed by PHPProcessManager.
5236
+ FS.mkdir('/internal/shared');
5237
+ // The files from the preload directory are preloaded using the
5238
+ // auto_prepend_file php.ini directive.
5239
+ FS.mkdir('/internal/shared/preload');
5240
+ // Create stdout and stderr devices. We can't just use Emscripten's
5241
+ // default stdout and stderr devices because they stop processing data
5242
+ // on the first null byte. However, when dealing with binary data,
5243
+ // null bytes are valid and common.
5244
+ FS.registerDevice(FS.makedev(64, 0), {
5245
+ open: () => {},
5246
+ close: () => {},
5247
+ read: () => 0,
5248
+ write: (stream, buffer, offset, length, pos) => {
5249
+ const chunk = buffer.subarray(offset, offset + length);
5250
+ PHPWASM.onStdout(chunk);
5251
+ return length;
5252
+ },
5253
+ });
5254
+ FS.mkdev('/internal/stdout', FS.makedev(64, 0));
5255
+ FS.registerDevice(FS.makedev(63, 0), {
5256
+ open: () => {},
5257
+ close: () => {},
5258
+ read: () => 0,
5259
+ write: (stream, buffer, offset, length, pos) => {
5260
+ const chunk = buffer.subarray(offset, offset + length);
5261
+ PHPWASM.onStderr(chunk);
5262
+ return length;
5263
+ },
5264
+ });
5265
+ FS.mkdev('/internal/stderr', FS.makedev(63, 0));
5266
+ FS.registerDevice(FS.makedev(62, 0), {
5267
+ open: () => {},
5268
+ close: () => {},
5269
+ read: () => 0,
5270
+ write: (stream, buffer, offset, length, pos) => {
5271
+ const chunk = buffer.subarray(offset, offset + length);
5272
+ PHPWASM.onHeaders(chunk);
5273
+ return length;
5274
+ },
5275
+ });
5276
+ FS.mkdev('/internal/headers', FS.makedev(62, 0));
5277
+ // Handle events.
5278
+ PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE
5279
+ ? require('events').EventEmitter
5280
+ : class EventEmitter {
5281
+ constructor() {
5282
+ this.listeners = {};
5283
+ }
5284
+ emit(eventName, data) {
5285
+ if (this.listeners[eventName]) {
5286
+ this.listeners[eventName].forEach(
5287
+ (callback) => {
5288
+ callback(data);
5289
+ }
5290
+ );
5291
+ }
5292
+ }
5293
+ once(eventName, callback) {
5294
+ const self = this;
5295
+ function removedCallback() {
5296
+ callback(...arguments);
5297
+ self.removeListener(eventName, removedCallback);
5298
+ }
5299
+ this.on(eventName, removedCallback);
5300
+ }
5301
+ removeAllListeners(eventName) {
5302
+ if (eventName) {
5303
+ delete this.listeners[eventName];
5304
+ } else {
5305
+ this.listeners = {};
5306
+ }
5307
+ }
5308
+ removeListener(eventName, callback) {
5309
+ if (this.listeners[eventName]) {
5310
+ const idx =
5311
+ this.listeners[eventName].indexOf(callback);
5312
+ if (idx !== -1) {
5313
+ this.listeners[eventName].splice(idx, 1);
5314
+ }
5315
+ }
5316
+ }
5317
+ };
5318
+ // Clean up the fd -> childProcess mapping when the fd is closed:
5319
+ const originalClose = FS.close;
5320
+ FS.close = function (stream) {
5321
+ originalClose(stream);
5322
+ delete PHPWASM.child_proc_by_fd[stream.fd];
5323
+ };
5324
+ PHPWASM.child_proc_by_fd = {};
5325
+ PHPWASM.child_proc_by_pid = {};
5326
+ PHPWASM.input_devices = {};
5327
+ const originalWrite = TTY.stream_ops.write;
5328
+ TTY.stream_ops.write = function (stream, ...rest) {
5329
+ const retval = originalWrite(stream, ...rest);
5330
+ // Implicit flush since PHP's fflush() doesn't seem to trigger the fsync event
5331
+ // @TODO: Fix this at the wasm level
5332
+ stream.tty.ops.fsync(stream.tty);
5333
+ return retval;
5334
+ };
5335
+ const originalPutChar = TTY.stream_ops.put_char;
5336
+ TTY.stream_ops.put_char = function (tty, val) {
5337
+ /**
5338
+ * Buffer newlines that Emscripten normally ignores.
5339
+ *
5340
+ * Emscripten doesn't do it by default because its default
5341
+ * print function is console.log that implicitly adds a newline. We are overwriting
5342
+ * it with an environment-specific function that outputs exaclty what it was given,
5343
+ * e.g. in Node.js it's process.stdout.write(). Therefore, we need to mak sure
5344
+ * all the newlines make it to the output buffer.
5345
+ */ if (val === 10) tty.output.push(val);
5346
+ return originalPutChar(tty, val);
5347
+ };
5348
+ },
5349
+ onHeaders: function (chunk) {
5350
+ if (Module['onHeaders']) {
5351
+ Module['onHeaders'](chunk);
5352
+ return;
5353
+ }
5354
+ console.log('headers', {
5355
+ chunk,
5356
+ });
5357
+ },
5358
+ onStdout: function (chunk) {
5359
+ if (Module['onStdout']) {
5360
+ Module['onStdout'](chunk);
5361
+ return;
5362
+ }
5363
+ if (ENVIRONMENT_IS_NODE) {
5364
+ process.stdout.write(chunk);
5365
+ } else {
5366
+ console.log('stdout', {
5367
+ chunk,
5368
+ });
5369
+ }
5370
+ },
5371
+ onStderr: function (chunk) {
5372
+ if (Module['onStderr']) {
5373
+ Module['onStderr'](chunk);
5374
+ return;
5375
+ }
5376
+ if (ENVIRONMENT_IS_NODE) {
5377
+ process.stderr.write(chunk);
5378
+ } else {
5379
+ console.warn('stderr', {
5380
+ chunk,
5381
+ });
5382
+ }
5383
+ },
5384
+ getAllWebSockets: function (sock) {
5385
+ const webSockets = new Set();
5386
+ if (sock.server) {
5387
+ sock.server.clients.forEach((ws) => {
5388
+ webSockets.add(ws);
5389
+ });
5390
+ }
5391
+ for (const peer of PHPWASM.getAllPeers(sock)) {
5392
+ webSockets.add(peer.socket);
5393
+ }
5394
+ return Array.from(webSockets);
5395
+ },
5396
+ getAllPeers: function (sock) {
5397
+ const peers = new Set();
5398
+ if (sock.server) {
5399
+ sock.pending
5400
+ .filter((pending) => pending.peers)
5401
+ .forEach((pending) => {
5402
+ for (const peer of Object.values(pending.peers)) {
5403
+ peers.add(peer);
5404
+ }
5405
+ });
5406
+ }
5407
+ if (sock.peers) {
5408
+ for (const peer of Object.values(sock.peers)) {
5409
+ peers.add(peer);
5410
+ }
5411
+ }
5412
+ return Array.from(peers);
5413
+ },
5414
+ awaitData: function (ws) {
5415
+ return PHPWASM.awaitEvent(ws, 'message');
5416
+ },
5417
+ awaitConnection: function (ws) {
5418
+ if (ws.OPEN === ws.readyState) {
5419
+ return [Promise.resolve(), PHPWASM.noop];
5420
+ }
5421
+ return PHPWASM.awaitEvent(ws, 'open');
5422
+ },
5423
+ awaitClose: function (ws) {
5424
+ if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
5425
+ return [Promise.resolve(), PHPWASM.noop];
5426
+ }
5427
+ return PHPWASM.awaitEvent(ws, 'close');
5428
+ },
5429
+ awaitError: function (ws) {
5430
+ if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
5431
+ return [Promise.resolve(), PHPWASM.noop];
5432
+ }
5433
+ return PHPWASM.awaitEvent(ws, 'error');
5434
+ },
5435
+ awaitEvent: function (ws, event) {
5436
+ let resolve;
5437
+ const listener = () => {
5438
+ resolve();
5439
+ };
5440
+ const promise = new Promise(function (_resolve) {
5441
+ resolve = _resolve;
5442
+ ws.once(event, listener);
5443
+ });
5444
+ const cancel = () => {
5445
+ ws.removeListener(event, listener);
5446
+ // Rejecting the promises bubbles up and kills the entire
5447
+ // node process. Let's resolve them on the next tick instead
5448
+ // to give the caller some space to unbind any handlers.
5449
+ setTimeout(resolve);
5450
+ };
5451
+ return [promise, cancel];
5452
+ },
5453
+ noop: function () {},
5454
+ spawnProcess: function (command, args, options) {
5455
+ if (Module['spawnProcess']) {
5456
+ const spawnedPromise = Module['spawnProcess'](
5457
+ command,
5458
+ args,
5459
+ options
5460
+ );
5461
+ return Promise.resolve(spawnedPromise).then(function (spawned) {
5462
+ if (!spawned || !spawned.on) {
5463
+ throw new Error(
5464
+ 'spawnProcess() must return an EventEmitter but returned a different type.'
5465
+ );
5466
+ }
5467
+ return spawned;
5468
+ });
5469
+ }
5470
+ if (ENVIRONMENT_IS_NODE) {
5471
+ return require('child_process').spawn(command, args, {
5472
+ ...options,
5473
+ shell: true,
5474
+ stdio: ['pipe', 'pipe', 'pipe'],
5475
+ timeout: 100,
5476
+ });
5477
+ }
5478
+ const e = new Error(
5479
+ 'popen(), proc_open() etc. are unsupported in the browser. Call php.setSpawnHandler() ' +
5480
+ 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' +
5481
+ 'and similar functions via php.ini.'
5482
+ );
5483
+ e.code = 'SPAWN_UNSUPPORTED';
5484
+ throw e;
5485
+ },
5486
+ shutdownSocket: function (socketd, how) {
5487
+ // This implementation only supports websockets at the moment
5488
+ const sock = getSocketFromFD(socketd);
5489
+ const peer = Object.values(sock.peers)[0];
5490
+ if (!peer) {
5491
+ return -1;
5492
+ }
5493
+ try {
5494
+ peer.socket.close();
5495
+ SOCKFS.websocket_sock_ops.removePeer(sock, peer);
5496
+ return 0;
5497
+ } catch (e) {
5498
+ console.log('Socket shutdown error', e);
5499
+ return -1;
5500
+ }
5501
+ },
5502
+ };
5503
+
5504
+ function _js_getpid() {
5505
+ return PHPLoader.processId ?? 42;
5506
+ }
5507
+
5508
+ function _js_wasm_trace(format, ...args) {
5509
+ if (PHPLoader.trace instanceof Function) {
5510
+ PHPLoader.trace(_js_getpid(), format, ...args);
5511
+ }
5512
+ }
5513
+
5514
+ function _fd_close(fd) {
5515
+ _js_wasm_trace('fd_close(%d)', fd);
5516
+ const [vfsPath, pathResolutionErrno] = locking.get_vfs_path_from_fd(fd);
5517
+ if (pathResolutionErrno !== 0) {
5518
+ _js_wasm_trace(
5519
+ 'fd_close(%d) get_vfs_path_from_fd error %d',
5520
+ fd,
5521
+ pathResolutionErrno
5522
+ );
5523
+ return -ERRNO_CODES.EBADF;
5524
+ }
5525
+ const result = _builtin_fd_close(fd);
5526
+ if (result === 0 && locking.maybeLockedFds.has(fd)) {
5527
+ const nativeFilePath =
5528
+ locking.get_native_path_from_vfs_path(vfsPath);
5529
+ return PHPLoader.fileLockManager
5530
+ .releaseLocksForProcessFd(
5531
+ PHPLoader.processId,
5532
+ fd,
5533
+ nativeFilePath
5534
+ )
5535
+ .then(() => {
5536
+ _js_wasm_trace('fd_close(%d) release locks success', fd);
5537
+ })
5538
+ .catch((e) => {
5539
+ _js_wasm_trace("fd_close(%d) error '%s'", fd, e);
5540
+ })
5541
+ .then(() => {
5542
+ _js_wasm_trace('fd_close(%d) result %d', fd, result);
5543
+ return result;
5544
+ })
5545
+ .finally(() => {
5546
+ locking.maybeLockedFds.delete(fd);
5547
+ });
5548
+ } else {
5549
+ _js_wasm_trace('fd_close(%d) result %d', fd, result);
5550
+ return result;
5551
+ }
5552
+ }
5553
+
5554
+ function _builtin_fd_close(fd) {
5555
+ try {
5556
+ var stream = SYSCALLS.getStreamFromFD(fd);
5557
+ FS.close(stream);
5558
+ return 0;
5559
+ } catch (e) {
5560
+ if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;
5561
+ return e.errno;
5562
+ }
5563
+ }
5564
+
5565
+ function _builtin_fcntl64(fd, cmd, varargs) {
5213
5566
  SYSCALLS.varargs = varargs;
5214
5567
  try {
5215
5568
  var stream = SYSCALLS.getStreamFromFD(fd);
@@ -5264,6 +5617,439 @@ export function init(RuntimeName, PHPLoader) {
5264
5617
  }
5265
5618
  }
5266
5619
 
5620
+ var locking = {
5621
+ maybeLockedFds: new Set(),
5622
+ F_RDLCK: 0,
5623
+ F_WRLCK: 1,
5624
+ F_UNLCK: 2,
5625
+ lockStateToFcntl: {
5626
+ shared: 0,
5627
+ exclusive: 1,
5628
+ unlocked: 2,
5629
+ },
5630
+ fcntlToLockState: {
5631
+ 0: 'shared',
5632
+ 1: 'exclusive',
5633
+ 2: 'unlocked',
5634
+ },
5635
+ is_shared_fs_node(node) {
5636
+ if (node?.isSharedFS) {
5637
+ return true;
5638
+ }
5639
+ // Handle PROXYFS nodes which wrap other nodes.
5640
+ if (!node?.mount?.opts?.fs?.lookupPath) {
5641
+ return false;
5642
+ }
5643
+ const vfsPath = NODEFS.realPath(node);
5644
+ const underlyingNode = node.mount.opts.fs.lookupPath(vfsPath)?.node;
5645
+ return !!underlyingNode?.isSharedFS;
5646
+ },
5647
+ is_path_to_shared_fs(path) {
5648
+ const { node } = FS.lookupPath(path);
5649
+ return locking.is_shared_fs_node(node);
5650
+ },
5651
+ get_fd_access_mode(fd) {
5652
+ const emscripten_F_GETFL = Number('3');
5653
+ const emscripten_O_ACCMODE = Number('2097155');
5654
+ return (
5655
+ _builtin_fcntl64(fd, emscripten_F_GETFL) & emscripten_O_ACCMODE
5656
+ );
5657
+ },
5658
+ get_vfs_path_from_fd(fd) {
5659
+ try {
5660
+ return [FS.readlink(`/proc/self/fd/${fd}`), 0];
5661
+ } catch (error) {
5662
+ return [null, ERRNO_CODES.EBADF];
5663
+ }
5664
+ },
5665
+ get_native_path_from_vfs_path(vfsPath) {
5666
+ const { node } = FS.lookupPath(vfsPath);
5667
+ return NODEFS.realPath(node);
5668
+ },
5669
+ check_lock_params(fd, l_type) {
5670
+ const emscripten_O_RDONLY = Number('0');
5671
+ const emscripten_O_WRONLY = Number('1');
5672
+ const accessMode = locking.get_fd_access_mode(fd);
5673
+ if (
5674
+ (l_type === locking.F_WRLCK &&
5675
+ accessMode === emscripten_O_RDONLY) ||
5676
+ (l_type === locking.F_RDLCK &&
5677
+ accessMode === emscripten_O_WRONLY)
5678
+ ) {
5679
+ return ERRNO_CODES.EBADF;
5680
+ }
5681
+ return 0;
5682
+ },
5683
+ };
5684
+
5685
+ async function ___syscall_fcntl64(fd, cmd, varargs) {
5686
+ // Necessary to use varargs accessor
5687
+ SYSCALLS.varargs = varargs;
5688
+ // These constants are replaced by Emscripten during the build process
5689
+ const emscripten_F_GETLK = Number('12');
5690
+ const emscripten_F_SETLK = Number('13');
5691
+ const emscripten_F_SETLKW = Number('14');
5692
+ const emscripten_SEEK_SET = Number('0');
5693
+ // NOTE: With the exception of l_type, these offsets are not exposed to
5694
+ // JS by Emscripten, so we hardcode them here.
5695
+ const emscripten_flock_l_type_offset = 0;
5696
+ const emscripten_flock_l_whence_offset = 2;
5697
+ const emscripten_flock_l_start_offset = 8;
5698
+ const emscripten_flock_l_len_offset = 16;
5699
+ const emscripten_flock_l_pid_offset = 24;
5700
+ /**
5701
+ * Read the flock struct at the given address.
5702
+ *
5703
+ * @param {bigint} flockStructAddress - the address of the flock struct
5704
+ * @returns the flock struct
5705
+ */ function read_flock_struct(flockStructAddress) {
5706
+ /*
5707
+ * NOTE: Since we are using HEAP<WORD_SIZE> vars like HEAP16 and HEAP64,
5708
+ * we need to adjust offsets to address the word size of each HEAP.
5709
+ *
5710
+ * For example, an offset of 64 bytes is the following for each HEAP:
5711
+ * - HEAP8: 64 (the 64th byte)
5712
+ * - HEAP16: 32 (the 32nd 16-bit word)
5713
+ * - HEAP32: 16 (the 16th 32-bit word)
5714
+ * - HEAP64: 8 (the 8th 64-bit word)
5715
+ *
5716
+ * We get a word offset by dividing the byte offset by the word size.
5717
+ */ return {
5718
+ l_type: HEAP16[ // Shift right by 1 to divide by 2^1.
5719
+ (flockStructAddress + emscripten_flock_l_type_offset) >> 1
5720
+ ],
5721
+ l_whence:
5722
+ HEAP16[ // Shift right by 1 to divide by 2^1.
5723
+ (flockStructAddress +
5724
+ emscripten_flock_l_whence_offset) >>
5725
+ 1
5726
+ ],
5727
+ l_start:
5728
+ HEAP64[ // Shift right by 3 to divide by 2^3.
5729
+ (flockStructAddress +
5730
+ emscripten_flock_l_start_offset) >>
5731
+ 3
5732
+ ],
5733
+ l_len: HEAP64[ // Shift right by 3 to divide by 2^3.
5734
+ (flockStructAddress + emscripten_flock_l_len_offset) >> 3
5735
+ ],
5736
+ l_pid: HEAP32[ // Shift right by 2 to divide by 2^2.
5737
+ (flockStructAddress + emscripten_flock_l_pid_offset) >> 2
5738
+ ],
5739
+ };
5740
+ }
5741
+ /**
5742
+ * Update the flock struct at the given address with the given fields.
5743
+ *
5744
+ * @param {bigint} flockStructAddress - the address of the flock struct
5745
+ * @param {object} fields - the fields to update
5746
+ */ function update_flock_struct(flockStructAddress, fields) {
5747
+ /*
5748
+ * NOTE: Since we are using HEAP<WORD_SIZE> vars like HEAP16 and HEAP64,
5749
+ * we need to adjust offsets to address the word size of each HEAP.
5750
+ *
5751
+ * For example, an offset of 64 bytes is the following for each HEAP:
5752
+ * - HEAP8: 64 (the 64th byte)
5753
+ * - HEAP16: 32 (the 32nd 16-bit word)
5754
+ * - HEAP32: 16 (the 16th 32-bit word)
5755
+ * - HEAP64: 8 (the 8th 64-bit word)
5756
+ *
5757
+ * We get a word offset by dividing the byte offset by the word size.
5758
+ */ if (fields.l_type !== undefined) {
5759
+ HEAP16[ // Shift right by 1 to divide by 2^1.
5760
+ (flockStructAddress + emscripten_flock_l_type_offset) >> 1
5761
+ ] = fields.l_type;
5762
+ }
5763
+ if (fields.l_whence !== undefined) {
5764
+ HEAP16[ // Shift right by 1 to divide by 2^1.
5765
+ (flockStructAddress + emscripten_flock_l_whence_offset) >> 1
5766
+ ] = fields.l_whence;
5767
+ }
5768
+ if (fields.l_start !== undefined) {
5769
+ HEAP64[ // Shift right by 3 to divide by 2^3.
5770
+ (flockStructAddress + emscripten_flock_l_start_offset) >> 3
5771
+ ] = fields.l_start;
5772
+ }
5773
+ if (fields.l_len !== undefined) {
5774
+ HEAP64[ // Shift right by 3 to divide by 2^3.
5775
+ (flockStructAddress + emscripten_flock_l_len_offset) >> 3
5776
+ ] = fields.l_len;
5777
+ }
5778
+ if (fields.l_pid !== undefined) {
5779
+ HEAP32[ // Shift right by 2 to divide by 2^2.
5780
+ (flockStructAddress + emscripten_flock_l_pid_offset) >> 2
5781
+ ] = fields.l_pid;
5782
+ }
5783
+ }
5784
+ /**
5785
+ * Resolve the base address of the range depending on the whence and start offset.
5786
+ *
5787
+ * @param {number} fd - the file descriptor
5788
+ * @param {number} whence - what the start offset is relative to
5789
+ * @param {bigint} startOffset - the offset from the whence
5790
+ * @returns The resolved offset and the errno. If there is an error,
5791
+ * the resolved offset is null, and the errno is non-zero.
5792
+ */ function get_base_address(fd, whence, startOffset) {
5793
+ let baseAddress;
5794
+ switch (whence) {
5795
+ case emscripten_SEEK_SET:
5796
+ baseAddress = 0n;
5797
+ break;
5798
+
5799
+ case emscripten_SEEK_CUR:
5800
+ baseAddress = FS.lseek(fd, 0, whence);
5801
+ break;
5802
+
5803
+ case emscripten_SEEK_END:
5804
+ baseAddress = _wasm_get_end_offset(fd);
5805
+ break;
5806
+
5807
+ default:
5808
+ return [null, ERRNO_CODES.EINVAL];
5809
+ }
5810
+ if (baseAddress == -1) {
5811
+ // We cannot resolve the offset within the file.
5812
+ // Let's treat this as a problem with the file descriptor.
5813
+ return [null, ERRNO_CODES.EBADF];
5814
+ }
5815
+ const resolvedOffset = baseAddress + startOffset;
5816
+ if (resolvedOffset < 0) {
5817
+ // This is not a valid offset. Report args as invalid.
5818
+ return [null, ERRNO_CODES.EINVAL];
5819
+ }
5820
+ return [resolvedOffset, 0];
5821
+ }
5822
+ const pid = PHPLoader.processId;
5823
+ switch (cmd) {
5824
+ case emscripten_F_GETLK: {
5825
+ _js_wasm_trace('fcntl(%d, F_GETLK)', fd);
5826
+ let vfsPath;
5827
+ let errno;
5828
+ [vfsPath, errno] = locking.get_vfs_path_from_fd(fd);
5829
+ if (errno !== 0) {
5830
+ _js_wasm_trace(
5831
+ 'fcntl(%d, F_GETLK) %s get_vfs_path_from_fd errno %d',
5832
+ fd,
5833
+ vfsPath,
5834
+ errno
5835
+ );
5836
+ return -ERRNO_CODES.EBADF;
5837
+ }
5838
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
5839
+ _js_wasm_trace(
5840
+ "fcntl(%d, F_GETLK) locking is not implemented for non-NodeFS path '%s'",
5841
+ fd,
5842
+ vfsPath
5843
+ );
5844
+ // If not a NodeFS path, we can't lock it.
5845
+ // Default to succeeding as Emscripten does.
5846
+ update_flock_struct(flockStructAddr, {
5847
+ l_type: F_UNLCK,
5848
+ });
5849
+ return 0;
5850
+ }
5851
+ const flockStructAddr = syscallGetVarargP();
5852
+ const flockStruct = read_flock_struct(flockStructAddr);
5853
+ if (!(flockStruct.l_type in locking.fcntlToLockState)) {
5854
+ return -ERRNO_CODES.EINVAL;
5855
+ }
5856
+ errno = locking.check_lock_params(fd, flockStruct.l_type);
5857
+ if (errno !== 0) {
5858
+ _js_wasm_trace(
5859
+ 'fcntl(%d, F_GETLK) %s check_lock_params errno %d',
5860
+ fd,
5861
+ vfsPath,
5862
+ errno
5863
+ );
5864
+ return -ERRNO_CODES.EINVAL;
5865
+ }
5866
+ const requestedLockType =
5867
+ locking.fcntlToLockState[flockStruct.l_type];
5868
+ let absoluteStartOffset;
5869
+ [absoluteStartOffset, errno] = get_base_address(
5870
+ fd,
5871
+ flockStruct.l_whence,
5872
+ flockStruct.l_start
5873
+ );
5874
+ if (errno !== 0) {
5875
+ _js_wasm_trace(
5876
+ 'fcntl(%d, F_GETLK) %s get_base_address errno %d',
5877
+ fd,
5878
+ vfsPath,
5879
+ errno
5880
+ );
5881
+ return -ERRNO_CODES.EINVAL;
5882
+ }
5883
+ const nativeFilePath =
5884
+ locking.get_native_path_from_vfs_path(vfsPath);
5885
+ return PHPLoader.fileLockManager
5886
+ .findFirstConflictingByteRangeLock(nativeFilePath, {
5887
+ type: requestedLockType,
5888
+ start: absoluteStartOffset,
5889
+ end: absoluteStartOffset + flockStruct.l_len,
5890
+ pid,
5891
+ })
5892
+ .then((conflictingLock) => {
5893
+ if (conflictingLock === undefined) {
5894
+ _js_wasm_trace(
5895
+ 'fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=unlocked start=0x%x end=0x%x',
5896
+ fd,
5897
+ vfsPath,
5898
+ absoluteStartOffset,
5899
+ absoluteStartOffset + flockStruct.l_len
5900
+ );
5901
+ update_flock_struct(flockStructAddr, {
5902
+ l_type: F_UNLCK,
5903
+ });
5904
+ return 0;
5905
+ }
5906
+ _js_wasm_trace(
5907
+ 'fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock type=%s start=0x%x end=0x%x conflictingLock %d',
5908
+ fd,
5909
+ vfsPath,
5910
+ conflictingLock.type,
5911
+ conflictingLock.start,
5912
+ conflictingLock.end,
5913
+ conflictingLock.pid
5914
+ );
5915
+ const fcntlLockState =
5916
+ locking.lockStateToFcntl[conflictingLock.type];
5917
+ update_flock_struct(flockStructAddr, {
5918
+ l_type: fcntlLockState,
5919
+ l_whence: emscripten_SEEK_SET,
5920
+ l_start: conflictingLock.start,
5921
+ l_len: conflictingLock.end - conflictingLock.start,
5922
+ l_pid: conflictingLock.pid,
5923
+ });
5924
+ return 0;
5925
+ })
5926
+ .catch((e) => {
5927
+ _js_wasm_trace(
5928
+ 'fcntl(%d, F_GETLK) %s findFirstConflictingByteRangeLock error %s',
5929
+ fd,
5930
+ vfsPath,
5931
+ e
5932
+ );
5933
+ return -ERRNO_CODES.EINVAL;
5934
+ });
5935
+ }
5936
+
5937
+ case emscripten_F_SETLK: {
5938
+ _js_wasm_trace('fcntl(%d, F_SETLK)', fd);
5939
+ let vfsPath;
5940
+ let errno;
5941
+ [vfsPath, errno] = locking.get_vfs_path_from_fd(fd);
5942
+ if (errno !== 0) {
5943
+ _js_wasm_trace(
5944
+ 'fcntl(%d, F_SETLK) %s get_vfs_path_from_fd errno %d',
5945
+ fd,
5946
+ vfsPath,
5947
+ errno
5948
+ );
5949
+ return -errno;
5950
+ }
5951
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
5952
+ _js_wasm_trace(
5953
+ 'fcntl(%d, F_SETLK) locking is not implemented for non-NodeFS path %s',
5954
+ fd,
5955
+ vfsPath
5956
+ );
5957
+ // If not a NodeFS path, we can't lock it.
5958
+ // Default to succeeding as Emscripten does.
5959
+ return 0;
5960
+ }
5961
+ var flockStructAddr = syscallGetVarargP();
5962
+ const flockStruct = read_flock_struct(flockStructAddr);
5963
+ let absoluteStartOffset;
5964
+ [absoluteStartOffset, errno] = get_base_address(
5965
+ fd,
5966
+ flockStruct.l_whence,
5967
+ flockStruct.l_start
5968
+ );
5969
+ if (errno !== 0) {
5970
+ _js_wasm_trace(
5971
+ 'fcntl(%d, F_SETLK) %s get_base_address errno %d',
5972
+ fd,
5973
+ vfsPath,
5974
+ errno
5975
+ );
5976
+ return -errno;
5977
+ }
5978
+ if (!(flockStruct.l_type in locking.fcntlToLockState)) {
5979
+ _js_wasm_trace(
5980
+ 'fcntl(%d, F_SETLK) %s invalid lock type %d',
5981
+ fd,
5982
+ vfsPath,
5983
+ flockStruct.l_type
5984
+ );
5985
+ return -ERRNO_CODES.EINVAL;
5986
+ }
5987
+ errno = locking.check_lock_params(fd, flockStruct.l_type);
5988
+ if (errno !== 0) {
5989
+ _js_wasm_trace(
5990
+ 'fcntl(%d, F_SETLK) %s check_lock_params errno %d',
5991
+ fd,
5992
+ vfsPath,
5993
+ errno
5994
+ );
5995
+ return -errno;
5996
+ }
5997
+ locking.maybeLockedFds.add(fd);
5998
+ const requestedLockType =
5999
+ locking.fcntlToLockState[flockStruct.l_type];
6000
+ const rangeLock = {
6001
+ type: requestedLockType,
6002
+ start: absoluteStartOffset,
6003
+ end: absoluteStartOffset + flockStruct.l_len,
6004
+ pid,
6005
+ };
6006
+ const nativeFilePath =
6007
+ locking.get_native_path_from_vfs_path(vfsPath);
6008
+ _js_wasm_trace(
6009
+ 'fcntl(%d, F_SETLK) %s calling lockFileByteRange for range lock %s',
6010
+ fd,
6011
+ vfsPath,
6012
+ rangeLock
6013
+ );
6014
+ return PHPLoader.fileLockManager
6015
+ .lockFileByteRange(nativeFilePath, rangeLock)
6016
+ .then((succeeded) => {
6017
+ _js_wasm_trace(
6018
+ 'fcntl(%d, F_SETLK) %s lockFileByteRange returned %d for range lock %s',
6019
+ fd,
6020
+ vfsPath,
6021
+ succeeded,
6022
+ rangeLock
6023
+ );
6024
+ return succeeded ? 0 : -ERRNO_CODES.EAGAIN;
6025
+ })
6026
+ .catch((e) => {
6027
+ _js_wasm_trace(
6028
+ 'fcntl(%d, F_SETLK) %s lockFileByteRange error %s for range lock %s',
6029
+ fd,
6030
+ vfsPath,
6031
+ e,
6032
+ rangeLock
6033
+ );
6034
+ return -ERRNO_CODES.EINVAL;
6035
+ });
6036
+ }
6037
+
6038
+ // @TODO: Implement waiting for lock
6039
+ case emscripten_F_SETLKW: {
6040
+ // We do not yet support the blocking form of flock().
6041
+ // We respond with EDEADLK to indicate failure
6042
+ // because it is a known errno for a failed F_SETLKW command.
6043
+ return -ERRNO_CODES.EDEADLK;
6044
+ }
6045
+
6046
+ default:
6047
+ return _builtin_fcntl64(fd, cmd, varargs);
6048
+ }
6049
+ }
6050
+
6051
+ ___syscall_fcntl64.isAsync = true;
6052
+
5267
6053
  function ___syscall_fdatasync(fd) {
5268
6054
  try {
5269
6055
  var stream = SYSCALLS.getStreamFromFD(fd);
@@ -5295,11 +6081,6 @@ export function init(RuntimeName, PHPLoader) {
5295
6081
  }
5296
6082
  }
5297
6083
 
5298
- var stringToUTF8 = (str, outPtr, maxBytesToWrite) =>
5299
- stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
5300
-
5301
- Module['stringToUTF8'] = stringToUTF8;
5302
-
5303
6084
  function ___syscall_getcwd(buf, size) {
5304
6085
  try {
5305
6086
  if (size === 0) return -28;
@@ -6559,17 +7340,6 @@ export function init(RuntimeName, PHPLoader) {
6559
7340
  return 0;
6560
7341
  };
6561
7342
 
6562
- function _fd_close(fd) {
6563
- try {
6564
- var stream = SYSCALLS.getStreamFromFD(fd);
6565
- FS.close(stream);
6566
- return 0;
6567
- } catch (e) {
6568
- if (typeof FS == 'undefined' || !(e.name === 'ErrnoError')) throw e;
6569
- return e.errno;
6570
- }
6571
- }
6572
-
6573
7343
  function _fd_fdstat_get(fd, pbuf) {
6574
7344
  try {
6575
7345
  var rightsBase = 0;
@@ -6806,312 +7576,125 @@ export function init(RuntimeName, PHPLoader) {
6806
7576
  return -2;
6807
7577
  }
6808
7578
  }
6809
- }
6810
- if (addr != null) {
6811
- ai = allocaddrinfo(family, type, proto, node, addr, port);
6812
- HEAPU32[out >> 2] = ai;
6813
- return 0;
6814
- }
6815
- if (flags & 4) {
6816
- return -2;
6817
- }
6818
- // try as a hostname
6819
- // resolve the hostname to a temporary fake address
6820
- node = DNS.lookup_name(node);
6821
- addr = inetPton4(node);
6822
- if (family === 0) {
6823
- family = 2;
6824
- } else if (family === 10) {
6825
- addr = [0, 0, _htonl(65535), addr];
6826
- }
6827
- ai = allocaddrinfo(family, type, proto, null, addr, port);
6828
- HEAPU32[out >> 2] = ai;
6829
- return 0;
6830
- };
6831
-
6832
- var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => {
6833
- var info = readSockaddr(sa, salen);
6834
- if (info.errno) {
6835
- return -6;
6836
- }
6837
- var port = info.port;
6838
- var addr = info.addr;
6839
- var overflowed = false;
6840
- if (node && nodelen) {
6841
- var lookup;
6842
- if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) {
6843
- if (flags & 8) {
6844
- return -2;
6845
- }
6846
- } else {
6847
- addr = lookup;
6848
- }
6849
- var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen);
6850
- if (numBytesWrittenExclNull + 1 >= nodelen) {
6851
- overflowed = true;
6852
- }
6853
- }
6854
- if (serv && servlen) {
6855
- port = '' + port;
6856
- var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen);
6857
- if (numBytesWrittenExclNull + 1 >= servlen) {
6858
- overflowed = true;
6859
- }
6860
- }
6861
- if (overflowed) {
6862
- // Note: even when we overflow, getnameinfo() is specced to write out the truncated results.
6863
- return -12;
6864
- }
6865
- return 0;
6866
- };
6867
-
6868
- var Protocols = {
6869
- list: [],
6870
- map: {},
6871
- };
6872
-
6873
- var _setprotoent = (stayopen) => {
6874
- // void setprotoent(int stayopen);
6875
- // Allocate and populate a protoent structure given a name, protocol number and array of aliases
6876
- function allocprotoent(name, proto, aliases) {
6877
- // write name into buffer
6878
- var nameBuf = _malloc(name.length + 1);
6879
- stringToAscii(name, nameBuf);
6880
- // write aliases into buffer
6881
- var j = 0;
6882
- var length = aliases.length;
6883
- var aliasListBuf = _malloc((length + 1) * 4);
6884
- // Use length + 1 so we have space for the terminating NULL ptr.
6885
- for (var i = 0; i < length; i++, j += 4) {
6886
- var alias = aliases[i];
6887
- var aliasBuf = _malloc(alias.length + 1);
6888
- stringToAscii(alias, aliasBuf);
6889
- HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf;
6890
- }
6891
- HEAPU32[(aliasListBuf + j) >> 2] = 0;
6892
- // Terminating NULL pointer.
6893
- // generate protoent
6894
- var pe = _malloc(12);
6895
- HEAPU32[pe >> 2] = nameBuf;
6896
- HEAPU32[(pe + 4) >> 2] = aliasListBuf;
6897
- HEAP32[(pe + 8) >> 2] = proto;
6898
- return pe;
6899
- }
6900
- // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial
6901
- // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful.
6902
- var list = Protocols.list;
6903
- var map = Protocols.map;
6904
- if (list.length === 0) {
6905
- var entry = allocprotoent('tcp', 6, ['TCP']);
6906
- list.push(entry);
6907
- map['tcp'] = map['6'] = entry;
6908
- entry = allocprotoent('udp', 17, ['UDP']);
6909
- list.push(entry);
6910
- map['udp'] = map['17'] = entry;
6911
- }
6912
- _setprotoent.index = 0;
6913
- };
6914
-
6915
- var _getprotobyname = (name) => {
6916
- // struct protoent *getprotobyname(const char *);
6917
- name = UTF8ToString(name);
6918
- _setprotoent(true);
6919
- var result = Protocols.map[name];
6920
- return result;
6921
- };
6922
-
6923
- var _getprotobynumber = (number) => {
6924
- // struct protoent *getprotobynumber(int proto);
6925
- _setprotoent(true);
6926
- var result = Protocols.map[number];
6927
- return result;
6928
- };
6929
-
6930
- var stackAlloc = (sz) => __emscripten_stack_alloc(sz);
6931
-
6932
- /** @suppress {duplicate } */ var stringToUTF8OnStack = (str) => {
6933
- var size = lengthBytesUTF8(str) + 1;
6934
- var ret = stackAlloc(size);
6935
- stringToUTF8(str, ret, size);
6936
- return ret;
6937
- };
6938
-
6939
- var allocateUTF8OnStack = stringToUTF8OnStack;
6940
-
6941
- var PHPWASM = {
6942
- init: function () {
6943
- // The /internal directory is required by the C module. It's where the
6944
- // stdout, stderr, and headers information are written for the JavaScript
6945
- // code to read later on.
6946
- FS.mkdir('/internal');
6947
- // The files from the shared directory are shared between all the
6948
- // PHP processes managed by PHPProcessManager.
6949
- FS.mkdir('/internal/shared');
6950
- // The files from the preload directory are preloaded using the
6951
- // auto_prepend_file php.ini directive.
6952
- FS.mkdir('/internal/shared/preload');
6953
- PHPWASM.EventEmitter = ENVIRONMENT_IS_NODE
6954
- ? require('events').EventEmitter
6955
- : class EventEmitter {
6956
- constructor() {
6957
- this.listeners = {};
6958
- }
6959
- emit(eventName, data) {
6960
- if (this.listeners[eventName]) {
6961
- this.listeners[eventName].forEach(
6962
- (callback) => {
6963
- callback(data);
6964
- }
6965
- );
6966
- }
6967
- }
6968
- once(eventName, callback) {
6969
- const self = this;
6970
- function removedCallback() {
6971
- callback(...arguments);
6972
- self.removeListener(eventName, removedCallback);
6973
- }
6974
- this.on(eventName, removedCallback);
6975
- }
6976
- removeAllListeners(eventName) {
6977
- if (eventName) {
6978
- delete this.listeners[eventName];
6979
- } else {
6980
- this.listeners = {};
6981
- }
6982
- }
6983
- removeListener(eventName, callback) {
6984
- if (this.listeners[eventName]) {
6985
- const idx =
6986
- this.listeners[eventName].indexOf(callback);
6987
- if (idx !== -1) {
6988
- this.listeners[eventName].splice(idx, 1);
6989
- }
6990
- }
6991
- }
6992
- };
6993
- PHPWASM.child_proc_by_fd = {};
6994
- PHPWASM.child_proc_by_pid = {};
6995
- PHPWASM.input_devices = {};
6996
- },
6997
- getAllWebSockets: function (sock) {
6998
- const webSockets = new Set();
6999
- if (sock.server) {
7000
- sock.server.clients.forEach((ws) => {
7001
- webSockets.add(ws);
7002
- });
7003
- }
7004
- for (const peer of PHPWASM.getAllPeers(sock)) {
7005
- webSockets.add(peer.socket);
7006
- }
7007
- return Array.from(webSockets);
7008
- },
7009
- getAllPeers: function (sock) {
7010
- const peers = new Set();
7011
- if (sock.server) {
7012
- sock.pending
7013
- .filter((pending) => pending.peers)
7014
- .forEach((pending) => {
7015
- for (const peer of Object.values(pending.peers)) {
7016
- peers.add(peer);
7017
- }
7018
- });
7019
- }
7020
- if (sock.peers) {
7021
- for (const peer of Object.values(sock.peers)) {
7022
- peers.add(peer);
7023
- }
7024
- }
7025
- return Array.from(peers);
7026
- },
7027
- awaitData: function (ws) {
7028
- return PHPWASM.awaitEvent(ws, 'message');
7029
- },
7030
- awaitConnection: function (ws) {
7031
- if (ws.OPEN === ws.readyState) {
7032
- return [Promise.resolve(), PHPWASM.noop];
7033
- }
7034
- return PHPWASM.awaitEvent(ws, 'open');
7035
- },
7036
- awaitClose: function (ws) {
7037
- if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
7038
- return [Promise.resolve(), PHPWASM.noop];
7039
- }
7040
- return PHPWASM.awaitEvent(ws, 'close');
7041
- },
7042
- awaitError: function (ws) {
7043
- if ([ws.CLOSING, ws.CLOSED].includes(ws.readyState)) {
7044
- return [Promise.resolve(), PHPWASM.noop];
7045
- }
7046
- return PHPWASM.awaitEvent(ws, 'error');
7047
- },
7048
- awaitEvent: function (ws, event) {
7049
- let resolve;
7050
- const listener = () => {
7051
- resolve();
7052
- };
7053
- const promise = new Promise(function (_resolve) {
7054
- resolve = _resolve;
7055
- ws.once(event, listener);
7056
- });
7057
- const cancel = () => {
7058
- ws.removeListener(event, listener);
7059
- // Rejecting the promises bubbles up and kills the entire
7060
- // node process. Let's resolve them on the next tick instead
7061
- // to give the caller some space to unbind any handlers.
7062
- setTimeout(resolve);
7063
- };
7064
- return [promise, cancel];
7065
- },
7066
- noop: function () {},
7067
- spawnProcess: function (command, args, options) {
7068
- if (Module['spawnProcess']) {
7069
- const spawnedPromise = Module['spawnProcess'](
7070
- command,
7071
- args,
7072
- options
7073
- );
7074
- return Promise.resolve(spawnedPromise).then(function (spawned) {
7075
- if (!spawned || !spawned.on) {
7076
- throw new Error(
7077
- 'spawnProcess() must return an EventEmitter but returned a different type.'
7078
- );
7079
- }
7080
- return spawned;
7081
- });
7579
+ }
7580
+ if (addr != null) {
7581
+ ai = allocaddrinfo(family, type, proto, node, addr, port);
7582
+ HEAPU32[out >> 2] = ai;
7583
+ return 0;
7584
+ }
7585
+ if (flags & 4) {
7586
+ return -2;
7587
+ }
7588
+ // try as a hostname
7589
+ // resolve the hostname to a temporary fake address
7590
+ node = DNS.lookup_name(node);
7591
+ addr = inetPton4(node);
7592
+ if (family === 0) {
7593
+ family = 2;
7594
+ } else if (family === 10) {
7595
+ addr = [0, 0, _htonl(65535), addr];
7596
+ }
7597
+ ai = allocaddrinfo(family, type, proto, null, addr, port);
7598
+ HEAPU32[out >> 2] = ai;
7599
+ return 0;
7600
+ };
7601
+
7602
+ var _getnameinfo = (sa, salen, node, nodelen, serv, servlen, flags) => {
7603
+ var info = readSockaddr(sa, salen);
7604
+ if (info.errno) {
7605
+ return -6;
7606
+ }
7607
+ var port = info.port;
7608
+ var addr = info.addr;
7609
+ var overflowed = false;
7610
+ if (node && nodelen) {
7611
+ var lookup;
7612
+ if (flags & 1 || !(lookup = DNS.lookup_addr(addr))) {
7613
+ if (flags & 8) {
7614
+ return -2;
7615
+ }
7616
+ } else {
7617
+ addr = lookup;
7082
7618
  }
7083
- if (ENVIRONMENT_IS_NODE) {
7084
- return require('child_process').spawn(command, args, {
7085
- ...options,
7086
- shell: true,
7087
- stdio: ['pipe', 'pipe', 'pipe'],
7088
- timeout: 100,
7089
- });
7619
+ var numBytesWrittenExclNull = stringToUTF8(addr, node, nodelen);
7620
+ if (numBytesWrittenExclNull + 1 >= nodelen) {
7621
+ overflowed = true;
7090
7622
  }
7091
- const e = new Error(
7092
- 'popen(), proc_open() etc. are unsupported in the browser. Call php.setSpawnHandler() ' +
7093
- 'and provide a callback to handle spawning processes, or disable a popen(), proc_open() ' +
7094
- 'and similar functions via php.ini.'
7095
- );
7096
- e.code = 'SPAWN_UNSUPPORTED';
7097
- throw e;
7098
- },
7099
- shutdownSocket: function (socketd, how) {
7100
- // This implementation only supports websockets at the moment
7101
- const sock = getSocketFromFD(socketd);
7102
- const peer = Object.values(sock.peers)[0];
7103
- if (!peer) {
7104
- return -1;
7623
+ }
7624
+ if (serv && servlen) {
7625
+ port = '' + port;
7626
+ var numBytesWrittenExclNull = stringToUTF8(port, serv, servlen);
7627
+ if (numBytesWrittenExclNull + 1 >= servlen) {
7628
+ overflowed = true;
7105
7629
  }
7106
- try {
7107
- peer.socket.close();
7108
- SOCKFS.websocket_sock_ops.removePeer(sock, peer);
7109
- return 0;
7110
- } catch (e) {
7111
- console.log('Socket shutdown error', e);
7112
- return -1;
7630
+ }
7631
+ if (overflowed) {
7632
+ // Note: even when we overflow, getnameinfo() is specced to write out the truncated results.
7633
+ return -12;
7634
+ }
7635
+ return 0;
7636
+ };
7637
+
7638
+ var Protocols = {
7639
+ list: [],
7640
+ map: {},
7641
+ };
7642
+
7643
+ var _setprotoent = (stayopen) => {
7644
+ // void setprotoent(int stayopen);
7645
+ // Allocate and populate a protoent structure given a name, protocol number and array of aliases
7646
+ function allocprotoent(name, proto, aliases) {
7647
+ // write name into buffer
7648
+ var nameBuf = _malloc(name.length + 1);
7649
+ stringToAscii(name, nameBuf);
7650
+ // write aliases into buffer
7651
+ var j = 0;
7652
+ var length = aliases.length;
7653
+ var aliasListBuf = _malloc((length + 1) * 4);
7654
+ // Use length + 1 so we have space for the terminating NULL ptr.
7655
+ for (var i = 0; i < length; i++, j += 4) {
7656
+ var alias = aliases[i];
7657
+ var aliasBuf = _malloc(alias.length + 1);
7658
+ stringToAscii(alias, aliasBuf);
7659
+ HEAPU32[(aliasListBuf + j) >> 2] = aliasBuf;
7113
7660
  }
7114
- },
7661
+ HEAPU32[(aliasListBuf + j) >> 2] = 0;
7662
+ // Terminating NULL pointer.
7663
+ // generate protoent
7664
+ var pe = _malloc(12);
7665
+ HEAPU32[pe >> 2] = nameBuf;
7666
+ HEAPU32[(pe + 4) >> 2] = aliasListBuf;
7667
+ HEAP32[(pe + 8) >> 2] = proto;
7668
+ return pe;
7669
+ }
7670
+ // Populate the protocol 'database'. The entries are limited to tcp and udp, though it is fairly trivial
7671
+ // to add extra entries from /etc/protocols if desired - though not sure if that'd actually be useful.
7672
+ var list = Protocols.list;
7673
+ var map = Protocols.map;
7674
+ if (list.length === 0) {
7675
+ var entry = allocprotoent('tcp', 6, ['TCP']);
7676
+ list.push(entry);
7677
+ map['tcp'] = map['6'] = entry;
7678
+ entry = allocprotoent('udp', 17, ['UDP']);
7679
+ list.push(entry);
7680
+ map['udp'] = map['17'] = entry;
7681
+ }
7682
+ _setprotoent.index = 0;
7683
+ };
7684
+
7685
+ var _getprotobyname = (name) => {
7686
+ // struct protoent *getprotobyname(const char *);
7687
+ name = UTF8ToString(name);
7688
+ _setprotoent(true);
7689
+ var result = Protocols.map[name];
7690
+ return result;
7691
+ };
7692
+
7693
+ var _getprotobynumber = (number) => {
7694
+ // struct protoent *getprotobynumber(int proto);
7695
+ _setprotoent(true);
7696
+ var result = Protocols.map[number];
7697
+ return result;
7115
7698
  };
7116
7699
 
7117
7700
  function _js_create_input_device(deviceId) {
@@ -7149,6 +7732,98 @@ export function init(RuntimeName, PHPLoader) {
7149
7732
  return allocateUTF8OnStack(devicePath);
7150
7733
  }
7151
7734
 
7735
+ async function _js_flock(fd, op) {
7736
+ _js_wasm_trace('js_flock(%d, %d)', fd, op);
7737
+ // Emscripten does not expose these constants to JS, so we hardcode them here.
7738
+ // Based on
7739
+ // https://github.com/emscripten-core/emscripten/blob/76860cc47cef67f5712a7a03a247bc1baabf7ba4/system/lib/libc/musl/include/sys/file.h#L7-L10
7740
+ const emscripten_LOCK_SH = 1;
7741
+ const emscripten_LOCK_EX = 2;
7742
+ const emscripten_LOCK_NB = 4;
7743
+ const emscripten_LOCK_UN = 8;
7744
+ const flockToLockOpType = {
7745
+ [emscripten_LOCK_SH]: 'shared',
7746
+ [emscripten_LOCK_EX]: 'exclusive',
7747
+ [emscripten_LOCK_UN]: 'unlocked',
7748
+ };
7749
+ let vfsPath;
7750
+ let errno;
7751
+ [vfsPath, errno] = locking.get_vfs_path_from_fd(fd);
7752
+ if (errno !== 0) {
7753
+ _js_wasm_trace(
7754
+ 'js_flock(%d, %d) get_vfs_path_from_fd errno %d',
7755
+ fd,
7756
+ op,
7757
+ vfsPath,
7758
+ errno
7759
+ );
7760
+ return -errno;
7761
+ }
7762
+ if (!locking.is_path_to_shared_fs(vfsPath)) {
7763
+ _js_wasm_trace(
7764
+ 'flock(%d, %d) locking is not implemented for non-NodeFS path %s',
7765
+ fd,
7766
+ op,
7767
+ vfsPath
7768
+ );
7769
+ // If not a NodeFS path, we can't lock it.
7770
+ // Default to succeeding as Emscripten does.
7771
+ return 0;
7772
+ }
7773
+ errno = locking.check_lock_params(fd, op);
7774
+ if (errno !== 0) {
7775
+ _js_wasm_trace(
7776
+ 'js_flock(%d, %d) check_lock_params errno %d',
7777
+ fd,
7778
+ op,
7779
+ errno
7780
+ );
7781
+ return -errno;
7782
+ }
7783
+ // @TODO: Consider supporting blocking mode of flock()
7784
+ if (op & (emscripten_LOCK_NB === 0)) {
7785
+ _js_wasm_trace(
7786
+ 'js_flock(%d, %d) blocking mode of flock() is not implemented',
7787
+ fd,
7788
+ op
7789
+ );
7790
+ // We do not yet support the blocking form of flock().
7791
+ // We respond with EINVAL to indicate failure
7792
+ // because it is a known errno for a failed blocking flock().
7793
+ return -ERRNO_CODES.EINVAL;
7794
+ }
7795
+ const maskedOp =
7796
+ op & (emscripten_LOCK_SH | emscripten_LOCK_EX | emscripten_LOCK_UN);
7797
+ const lockOpType = flockToLockOpType[maskedOp];
7798
+ if (lockOpType === undefined) {
7799
+ _js_wasm_trace(
7800
+ 'js_flock(%d, %d) invalid flock() operation',
7801
+ fd,
7802
+ op
7803
+ );
7804
+ return -ERRNO_CODES.EINVAL;
7805
+ }
7806
+ const nativeFilePath = locking.get_native_path_from_vfs_path(vfsPath);
7807
+ const obtainedLock = await PHPLoader.fileLockManager.lockWholeFile(
7808
+ nativeFilePath,
7809
+ {
7810
+ type: lockOpType,
7811
+ pid: PHPLoader.processId,
7812
+ fd,
7813
+ }
7814
+ );
7815
+ _js_wasm_trace(
7816
+ 'js_flock(%d, %d) lockWholeFile %s returned %d',
7817
+ fd,
7818
+ op,
7819
+ vfsPath,
7820
+ obtainedLock
7821
+ );
7822
+ return obtainedLock ? 0 : -ERRNO_CODES.EWOULDBLOCK;
7823
+ }
7824
+
7825
+ _js_flock.isAsync = true;
7826
+
7152
7827
  function _js_open_process(
7153
7828
  command,
7154
7829
  argsPtr,
@@ -7174,7 +7849,7 @@ export function init(RuntimeName, PHPLoader) {
7174
7849
  argsArray.push(UTF8ToString(HEAPU32[charPointer >> 2]));
7175
7850
  }
7176
7851
  }
7177
- const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : null;
7852
+ const cwdstr = cwdPtr ? UTF8ToString(cwdPtr) : FS.cwd();
7178
7853
  let envObject = null;
7179
7854
  if (envLength) {
7180
7855
  envObject = {};
@@ -7247,6 +7922,15 @@ export function init(RuntimeName, PHPLoader) {
7247
7922
  PHPWASM.child_proc_by_fd[ProcInfo.stderrParentFd] = ProcInfo;
7248
7923
  PHPWASM.child_proc_by_pid[ProcInfo.pid] = ProcInfo;
7249
7924
  cp.on('exit', function (code) {
7925
+ for (const fd of [
7926
+ // The child process exited. Let's clean up its output streams:
7927
+ ProcInfo.stdoutChildFd,
7928
+ ProcInfo.stderrChildFd,
7929
+ ]) {
7930
+ if (FS.streams[fd] && !FS.isClosed(FS.streams[fd])) {
7931
+ FS.close(FS.streams[fd]);
7932
+ }
7933
+ }
7250
7934
  ProcInfo.exitCode = code;
7251
7935
  ProcInfo.exited = true;
7252
7936
  // Emit events for the wasm_poll_socket function.
@@ -7297,12 +7981,52 @@ export function init(RuntimeName, PHPLoader) {
7297
7981
  * listen to the 'exit' event.
7298
7982
  */ try {
7299
7983
  await new Promise((resolve, reject) => {
7300
- cp.on('spawn', resolve);
7301
- cp.on('error', reject);
7984
+ /**
7985
+ * There was no `await` between the `spawnProcess` call
7986
+ * and the `await` below so the process haven't had a chance
7987
+ * to run any of the exit-related callbacks yet.
7988
+ *
7989
+ * Good.
7990
+ *
7991
+ * Let's listen to all the lifecycle events and resolve
7992
+ * the promise when the process starts or immediately crashes.
7993
+ */ let resolved = false;
7994
+ cp.on('spawn', () => {
7995
+ if (resolved) return;
7996
+ resolved = true;
7997
+ resolve();
7998
+ });
7999
+ cp.on('error', (e) => {
8000
+ if (resolved) return;
8001
+ resolved = true;
8002
+ reject(e);
8003
+ });
8004
+ cp.on('exit', function (code) {
8005
+ if (resolved) return;
8006
+ resolved = true;
8007
+ if (code === 0) {
8008
+ resolve();
8009
+ } else {
8010
+ reject(
8011
+ new Error(`Process exited with code ${code}`)
8012
+ );
8013
+ }
8014
+ });
8015
+ /**
8016
+ * If the process haven't even started after 5 seconds, something
8017
+ * is wrong. Perhaps we're missing an event listener, or perhaps
8018
+ * the `spawnProcess` implementation failed to dispatch the relevant
8019
+ * event. Either way, let's crash to avoid blocking the proc_open()
8020
+ * call indefinitely.
8021
+ */ setTimeout(() => {
8022
+ if (resolved) return;
8023
+ resolved = true;
8024
+ reject(new Error('Process timed out'));
8025
+ }, 5e3);
7302
8026
  });
7303
8027
  } catch (e) {
7304
8028
  console.error(e);
7305
- wakeUp(1);
8029
+ wakeUp(ProcInfo.pid);
7306
8030
  return;
7307
8031
  }
7308
8032
  // Now we want to pass data from the STDIN source supplied by PHP
@@ -7372,6 +8096,21 @@ export function init(RuntimeName, PHPLoader) {
7372
8096
  return 0;
7373
8097
  }
7374
8098
 
8099
+ var _js_release_file_locks = async function js_release_file_locks() {
8100
+ _js_wasm_trace('js_release_file_locks()');
8101
+ const pid = PHPLoader.processId;
8102
+ return await PHPLoader.fileLockManager
8103
+ .releaseLocksForProcess(pid)
8104
+ .then(() => {
8105
+ _js_wasm_trace('js_release_file_locks succeeded');
8106
+ })
8107
+ .catch((e) => {
8108
+ _js_wasm_trace('js_release_file_locks error %s', e);
8109
+ });
8110
+ };
8111
+
8112
+ _js_release_file_locks.isAsync = true;
8113
+
7375
8114
  function _js_waitpid(pid, exitCodePtr) {
7376
8115
  if (!PHPWASM.child_proc_by_pid[pid]) {
7377
8116
  return -1;
@@ -8008,14 +8747,7 @@ export function init(RuntimeName, PHPLoader) {
8008
8747
  const POLLNVAL = 32;
8009
8748
  return returnCallback((wakeUp) => {
8010
8749
  const polls = [];
8011
- if (socketd in PHPWASM.child_proc_by_fd) {
8012
- const procInfo = PHPWASM.child_proc_by_fd[socketd];
8013
- if (procInfo.exited) {
8014
- wakeUp(0);
8015
- return;
8016
- }
8017
- polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data'));
8018
- } else if (FS.isSocket(FS.getStream(socketd)?.node.mode)) {
8750
+ if (FS.isSocket(FS.getStream(socketd)?.node.mode)) {
8019
8751
  const sock = getSocketFromFD(socketd);
8020
8752
  if (!sock) {
8021
8753
  wakeUp(0);
@@ -8049,7 +8781,12 @@ export function init(RuntimeName, PHPLoader) {
8049
8781
  polls.push(PHPWASM.awaitConnection(ws));
8050
8782
  lookingFor.add('POLLOUT');
8051
8783
  }
8052
- if (events & POLLHUP) {
8784
+ if (
8785
+ events & POLLHUP ||
8786
+ events & POLLIN ||
8787
+ events & POLLOUT ||
8788
+ events & POLLERR
8789
+ ) {
8053
8790
  polls.push(PHPWASM.awaitClose(ws));
8054
8791
  lookingFor.add('POLLHUP');
8055
8792
  }
@@ -8058,6 +8795,13 @@ export function init(RuntimeName, PHPLoader) {
8058
8795
  lookingFor.add('POLLERR');
8059
8796
  }
8060
8797
  }
8798
+ } else if (socketd in PHPWASM.child_proc_by_fd) {
8799
+ const procInfo = PHPWASM.child_proc_by_fd[socketd];
8800
+ if (procInfo.exited) {
8801
+ wakeUp(0);
8802
+ return;
8803
+ }
8804
+ polls.push(PHPWASM.awaitEvent(procInfo.stdout, 'data'));
8061
8805
  } else {
8062
8806
  setTimeout(function () {
8063
8807
  wakeUp(1);
@@ -8289,9 +9033,13 @@ export function init(RuntimeName, PHPLoader) {
8289
9033
  /** @export */ getprotobyname: _getprotobyname,
8290
9034
  /** @export */ getprotobynumber: _getprotobynumber,
8291
9035
  /** @export */ js_create_input_device: _js_create_input_device,
9036
+ /** @export */ js_flock: _js_flock,
9037
+ /** @export */ js_getpid: _js_getpid,
8292
9038
  /** @export */ js_open_process: _js_open_process,
8293
9039
  /** @export */ js_process_status: _js_process_status,
9040
+ /** @export */ js_release_file_locks: _js_release_file_locks,
8294
9041
  /** @export */ js_waitpid: _js_waitpid,
9042
+ /** @export */ js_wasm_trace: _js_wasm_trace,
8295
9043
  /** @export */ proc_exit: _proc_exit,
8296
9044
  /** @export */ strptime: _strptime,
8297
9045
  /** @export */ wasm_close: _wasm_close,
@@ -8307,6 +9055,9 @@ export function init(RuntimeName, PHPLoader) {
8307
9055
 
8308
9056
  var _malloc = (a0) => (_malloc = wasmExports['malloc'])(a0);
8309
9057
 
9058
+ var _getpid = (Module['_getpid'] = () =>
9059
+ (_getpid = Module['_getpid'] = wasmExports['getpid'])());
9060
+
8310
9061
  var _wasm_sleep = (Module['_wasm_sleep'] = (a0) =>
8311
9062
  (_wasm_sleep = Module['_wasm_sleep'] = wasmExports['wasm_sleep'])(a0));
8312
9063
 
@@ -8325,6 +9076,9 @@ export function init(RuntimeName, PHPLoader) {
8325
9076
 
8326
9077
  var _fflush = (a0) => (_fflush = wasmExports['fflush'])(a0);
8327
9078
 
9079
+ var _flock = (Module['_flock'] = (a0, a1) =>
9080
+ (_flock = Module['_flock'] = wasmExports['flock'])(a0, a1));
9081
+
8328
9082
  var _wasm_popen = (Module['_wasm_popen'] = (a0, a1) =>
8329
9083
  (_wasm_popen = Module['_wasm_popen'] = wasmExports['wasm_popen'])(
8330
9084
  a0,
@@ -8343,13 +9097,6 @@ export function init(RuntimeName, PHPLoader) {
8343
9097
  (___wrap_select = Module['___wrap_select'] =
8344
9098
  wasmExports['__wrap_select'])(a0, a1, a2, a3, a4));
8345
9099
 
8346
- var _wasm_add_cli_arg = (Module['_wasm_add_cli_arg'] = (a0) =>
8347
- (_wasm_add_cli_arg = Module['_wasm_add_cli_arg'] =
8348
- wasmExports['wasm_add_cli_arg'])(a0));
8349
-
8350
- var _run_cli = (Module['_run_cli'] = () =>
8351
- (_run_cli = Module['_run_cli'] = wasmExports['run_cli'])());
8352
-
8353
9100
  var _wasm_set_sapi_name = (Module['_wasm_set_sapi_name'] = (a0) =>
8354
9101
  (_wasm_set_sapi_name = Module['_wasm_set_sapi_name'] =
8355
9102
  wasmExports['wasm_set_sapi_name'])(a0));
@@ -8358,6 +9105,13 @@ export function init(RuntimeName, PHPLoader) {
8358
9105
  (_wasm_set_phpini_path = Module['_wasm_set_phpini_path'] =
8359
9106
  wasmExports['wasm_set_phpini_path'])(a0));
8360
9107
 
9108
+ var _wasm_add_cli_arg = (Module['_wasm_add_cli_arg'] = (a0) =>
9109
+ (_wasm_add_cli_arg = Module['_wasm_add_cli_arg'] =
9110
+ wasmExports['wasm_add_cli_arg'])(a0));
9111
+
9112
+ var _run_cli = (Module['_run_cli'] = () =>
9113
+ (_run_cli = Module['_run_cli'] = wasmExports['run_cli'])());
9114
+
8361
9115
  var _wasm_add_SERVER_entry = (Module['_wasm_add_SERVER_entry'] = (a0, a1) =>
8362
9116
  (_wasm_add_SERVER_entry = Module['_wasm_add_SERVER_entry'] =
8363
9117
  wasmExports['wasm_add_SERVER_entry'])(a0, a1));
@@ -8430,6 +9184,16 @@ export function init(RuntimeName, PHPLoader) {
8430
9184
  var _wasm_free = (Module['_wasm_free'] = (a0) =>
8431
9185
  (_wasm_free = Module['_wasm_free'] = wasmExports['wasm_free'])(a0));
8432
9186
 
9187
+ var _wasm_get_end_offset = (Module['_wasm_get_end_offset'] = (a0) =>
9188
+ (_wasm_get_end_offset = Module['_wasm_get_end_offset'] =
9189
+ wasmExports['wasm_get_end_offset'])(a0));
9190
+
9191
+ var _wasm_trace = (Module['_wasm_trace'] = (a0, a1) =>
9192
+ (_wasm_trace = Module['_wasm_trace'] = wasmExports['wasm_trace'])(
9193
+ a0,
9194
+ a1
9195
+ ));
9196
+
8433
9197
  var ___funcs_on_exit = () =>
8434
9198
  (___funcs_on_exit = wasmExports['__funcs_on_exit'])();
8435
9199
 
@@ -8583,6 +9347,32 @@ export function init(RuntimeName, PHPLoader) {
8583
9347
  PHPLoader['free'] =
8584
9348
  typeof _free === 'function' ? _free : PHPLoader['_wasm_free'];
8585
9349
 
9350
+ if (typeof NODEFS === 'object') {
9351
+ // We override NODEFS.createNode() to add an `isSharedFS` flag to all NODEFS
9352
+ // nodes. This way we can tell whether file-locking is needed and possible
9353
+ // for an FS node, even if wrapped with PROXYFS.
9354
+ const originalCreateNode = NODEFS.createNode;
9355
+ NODEFS.createNode = function createNodeWithSharedFlag() {
9356
+ const node = originalCreateNode.apply(NODEFS, arguments);
9357
+ node.isSharedFS = true;
9358
+ return node;
9359
+ };
9360
+
9361
+ var originalHashAddNode = FS.hashAddNode;
9362
+ FS.hashAddNode = function hashAddNodeIfNotSharedFS(node) {
9363
+ if (
9364
+ typeof locking === 'object' &&
9365
+ locking?.is_shared_fs_node(node)
9366
+ ) {
9367
+ // Avoid caching shared VFS nodes so multiple instances
9368
+ // can access the same underlying filesystem without
9369
+ // conflicting caches.
9370
+ return;
9371
+ }
9372
+ return originalHashAddNode.apply(FS, arguments);
9373
+ };
9374
+ }
9375
+
8586
9376
  return PHPLoader;
8587
9377
 
8588
9378
  // Close the opening bracket from esm-prefix.js: