From d33de2f4eb50394fb73562d88e0bee4bd194e45c Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 2 Sep 2025 10:59:38 +0200 Subject: [PATCH] feat: add `cli_set_process_title()` and `cli_get_process_title()` --- frankenphp.c | 184 +++++++++++++++++++++++++++++++++++++++++++ frankenphp.stub.php | 3 + frankenphp_arginfo.h | 15 +++- 3 files changed, 201 insertions(+), 1 deletion(-) diff --git a/frankenphp.c b/frankenphp.c index 3e516ffd48..4a6aca0dab 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -360,6 +360,183 @@ PHP_FUNCTION(frankenphp_request_headers) { } /* }}} */ +/* Process title implementation borrowed from sapi/cli/ps_title.c */ +#ifdef PHP_WIN32 +#include "win32/codepage.h" +#include +#include +#endif + +#ifdef HAVE_SETPROCTITLE +#define PS_USE_SETPROCTITLE +#elif defined(__linux__) || defined(_AIX) || defined(__sgi) || \ + (defined(sun) && !defined(BSD)) || defined(ultrix) || defined(__osf__) || \ + defined(__APPLE__) +#define PS_USE_CLOBBER_ARGV +#elif defined(PHP_WIN32) +#define PS_USE_WIN32 +#else +#define PS_USE_NONE +#endif + +typedef enum { + PS_TITLE_SUCCESS = 0, + PS_TITLE_NOT_AVAILABLE = 1, + PS_TITLE_NOT_INITIALIZED = 2, + PS_TITLE_BUFFER_NOT_AVAILABLE = 3, + PS_TITLE_WINDOWS_ERROR = 4, + PS_TITLE_TOO_LONG = 5, +} ps_title_status; + +static char *ps_buffer = NULL; +static size_t ps_buffer_size = 0; +static size_t ps_buffer_cur_len = 0; + +static ps_title_status is_ps_title_available(void) { +#ifdef PS_USE_NONE + return PS_TITLE_NOT_AVAILABLE; +#else + if (!ps_buffer) { + return PS_TITLE_NOT_INITIALIZED; + } + return PS_TITLE_SUCCESS; +#endif +} + +static const char *ps_title_errno(ps_title_status rc) { + switch (rc) { + case PS_TITLE_SUCCESS: + return "Success"; + case PS_TITLE_NOT_AVAILABLE: + return "Not available on this platform"; + case PS_TITLE_NOT_INITIALIZED: + return "Not initialized"; + case PS_TITLE_BUFFER_NOT_AVAILABLE: + return "Buffer not available"; + case PS_TITLE_WINDOWS_ERROR: + return "Windows error"; + case PS_TITLE_TOO_LONG: + return "Title too long"; + default: + return "Unknown error"; + } +} + +static ps_title_status set_ps_title(const char *title, size_t title_len) { +#ifdef PS_USE_NONE + return PS_TITLE_NOT_AVAILABLE; +#else + if (title_len >= ps_buffer_size) { + return PS_TITLE_TOO_LONG; + } + ps_title_status rc = is_ps_title_available(); + if (rc != PS_TITLE_SUCCESS) + return rc; + + memcpy(ps_buffer, title, title_len); + ps_buffer[title_len] = '\0'; + ps_buffer_cur_len = title_len; + +#ifdef PS_USE_SETPROCTITLE + setproctitle("%s", ps_buffer); +#endif +#ifdef PS_USE_WIN32 + { + wchar_t *ps_buffer_w = php_win32_cp_any_to_w(ps_buffer); + if (!ps_buffer_w || !SetConsoleTitleW(ps_buffer_w)) { + if (ps_buffer_w) + free(ps_buffer_w); + return PS_TITLE_WINDOWS_ERROR; + } + free(ps_buffer_w); + } +#endif + return PS_TITLE_SUCCESS; +#endif +} + +static ps_title_status get_ps_title(size_t *displen, const char **string) { + ps_title_status rc = is_ps_title_available(); + if (rc != PS_TITLE_SUCCESS) + return rc; + +#ifdef PS_USE_WIN32 + { + wchar_t ps_buffer_w[MAX_PATH]; + char *tmp; + + if (!(ps_buffer_cur_len = + GetConsoleTitleW(ps_buffer_w, (DWORD)sizeof(ps_buffer_w)))) { + return PS_TITLE_WINDOWS_ERROR; + } + + tmp = php_win32_cp_conv_w_to_any(ps_buffer_w, PHP_WIN32_CP_IGNORE_LEN, + &ps_buffer_cur_len); + if (!tmp) { + return PS_TITLE_WINDOWS_ERROR; + } + + ps_buffer_cur_len = ps_buffer_cur_len > ps_buffer_size - 1 + ? ps_buffer_size - 1 + : ps_buffer_cur_len; + memmove(ps_buffer, tmp, ps_buffer_cur_len); + free(tmp); + } +#endif + *displen = ps_buffer_cur_len; + *string = ps_buffer; + return PS_TITLE_SUCCESS; +} + +/* {{{ cli_set_process_title */ +PHP_FUNCTION(cli_set_process_title) { + char *title; + size_t title_len; + ps_title_status rc; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(title, title_len) + ZEND_PARSE_PARAMETERS_END(); + + if (!ps_buffer) { + ps_buffer_size = 256; /* Reasonable default */ + ps_buffer = malloc(ps_buffer_size); + if (ps_buffer) { + ps_buffer[0] = '\0'; + ps_buffer_cur_len = 0; + } + } + + rc = set_ps_title(title, title_len); + if (rc != PS_TITLE_SUCCESS) { + php_error_docref(NULL, E_WARNING, "cli_set_process_title had an error: %s", + ps_title_errno(rc)); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ cli_get_process_title */ +PHP_FUNCTION(cli_get_process_title) { + const char *title = NULL; + size_t length = 0; + ps_title_status rc; + + ZEND_PARSE_PARAMETERS_NONE(); + + rc = get_ps_title(&length, &title); + if (rc != PS_TITLE_SUCCESS) { + php_error_docref(NULL, E_WARNING, "cli_get_process_title had an error: %s", + ps_title_errno(rc)); + RETURN_NULL(); + } + + RETURN_STRINGL(title, length); +} +/* }}} */ + /* add_response_header and apache_response_headers are copied from * https://github.com/php/php-src/blob/master/sapi/cli/php_cli_server.c * Copyright (c) The PHP Group @@ -1119,6 +1296,13 @@ static void *execute_script_cli(void *arg) { php_embed_init(cli_argc, cli_argv); cli_register_file_handles(false); + +#if PHP_VERSION_ID < 80400 + zend_register_module_ex(&frankenphp_module); +#else + zend_register_module_ex(&frankenphp_module, MODULE_PERSISTENT); +#endif + zend_first_try { if (eval) { /* evaluate the cli_script as literal PHP code (php-cli -r "...") */ diff --git a/frankenphp.stub.php b/frankenphp.stub.php index 6c5a71cb5c..6f75c4d75d 100644 --- a/frankenphp.stub.php +++ b/frankenphp.stub.php @@ -32,3 +32,6 @@ function frankenphp_response_headers(): array|bool {} */ function apache_response_headers(): array|bool {} +function cli_set_process_title(string $title): bool {} + +function cli_get_process_title(): ?string {} diff --git a/frankenphp_arginfo.h b/frankenphp_arginfo.h index c1bd7b550a..587d37bc84 100644 --- a/frankenphp_arginfo.h +++ b/frankenphp_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 05ebde17137c559e891362fba6524fad1e0a2dfe */ + * Stub hash: 1b144396ea8609321ba7fc51b6c22f0d5635197d */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_frankenphp_handle_request, 0, 1, _IS_BOOL, 0) @@ -30,11 +30,22 @@ ZEND_END_ARG_INFO() #define arginfo_apache_response_headers arginfo_frankenphp_response_headers +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_cli_set_process_title, 0, 1, + _IS_BOOL, 0) +ZEND_ARG_TYPE_INFO(0, title, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_cli_get_process_title, 0, 0, + IS_STRING, 1) +ZEND_END_ARG_INFO() + ZEND_FUNCTION(frankenphp_handle_request); ZEND_FUNCTION(headers_send); ZEND_FUNCTION(frankenphp_finish_request); ZEND_FUNCTION(frankenphp_request_headers); ZEND_FUNCTION(frankenphp_response_headers); +ZEND_FUNCTION(cli_set_process_title); +ZEND_FUNCTION(cli_get_process_title); // clang-format off static const zend_function_entry ext_functions[] = { @@ -47,6 +58,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FALIAS(getallheaders, frankenphp_request_headers, arginfo_getallheaders) ZEND_FE(frankenphp_response_headers, arginfo_frankenphp_response_headers) ZEND_FALIAS(apache_response_headers, frankenphp_response_headers, arginfo_apache_response_headers) + ZEND_FE(cli_set_process_title, arginfo_cli_set_process_title) + ZEND_FE(cli_get_process_title, arginfo_cli_get_process_title) ZEND_FE_END }; // clang-format on