From f99f7fcab9cd379ac2603701e0c193a955570a8f Mon Sep 17 00:00:00 2001 From: Andy Green Date: Wed, 13 Aug 2025 09:39:45 +0100 Subject: [PATCH 1/3] feat: Add API test for lws_spawn Co-developed-by: Gemini 2.5 Pro --- lib/plat/windows/windows-spawn.c | 22 ++- .../api-tests/api-test-spawn/CMakeLists.txt | 23 +++ .../api-tests/api-test-spawn/main.c | 181 ++++++++++++++++++ 3 files changed, 216 insertions(+), 10 deletions(-) create mode 100644 minimal-examples-lowlevel/api-tests/api-test-spawn/CMakeLists.txt create mode 100644 minimal-examples-lowlevel/api-tests/api-test-spawn/main.c diff --git a/lib/plat/windows/windows-spawn.c b/lib/plat/windows/windows-spawn.c index cb489ab015..1ceace1e47 100644 --- a/lib/plat/windows/windows-spawn.c +++ b/lib/plat/windows/windows-spawn.c @@ -45,7 +45,7 @@ lws_spawn_sul_reap(struct lws_sorted_usec_list *sul) struct lws_spawn_piped *lsp = lws_container_of(sul, struct lws_spawn_piped, sul_reap); - lwsl_notice("%s: reaping spawn after last stdpipe, tries left %d\n", + lwsl_info("%s: reaping spawn after last stdpipe, tries left %d\n", __func__, lsp->reap_retry_budget); if (!lws_spawn_reap(lsp) && !lsp->pipes_alive) { if (--lsp->reap_retry_budget) { @@ -269,7 +269,7 @@ windows_pipe_poll_hack(lws_sorted_usec_list_t *sul) if (!PeekNamedPipe(lsp->pipe_fds[LWS_STDOUT][0], &c, 1, &br, NULL, NULL)) { - lwsl_notice("%s: stdout pipe errored\n", __func__); + // lwsl_notice("%s: stdout pipe errored\n", __func__); CloseHandle(lsp->stdwsi[LWS_STDOUT]->desc.filefd); lsp->pipe_fds[LWS_STDOUT][0] = NULL; lsp->stdwsi[LWS_STDOUT]->desc.filefd = NULL; @@ -277,7 +277,7 @@ windows_pipe_poll_hack(lws_sorted_usec_list_t *sul) lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC); if (lsp->stdwsi[LWS_STDIN]) { - lwsl_notice("%s: closing stdin from stdout close\n", + lwsl_info("%s: closing stdin from stdout close\n", __func__); CloseHandle(lsp->stdwsi[LWS_STDIN]->desc.filefd); wsi = lsp->stdwsi[LWS_STDIN]; @@ -308,7 +308,7 @@ windows_pipe_poll_hack(lws_sorted_usec_list_t *sul) if (!PeekNamedPipe(lsp->pipe_fds[LWS_STDERR][0], &c, 1, &br, NULL, NULL)) { - lwsl_notice("%s: stderr pipe errored\n", __func__); + lwsl_info("%s: stderr pipe errored\n", __func__); CloseHandle(wsi1->desc.filefd); /* * Assume is stderr still extant on entry, lsp can't @@ -400,7 +400,7 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i) if (!SetHandleInformation(&lsp->pipe_fds[n][!n], HANDLE_FLAG_INHERIT, 0)) { - lwsl_err("%s: SetHandleInformation() failed\n", __func__); + lwsl_info("%s: SetHandleInformation() failed\n", __func__); //goto bail1; } } @@ -452,10 +452,10 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i) i->opt_parent->child_list = lsp->stdwsi[n]; } - lwsl_notice("%s: pipe handles in %p, out %p, err %p\n", __func__, - lsp->stdwsi[LWS_STDIN]->desc.sockfd, - lsp->stdwsi[LWS_STDOUT]->desc.sockfd, - lsp->stdwsi[LWS_STDERR]->desc.sockfd); + // lwsl_notice("%s: pipe handles in %p, out %p, err %p\n", __func__, + // lsp->stdwsi[LWS_STDIN]->desc.sockfd, + // lsp->stdwsi[LWS_STDOUT]->desc.sockfd, + // lsp->stdwsi[LWS_STDERR]->desc.sockfd); /* * Windows nonblocking pipe handling is a mess that is unable @@ -484,7 +484,9 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i) n++; } - puts(cli); + if (p > cli && p[-1] == ' ') + *(--p) = '\0'; + // puts(cli); memset(&pi, 0, sizeof(pi)); memset(&si, 0, sizeof(si)); diff --git a/minimal-examples-lowlevel/api-tests/api-test-spawn/CMakeLists.txt b/minimal-examples-lowlevel/api-tests/api-test-spawn/CMakeLists.txt new file mode 100644 index 0000000000..1154347ead --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-spawn/CMakeLists.txt @@ -0,0 +1,23 @@ +project(lws-api-test-spawn C) +cmake_minimum_required(VERSION 3.10) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SRCS main.c) + +set(requirements 1) +require_lws_config(LWS_WITH_SPAWN 1 requirements) + +if (requirements) + + add_executable(${PROJECT_NAME} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${PROJECT_NAME} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${PROJECT_NAME} websockets_shared) + else() + target_link_libraries(${PROJECT_NAME} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() +endif() diff --git a/minimal-examples-lowlevel/api-tests/api-test-spawn/main.c b/minimal-examples-lowlevel/api-tests/api-test-spawn/main.c new file mode 100644 index 0000000000..09e8689c03 --- /dev/null +++ b/minimal-examples-lowlevel/api-tests/api-test-spawn/main.c @@ -0,0 +1,181 @@ +/* + * lws-api-test-spawn + * + * Written in 2010-2022 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * The test spawns a child process and captures the stdout, which is checked + * to not be empty. + */ + +#include +#include +#include + +static int interrupted, result = 1; +static struct lws_context *context; + +struct spawn_test { + lws_sorted_usec_list_t sul_timeout; + struct lws_spawn_piped *lsp; +}; + +#if defined(WIN32) +static const char * const exec_array[] = { "cmd.exe", "/c", "echo lws-test-spawn-data", NULL }; +static const char *expected_stdout = "lws-test-spawn-data\r\n"; +#else +static const char * const exec_array[] = { "/bin/sh", "-c", "echo lws-test-spawn-data", NULL }; +static const char *expected_stdout = "lws-test-spawn-data\n"; +#endif + +static char captured_stdout[128]; +static size_t captured_stdout_len; + +static void +timeout_cb(lws_sorted_usec_list_t *sul) +{ + struct spawn_test *st = lws_container_of(sul, struct spawn_test, sul_timeout); + lwsl_err("%s: test timed out\n", __func__); + /* lsp may be NULL if the spawn failed */ + if (st->lsp) + lws_spawn_piped_kill_child_process(st->lsp); + interrupted = 1; + lws_cancel_service(context); +} + +static void +reap_cb(void *opaque, lws_usec_t *accounting, siginfo_t *si, int we_killed_him) +{ + lwsl_user("%s: child process exited\n", __func__); + + if (captured_stdout_len != strlen(expected_stdout) || + strncmp(captured_stdout, expected_stdout, captured_stdout_len)) { + lwsl_err("Captured stdout mismatch. Got:\n"); + lwsl_hexdump_err(captured_stdout, captured_stdout_len); + lwsl_err("Expected:\n"); + lwsl_hexdump_err(expected_stdout, strlen(expected_stdout)); + } else { + lwsl_user("Captured expected stdout\n"); + result = 0; /* PASS */ + } + + interrupted = 1; + lws_cancel_service(context); +} + +static int +protocol_test_spawn_cb(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct spawn_test *st = lws_get_opaque_user_data(wsi); + char buf[4096]; + ssize_t ilen; + + switch (reason) { + case LWS_CALLBACK_RAW_RX_FILE: + +#if defined(WIN32) + { + DWORD rb; + if (!ReadFile((HANDLE)lws_get_socket_fd(wsi), buf, sizeof(buf), &rb, NULL)) { + lwsl_debug("%s: read on stdwsi failed\n", __func__); + return -1; + } + ilen = rb; + } +#else + ilen = read((int)(intptr_t)lws_get_socket_fd(wsi), buf, sizeof(buf)); + if (ilen < 1) { + lwsl_debug("%s: read on stdwsi failed\n", __func__); + return -1; + } +#endif + + + if (ilen > 0 && captured_stdout_len < sizeof(captured_stdout) - 1) { + size_t avail = sizeof(captured_stdout) - 1 - captured_stdout_len; + if (len > avail) + len = avail; + memcpy(captured_stdout + captured_stdout_len, buf, (size_t)ilen); + captured_stdout_len += (size_t)ilen; + captured_stdout[captured_stdout_len] = '\0'; + } + break; + + case LWS_CALLBACK_RAW_CLOSE_FILE: + lws_spawn_stdwsi_closed(st->lsp, wsi); + break; + + default: + break; + } + + return 0; +} + +static struct lws_protocols protocols[] = { + { + .name = "lws-test-spawn", + .callback = protocol_test_spawn_cb, + }, + LWS_PROTOCOL_LIST_TERM +}; + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_spawn_piped_info pinfo; + struct spawn_test st; + const char *env[] = { + "PATH=/usr/local/bin:/usr/bin:/bin", + "LANG=en_US.UTF-8", + NULL + }; + + memset(&pinfo, 0, sizeof(pinfo)); + memset(&info, 0, sizeof info); + memset(&st, 0, sizeof st); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS API selftest: spawn\n"); + + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = protocols; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + pinfo.env_array = env; + pinfo.exec_array = exec_array; + pinfo.protocol_name = protocols[0].name; + pinfo.reap_cb = reap_cb; + pinfo.plsp = &st.lsp; + pinfo.timeout_us = 10 * LWS_US_PER_SEC; + pinfo.tsi = 0; + pinfo.vh = lws_get_vhost_by_name(context, "default"); + pinfo.opaque = &st; + + st.lsp = lws_spawn_piped(&pinfo); + if (!st.lsp) { + lwsl_err("lws_spawn_piped failed\n"); + goto bail; + } + + lws_sul_schedule(context, 0, &st.sul_timeout, timeout_cb, 15 * LWS_US_PER_SEC); + + while (lws_service(context, 0) >= 0 && !interrupted) + ; + +bail: + lws_sul_cancel(&st.sul_timeout); + lws_context_destroy(context); + + lwsl_user("Completed: %s\n", result ? "FAIL" : "PASS"); + + return result; +} From 25f36ef9283377251458dfabb99c7cc4f232a26c Mon Sep 17 00:00:00 2001 From: Andy Green Date: Wed, 13 Aug 2025 17:30:53 +0100 Subject: [PATCH 2/3] lws_spawn: windows plat fixes --- lib/plat/windows/windows-spawn.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/plat/windows/windows-spawn.c b/lib/plat/windows/windows-spawn.c index 1ceace1e47..b7dc714af8 100644 --- a/lib/plat/windows/windows-spawn.c +++ b/lib/plat/windows/windows-spawn.c @@ -527,6 +527,9 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i) lws_sul_schedule(context, i->tsi, &lsp->sul, lws_spawn_timeout, i->timeout_us); + if (i->plsp) + *(i->plsp) = lsp; + return lsp; bail3: @@ -562,14 +565,21 @@ lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp, struct lws *wsi) assert(lsp); lsp->pipes_alive--; - lwsl_debug("%s: pipes alive %d\n", __func__, lsp->pipes_alive); - if (!lsp->pipes_alive) + lwsl_wsi_warn(wsi, "stdxxx down: pipes alive %d\n", lsp->pipes_alive); + if (!lsp->pipes_alive) { + lwsl_wsi_warn(wsi, "Scheduling reap"); lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul_reap, lws_spawn_sul_reap, 1); + } for (n = 0; n < 3; n++) - if (lsp->stdwsi[n] == wsi) + if (lsp->stdwsi[n] == wsi) { + lwsl_wsi_warn(wsi, "Identified stxxx wsi in lsp"); lsp->stdwsi[n] = NULL; + return; + } + + lwsl_wsi_warn(wsi, "!!! unable to find stdwsi in lsp %p", lsp); } int From af0906fcba42060e5ff78b15ad3137cd66f77b2b Mon Sep 17 00:00:00 2001 From: makejian Date: Thu, 14 Aug 2025 11:14:38 +0800 Subject: [PATCH 3/3] lws/openssl-wrapper: Align ssl_handshake and openssl standard return values standard return code of ssl_handshake list following: 0 The TLS/SSL handshake was not successful but was shut down controlled and by the specifications of the TLS/SSL protocol. Call SSL_get_error() with the return value ret to find out the reason. 1 The TLS/SSL handshake was successfully completed, a TLS/SSL connection has been established. <0 The TLS/SSL handshake was not successful because a fatal error occurred either at the protocol level or a connection failure occurred. The shutdown was not clean. It can also occur of action is need to continue the operation for non-blocking BIOs. Call SSL_get_error() with the return value ret to find out the reason. so ssl_error_read/write should return -1. Signed-off-by: makejian --- lib/tls/mbedtls/wrapper/platform/ssl_pm.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c index ef5eab5e0d..3c53a46f53 100755 --- a/lib/tls/mbedtls/wrapper/platform/ssl_pm.c +++ b/lib/tls/mbedtls/wrapper/platform/ssl_pm.c @@ -338,14 +338,18 @@ int ssl_pm_handshake(SSL *ssl) /* * OpenSSL return codes: - * 0 = did not complete, but may be retried + * 0 = The TLS/SSL handshake was not successful but was shut down + * controlled and by the specifications of the TLS/SSL protocol. * 1 = successfully completed - * <0 = death + * <0 = The TLS/SSL handshake was not successful because a fatal error + * occurred either at the protocol level or a connection failure + * occurred. */ if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - ssl->err = ret; + ssl->err = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? SSL_ERROR_WANT_READ : + SSL_ERROR_WANT_WRITE; SSL_DEBUG(SSL_PLATFORM_ERROR_LEVEL, "mbedtls_ssl_handshake() return -0x%x", -ret); - return 0; /* OpenSSL: did not complete but may be retried */ + return -1; } if (ret == 0) { /* successful */ @@ -359,7 +363,7 @@ int ssl_pm_handshake(SSL *ssl) lwsl_info("%s: ambiguous EAGAIN taken as WANT_READ\n", __func__); ssl->err = ret == MBEDTLS_ERR_SSL_WANT_READ; - return 0; + return -1; } lwsl_info("%s: mbedtls_ssl_handshake() returned -0x%x\n", __func__, -ret);