Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/php-wasm/compile/php/php_wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si
}

const success = returnCode === 0;
// @TODO: Do not reason about child_proc_by_fd. Just use pipes and detect
Copy link
Collaborator Author

@adamziel adamziel Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, PHP uses that distinction internally, so maybe it's not a problem at all? Although we operate at the kernel level here.

https://github.com/php/php-src/blob/004cb827501d1ddaf98daacb185a53e0816f78a7/main/streams/plain_wrapper.c#L476

if (data->is_process_pipe) {
	errno = 0;
	ret = pclose(data->file);

#if HAVE_SYS_WAIT_H
	if (WIFEXITED(ret)) {
		ret = WEXITSTATUS(ret);
	}
#endif
} else {
	ret = fclose(data->file);
	data->file = NULL;
}

// when the other end of the pipe is closed.
const failure = (
++retries > maxRetries ||
!(fd in PHPWASM.child_proc_by_fd) ||
Expand All @@ -360,7 +362,16 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si
// If the failure is due to a timeout, return 0 to indicate that we
// reached EOF. Otherwise, propagate the error code.
wakeUp(returnCode === 6 ? 0 : returnCode);
} else if (
returnCode === ERRNO_CODES.EWOULDBLOCK &&
stream.flags & locking.O_NONBLOCK
) {
// Non-blocking stream with no data available yet – return immediately.
HEAPU32[pnum >> 2] = 0;
wakeUp(returnCode);
} else {
// It's a blocking stream and we Blocking stream with no data available yet.
// Let's poll up to a timeout.
setTimeout(poll, interval);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ const LibraryForFileLocking = {
F_RDLCK: 0,
F_WRLCK: 1,
F_UNLCK: 2,
/**
* @see fcntl.c:
* https://github.com/torvalds/linux/blob/a79a588fc1761dc12a3064fc2f648ae66cea3c5a/fs/fcntl.c#L37
*/
O_APPEND: Number('{{{cDefs.O_APPEND}}}'),
O_NONBLOCK: Number('{{{cDefs.O_NONBLOCK}}}'),
SETFL_MASK:
Number('{{{cDefs.O_APPEND}}}') |
Number('{{{cDefs.O_NONBLOCK}}}')
// These macros are not defined in Emscripten at the time of writing:
// emscripten_O_NDELAY |
// emscripten_O_DIRECT |
// emscripten_O_NOATIME
,
lockStateToFcntl: {
shared: 0,
exclusive: 1,
Expand All @@ -40,7 +54,10 @@ const LibraryForFileLocking = {
}

// Handle PROXYFS nodes which wrap other nodes.
if (!node?.mount?.opts?.fs?.lookupPath || !node?.mount?.type?.realPath) {
if (
!node?.mount?.opts?.fs?.lookupPath ||
!node?.mount?.type?.realPath
) {
return false;
}

Expand All @@ -50,7 +67,8 @@ const LibraryForFileLocking = {
}
const vfsPath = node.mount.type.realPath(node);
try {
const underlyingNode = node.mount.opts.fs.lookupPath(vfsPath)?.node;
const underlyingNode =
node.mount.opts.fs.lookupPath(vfsPath)?.node;
return !!underlyingNode?.isSharedFS;
} catch (e) {
return false;
Expand Down Expand Up @@ -116,11 +134,14 @@ const LibraryForFileLocking = {
SYSCALLS.varargs = varargs;

// These constants are replaced by Emscripten during the build process
const emscripten_F_SETFL = Number('{{{cDefs.F_SETFL}}}');
const emscripten_F_GETLK = Number('{{{cDefs.F_GETLK}}}');
const emscripten_F_SETLK = Number('{{{cDefs.F_SETLK}}}');
const emscripten_F_SETLKW = Number('{{{cDefs.F_SETLKW}}}');
const emscripten_SEEK_SET = Number('{{{cDefs.SEEK_SET}}}');



// NOTE: With the exception of l_type, these offsets are not exposed to
// JS by Emscripten, so we hardcode them here.
const emscripten_flock_l_type_offset = 0;
Expand Down Expand Up @@ -511,6 +532,38 @@ const LibraryForFileLocking = {
// because it is a known errno for a failed F_SETLKW command.
return -ERRNO_CODES.EDEADLK;
}
case emscripten_F_SETFL: {
/**
* Overrides the core Emscripten implementation to reflect what
* fcntl does in linux kernel. This implementation is still missing
* a bunch of nuance, but, unlike the core Emscripten implementation,
* it overrides the stream flags while preserving non-stream flags.
*
* @see fcntl.c:
* https://github.com/torvalds/linux/blob/a79a588fc1761dc12a3064fc2f648ae66cea3c5a/fs/fcntl.c#L39
*/
const arg = SYSCALLS.get();
const stream = SYSCALLS.getStreamFromFD(fd);

// Get current flags
const currentFlags = stream.flags;

// Required for strict SunOS emulation
if (emscripten_O_NONBLOCK !== emscripten_O_NDELAY) {
if (arg & emscripten_O_NDELAY) {
arg |= emscripten_O_NONBLOCK;
}
}

// Pipe packetized mode is controlled by O_DIRECT flag
// We don't have S_ISFIFO or FMODE_CAN_ODIRECT checks in our implementation
// so we skip this validation

// Update the stream flags
stream.flags = (arg & locking.SETFL_MASK) | (currentFlags & ~locking.SETFL_MASK);

return 0;
}
default:
return _builtin_fcntl64(fd, cmd, varargs);
}
Expand Down Expand Up @@ -683,17 +736,14 @@ const LibraryForFileLocking = {
js_release_file_locks: async function js_release_file_locks() {
_js_wasm_trace('js_release_file_locks()');
const pid = PHPLoader.processId;
if(pid && PHPLoader.fileLockManager)
{
return await PHPLoader.fileLockManager
.releaseLocksForProcess(pid)
.then(() => {
_js_wasm_trace('js_release_file_locks succeeded');
})
.catch((e) => {
_js_wasm_trace('js_release_file_locks error %s', e);
});
}
return await PHPLoader.fileLockManager
.releaseLocksForProcess(pid)
.then(() => {
_js_wasm_trace('js_release_file_locks succeeded');
})
.catch((e) => {
_js_wasm_trace('js_release_file_locks error %s', e);
});
},
};

Expand Down
Loading