Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
23 changes: 9 additions & 14 deletions packages/php-wasm/compile/php/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -455,19 +455,9 @@ RUN /root/replace.sh 's/ret = read/ret = wasm_read/g' /root/php-src/main/streams
# to an unused identifier "php_exec_old", and then we mark php_exec as extern.
RUN /root/replace.sh 's/PHPAPI int php_exec(.+)$/PHPAPI extern int php_exec\1; int php_exec_old\1/g' /root/php-src/ext/standard/exec.c

# Provide a custom implementation of the VCWD_POPEN() function that handles spawning
# the process inside PHP_FUNCTION(popen).
RUN /root/replace.sh 's/#define VCWD_POPEN.+/#define VCWD_POPEN(command, type) wasm_popen(command,type)/g' /root/php-src/Zend/zend_virtual_cwd.h
RUN echo 'extern FILE *wasm_popen(const char *cmd, const char *mode);' >> /root/php-src/Zend/zend_virtual_cwd.h

# Patch mail.c for Emscripten compatibility:
# 1. Use VCWD_POPEN (routed through wasm_popen) instead of raw popen(),
# so that PHP's mail() function goes through our JS spawn handler.
# 2. Use wasm_pclose() instead of pclose() so we wait for the spawned
# process to finish and get a correct exit code. Libc's pclose() doesn't
# work with FILE handles created by wasm_popen (fdopen).
RUN sed -i '1i #include <stdio.h>\nextern int wasm_pclose(FILE *fp);' /root/php-src/ext/standard/mail.c
RUN perl -pi.bak -e 's/\bpopen\s*\(\s*sendmail_cmd\s*,/VCWD_POPEN(sendmail_cmd,/g; s/\bpclose\s*\(\s*sendmail\s*\)/wasm_pclose(sendmail)/g' /root/php-src/ext/standard/mail.c
# popen() and pclose() are redirected to wasm_popen/wasm_pclose at link
# time via -Wl,--wrap=popen and -Wl,--wrap=pclose (see linker flags below).
# This covers all call sites including PHP's popen() function and mail().

# Provide a custom implementation of the shutdown() function.
RUN perl -pi.bak -e $'s/(\s+)shutdown\(/$1 wasm_shutdown(/g' /root/php-src/sapi/cli/php_cli_server.c
Expand Down Expand Up @@ -1676,6 +1666,8 @@ RUN export ASYNCIFY_IMPORTS=$'[\n\
"wasm_php_stream_read",\
"wasm_pclose",\
"wasm_popen",\
"__wrap_pclose",\
"__wrap_popen",\
"wasm_read",\
"wasm_sapi_handle_request",\
"wasm_sapi_request_shutdown",\
Expand Down Expand Up @@ -2040,6 +2032,7 @@ RUN export ASYNCIFY_IMPORTS=$'[\n\
"zif_phar_file_get_contents",\
"zif_phar_fopen",\
"zif_mail",\
"zif_pclose",\
"zif_popen",\
"zif_post_message_to_js",\
"zif_preg_replace_callback",\
Expand Down Expand Up @@ -2246,7 +2239,7 @@ RUN set -euxo pipefail; \
source /root/emsdk/emsdk_env.sh; \
if [ "$WITH_JSPI" = "yes" ]; then \
# Both imports and exports are required for inter-module communication with wrapped methods, e.g., wasm_recv.
export ASYNCIFY_FLAGS=" -s ASYNCIFY=2 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -sJSPI_IMPORTS=js_open_process,js_fd_read,js_waitpid,js_process_status,js_create_input_device,wasm_setsockopt,wasm_shutdown,wasm_close,wasm_recv,wasm_connect,__syscall_fcntl64,js_flock,js_release_file_locks,js_waitpid -sJSPI_EXPORTS=php_wasm_init,wasm_sleep,wasm_read,emscripten_sleep,wasm_sapi_handle_request,wasm_sapi_request_shutdown,wasm_poll_socket,wrap_select,__wrap_select,select,php_pollfd_for,fflush,wasm_popen,wasm_pclose,wasm_read,wasm_php_exec,run_cli,wasm_recv,wasm_connect,__wasm_call_ctors,__errno_location,__funcs_on_exit -s EXPORTED_RUNTIME_METHODS=HEAPU32,HEAPU8,ccall,PROXYFS,wasmExports "; \
export ASYNCIFY_FLAGS=" -s ASYNCIFY=2 -sSUPPORT_LONGJMP=wasm -fwasm-exceptions -sJSPI_IMPORTS=js_open_process,js_fd_read,js_waitpid,js_process_status,js_create_input_device,wasm_setsockopt,wasm_shutdown,wasm_close,wasm_recv,wasm_connect,__syscall_fcntl64,js_flock,js_release_file_locks,js_waitpid -sJSPI_EXPORTS=php_wasm_init,wasm_sleep,wasm_read,emscripten_sleep,wasm_sapi_handle_request,wasm_sapi_request_shutdown,wasm_poll_socket,wrap_select,__wrap_select,select,php_pollfd_for,fflush,wasm_popen,wasm_pclose,__wrap_popen,__wrap_pclose,wasm_read,wasm_php_exec,run_cli,wasm_recv,wasm_connect,__wasm_call_ctors,__errno_location,__funcs_on_exit -s EXPORTED_RUNTIME_METHODS=HEAPU32,HEAPU8,ccall,PROXYFS,wasmExports "; \
echo '#define PLAYGROUND_JSPI 1' > /root/php_wasm_asyncify.h; \
else \
export ASYNCIFY_FLAGS=" -s ASYNCIFY=1 -s ASYNCIFY_IGNORE_INDIRECT=1 -s EXPORTED_RUNTIME_METHODS=HEAPU32,HEAPU8,ccall,PROXYFS,wasmExports,UTF8ToString,lengthBytesUTF8,stringToUTF8 $(cat /root/.emcc-php-asyncify-flags) "; \
Expand Down Expand Up @@ -2302,6 +2295,8 @@ RUN set -euxo pipefail; \
-Wl,@/root/.wasm-exports \
-Wl,--wrap=usleep \
-Wl,--wrap=select \
-Wl,--wrap=popen \
-Wl,--wrap=pclose \
/root/lib/libphp.a \
/root/proc_open.c \
/root/php_wasm.c \
Expand Down
43 changes: 26 additions & 17 deletions packages/php-wasm/compile/php/php_wasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,9 @@ EM_JS(__wasi_errno_t, js_fd_read, (__wasi_fd_t fd, const __wasi_iovec_t *iov, si
});
extern int __wasi_syscall_ret(__wasi_errno_t code);

// Exit code of the last exited child process call.
int wasm_pclose_ret = -1;

// PID of the last process spawned by wasm_popen("w").
// Used by wasm_pclose to wait for the process to finish.
static int wasm_popen_last_pid = -1;
extern void js_popen_set_pid_for_fd(int fd, int pid);
extern int js_popen_get_pid_for_fd(int fd);
extern void js_popen_clear_pid_for_fd(int fd);

/**
* Passes a message to the JavaScript module and writes the response
Expand Down Expand Up @@ -505,7 +502,6 @@ EMSCRIPTEN_KEEPALIVE FILE *wasm_popen(const char *cmd, const char *mode)
char *file_path = js_popen_to_file(cmd, mode, &last_exit_code);
fp = fopen(file_path, mode);
FG(pclose_ret) = last_exit_code;
wasm_pclose_ret = last_exit_code;
}
else if (*mode == 'w')
{
Expand Down Expand Up @@ -548,7 +544,7 @@ EMSCRIPTEN_KEEPALIVE FILE *wasm_popen(const char *cmd, const char *mode)
descv[1] = stdout;
descv[2] = stderr;

wasm_popen_last_pid = js_open_process(
int pid = js_open_process(
cmd,
NULL,
0,
Expand All @@ -559,6 +555,7 @@ EMSCRIPTEN_KEEPALIVE FILE *wasm_popen(const char *cmd, const char *mode)
0,
0
);
js_popen_set_pid_for_fd(fileno(fp), pid);

efree(stdin);
efree(stdout);
Expand All @@ -579,29 +576,41 @@ EMSCRIPTEN_KEEPALIVE FILE *wasm_popen(const char *cmd, const char *mode)
/**
* Close a FILE* created by wasm_popen and wait for the spawned process
* to exit. Returns the process exit code, or -1 on error.
*
* @TODO wasm_popen_last_pid and wasm_pclose_ret are single globals,
* so concurrent writable popen() calls will clobber each other's
* PID and exit code. Safe today because mail() is the only caller
* and it does a strict open-write-close sequence, but a proper fix
* would stash both in a table keyed by fd.
*/
extern int js_waitpid(int pid, int *exitcode);

EMSCRIPTEN_KEEPALIVE int wasm_pclose(FILE *fp)
{
int pid = wasm_popen_last_pid;
int pid = js_popen_get_pid_for_fd(fileno(fp));
js_popen_clear_pid_for_fd(fileno(fp));
fclose(fp);
if (pid < 0) {
return -1;
}
int wstatus = 0;
js_waitpid(pid, &wstatus);
wasm_pclose_ret = wstatus;
FG(pclose_ret) = wstatus;
return wstatus;
}

/**
* Linker-level wrappers so every call to popen()/pclose() in the
* compiled C code is redirected here via -Wl,--wrap=popen/pclose.
*/
FILE *__wrap_popen(const char *cmd, const char *mode)
{
return wasm_popen(cmd, mode);
}

extern int __real_pclose(FILE *fp);
int __wrap_pclose(FILE *fp)
{
if (js_popen_get_pid_for_fd(fileno(fp)) >= 0) {
return wasm_pclose(fp);
}
return __real_pclose(fp);
}

/**
* Ship php_exec, the function powering the following PHP
* functions:
Expand Down Expand Up @@ -767,7 +776,7 @@ EMSCRIPTEN_KEEPALIVE int wasm_php_exec(int type, const char *cmd, zval *array, z
pclose_return = php_stream_close(stream);
if (pclose_return == -1)
{
pclose_return = wasm_pclose_ret;
pclose_return = FG(pclose_ret);
}
efree(buf);

Expand Down
24 changes: 24 additions & 0 deletions packages/php-wasm/compile/php/phpwasm-emscripten-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,30 @@ const LibraryExample = {
});
},

js_popen_set_pid_for_fd: function (fd, pid) {
if (PHPWASM.processTable[pid]) {
PHPWASM.processTable[pid].fd = fd;
}
},

js_popen_get_pid_for_fd: function (fd) {
for (const pid in PHPWASM.processTable) {
if (PHPWASM.processTable[pid].fd === fd) {
return PHPWASM.processTable[pid].pid;
}
}
return -1;
},

js_popen_clear_pid_for_fd: function (fd) {
for (const pid in PHPWASM.processTable) {
if (PHPWASM.processTable[pid].fd === fd) {
delete PHPWASM.processTable[pid].fd;
return;
}
}
},

/**
* Shims unix shutdown(2) functionality for asynchronous:
* https://man7.org/linux/man-pages/man2/shutdown.2.html
Expand Down
Binary file modified packages/php-wasm/node-builds/8-4/asyncify/8_4_20/php_8_4.wasm
Binary file not shown.
Loading
Loading