Skip to content

Commit 4e888f4

Browse files
committed
spawn:pty
1 parent fa11f6c commit 4e888f4

11 files changed

Lines changed: 246 additions & 49 deletions

File tree

READMEs/README.coding.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,3 +1335,24 @@ the user-selected text message and attempts to pull in `/error.css` for styling.
13351335
If this file exists, it can be used to style the error page. See
13361336
https://libwebsockets.org/git/badrepo for an example of what can be done (
13371337
and https://libwebsockets.org/error.css for the corresponding css).
1338+
1339+
@section spawn Process Spawning and PTY routing
1340+
1341+
libwebsockets provides a cross-platform API for spawning child processes and
1342+
redirecting their standard streams (stdin, stdout, stderr) into the lws event loop
1343+
as wsi handles: `lws_spawn_piped`.
1344+
1345+
It is controlled by `struct lws_spawn_piped_info`. By default, the streams are
1346+
redirected via standard anonymous pipes.
1347+
1348+
However, if you wish to run a process that expects a terminal (for example, to
1349+
preserve ANSI color codes or other TTY-specific behaviors), you can set
1350+
`info.pty_mode = 1` before calling `lws_spawn_piped()`.
1351+
1352+
- On POSIX systems, `pty_mode` will allocate a pseudoterminal via `posix_openpt()`
1353+
and securely fuse both the child's stdout and stderr into the single PTY
1354+
master file descriptor.
1355+
- On Windows (Windows 10+), `pty_mode` will attempt to dynamically instantiate a
1356+
`CreatePseudoConsole` (ConPTY) handle and route the standard pipes through it. If
1357+
the host system does not support ConPTY, it will gracefully fall back to pipes
1358+
or fail cleanly.

include/libwebsockets/lws-misc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,7 @@ struct lws_spawn_piped_info {
14111411
const struct lws_role_ops *ops; /* NULL is raw file */
14121412

14131413
uint8_t disable_ctrlc;
1414+
uint8_t pty_mode;
14141415

14151416
const char *cgroup_name_suffix;
14161417
int *p_cgroup_ret;

lib/core-net/private-lib-core-net.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,7 @@ struct lws_spawn_piped {
10531053
lws_sorted_usec_list_t sul_poll;
10541054
FILETIME ft_create;
10551055
FILETIME ft_exit;
1056+
void *hPC; /* Pseudoconsole */
10561057
#else
10571058
pid_t child_pid;
10581059

lib/plat/unix/unix-spawn.c

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
#include <sys/types.h>
3232
#include <pwd.h>
3333
#include <grp.h>
34+
#include <stdlib.h>
35+
#include <fcntl.h>
36+
#include <termios.h>
3437

3538
#if defined(__linux__)
3639
#include <sys/stat.h>
@@ -452,9 +455,40 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
452455
/* create pipes for [stdin|stdout] and [stderr] */
453456

454457
for (n = 0; n < 3; n++) {
455-
if (pipe(lsp->pipe_fds[n]) == -1)
456-
goto bail1;
457-
if (lws_plat_apply_FD_CLOEXEC(lsp->pipe_fds[n][n == 0]))
458+
if (i->pty_mode && n != LWS_STDIN) {
459+
if (n == LWS_STDOUT) {
460+
int master = posix_openpt(O_RDWR | O_NOCTTY);
461+
if (master >= 0) {
462+
if (grantpt(master) == 0 && unlockpt(master) == 0) {
463+
char *slavename = ptsname(master);
464+
if (slavename) {
465+
int slave = open(slavename, O_RDWR | O_NOCTTY);
466+
if (slave >= 0) {
467+
struct termios t;
468+
tcgetattr(slave, &t);
469+
cfmakeraw(&t);
470+
tcsetattr(slave, TCSANOW, &t);
471+
lsp->pipe_fds[n][0] = master;
472+
lsp->pipe_fds[n][1] = slave;
473+
}
474+
}
475+
}
476+
}
477+
if (lsp->pipe_fds[n][0] == -1) {
478+
lwsl_err("%s: posix_openpt failed\n", __func__);
479+
goto bail1;
480+
}
481+
} else {
482+
/* STDERR: fuse into STDOUT's pty */
483+
lsp->pipe_fds[n][0] = -1; /* parent has no separate reader */
484+
lsp->pipe_fds[n][1] = lsp->pipe_fds[LWS_STDOUT][1]; /* child dup2s this */
485+
}
486+
} else {
487+
if (pipe(lsp->pipe_fds[n]) == -1)
488+
goto bail1;
489+
}
490+
491+
if (lsp->pipe_fds[n][0] >= 0 && lws_plat_apply_FD_CLOEXEC(lsp->pipe_fds[n][n == 0]))
458492
lwsl_info("%s: FD_CLOEXEC didn't stick\n", __func__);
459493
}
460494

@@ -466,6 +500,9 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
466500
/* create wsis for each stdin/out/err fd */
467501

468502
for (n = 0; n < 3; n++) {
503+
if (lsp->pipe_fds[n][n == 0] == -1)
504+
continue;
505+
469506
lsp->stdwsi[n] = lws_create_stdwsi(i->vh->context, i->tsi,
470507
i->ops ? i->ops : &role_ops_raw_file);
471508
if (!lsp->stdwsi[n]) {
@@ -508,6 +545,9 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
508545
*/
509546

510547
for (n = 0; n < 3; n++) {
548+
if (!lsp->stdwsi[n])
549+
continue;
550+
511551
if (context->event_loop_ops->sock_accept)
512552
if (context->event_loop_ops->sock_accept(lsp->stdwsi[n]))
513553
goto bail3;
@@ -528,13 +568,13 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
528568
goto bail3;
529569
if (lws_change_pollfd(lsp->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN))
530570
goto bail3;
531-
if (lws_change_pollfd(lsp->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN))
571+
if (lsp->stdwsi[LWS_STDERR] && lws_change_pollfd(lsp->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN))
532572
goto bail3;
533573

534574
lwsl_info("%s: fds in %d, out %d, err %d\n", __func__,
535575
lsp->stdwsi[LWS_STDIN]->desc.sockfd,
536576
lsp->stdwsi[LWS_STDOUT]->desc.sockfd,
537-
lsp->stdwsi[LWS_STDERR]->desc.sockfd);
577+
lsp->stdwsi[LWS_STDERR] ? lsp->stdwsi[LWS_STDERR]->desc.sockfd : -1);
538578

539579
#if defined(__linux__)
540580
if (i->cgroup_name_suffix && i->cgroup_name_suffix[0]) {
@@ -616,11 +656,17 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
616656
* "other" side of the pipe fds, ie, rd for stdin and wr for
617657
* stdout / stderr.
618658
*/
619-
for (n = 0; n < 3; n++)
659+
for (n = 0; n < 3; n++) {
620660
/* these guys didn't have any wsi footprint */
621-
close(lsp->pipe_fds[n][n != 0]);
661+
if (lsp->pipe_fds[n][n != 0] >= 0) {
662+
close(lsp->pipe_fds[n][n != 0]);
663+
/* if fused, prevent double close */
664+
if (i->pty_mode && n == LWS_STDOUT)
665+
lsp->pipe_fds[LWS_STDERR][1] = -1;
666+
}
667+
}
622668

623-
lsp->pipes_alive = 3;
669+
lsp->pipes_alive = i->pty_mode ? 2 : 3;
624670
lsp->created = lws_now_usecs();
625671

626672
lwsl_info("%s: lsp %p spawned PID %d\n", __func__, lsp,
@@ -744,7 +790,8 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
744790
bail3:
745791

746792
while (--n >= 0)
747-
__remove_wsi_socket_from_fds(lsp->stdwsi[n]);
793+
if (lsp->stdwsi[n])
794+
__remove_wsi_socket_from_fds(lsp->stdwsi[n]);
748795
bail2:
749796
for (n = 0; n < 3; n++)
750797
if (lsp->stdwsi[n])

lib/plat/windows/windows-spawn.c

Lines changed: 114 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@
2929
#include <strsafe.h>
3030
#include <Psapi.h>
3131

32+
#ifndef EXTENDED_STARTUPINFO_PRESENT
33+
#define EXTENDED_STARTUPINFO_PRESENT 0x00080000
34+
#endif
35+
36+
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
37+
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
38+
#endif
39+
40+
typedef VOID* HPCON;
41+
typedef HRESULT (WINAPI *PFN_CREATE_PSEUDO_CONSOLE)(COORD, HANDLE, HANDLE, DWORD, HPCON*);
42+
typedef VOID (WINAPI *PFN_CLOSE_PSEUDO_CONSOLE)(HPCON);
43+
typedef BOOL (WINAPI *PFN_INITIALIZE_PROC_THREAD_ATTRIBUTE_LIST)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD, PSIZE_T);
44+
typedef BOOL (WINAPI *PFN_UPDATE_PROC_THREAD_ATTRIBUTE)(LPPROC_THREAD_ATTRIBUTE_LIST, DWORD, DWORD_PTR, PVOID, SIZE_T, PVOID, PSIZE_T);
45+
typedef VOID (WINAPI *PFN_DELETE_PROC_THREAD_ATTRIBUTE_LIST)(LPPROC_THREAD_ATTRIBUTE_LIST);
46+
3247
void
3348
lws_spawn_timeout(struct lws_sorted_usec_list *sul)
3449
{
@@ -123,6 +138,17 @@ lws_spawn_piped_destroy(struct lws_spawn_piped **_lsp)
123138
lsp->hJob = NULL;
124139
}
125140

141+
if (lsp->hPC) {
142+
HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
143+
PFN_CLOSE_PSEUDO_CONSOLE pClosePseudoConsole = NULL;
144+
if (hKernel32) {
145+
pClosePseudoConsole = (PFN_CLOSE_PSEUDO_CONSOLE)GetProcAddress(hKernel32, "ClosePseudoConsole");
146+
if (pClosePseudoConsole)
147+
pClosePseudoConsole(lsp->hPC);
148+
}
149+
lsp->hPC = NULL;
150+
}
151+
126152
for (n = 0; n < 3; n++) {
127153
if (lsp->pipe_fds[n][!!(n == 0)]) {
128154
CloseHandle(lsp->pipe_fds[n][n == 0]);
@@ -421,23 +447,28 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
421447
sa.lpSecurityDescriptor = NULL;
422448

423449
for (n = 0; n < 3; n++) {
424-
DWORD waitmode = PIPE_NOWAIT;
450+
if (i->pty_mode && n == LWS_STDERR) {
451+
/* fuse stderr to stdout for pty */
452+
lsp->pipe_fds[n][0] = NULL;
453+
lsp->pipe_fds[n][1] = lsp->pipe_fds[LWS_STDOUT][1];
454+
} else {
455+
DWORD waitmode = PIPE_NOWAIT;
425456

426-
if (!CreatePipe(&lsp->pipe_fds[n][0], &lsp->pipe_fds[n][1],
427-
&sa, 0)) {
428-
lwsl_err("%s: CreatePipe() failed\n", __func__);
429-
goto bail1;
430-
}
457+
if (!CreatePipe(&lsp->pipe_fds[n][0], &lsp->pipe_fds[n][1],
458+
&sa, 0)) {
459+
lwsl_err("%s: CreatePipe() failed\n", __func__);
460+
goto bail1;
461+
}
431462

432-
SetNamedPipeHandleState(lsp->pipe_fds[1][0], &waitmode, NULL, NULL);
433-
SetNamedPipeHandleState(lsp->pipe_fds[2][0], &waitmode, NULL, NULL);
463+
if (n != LWS_STDIN)
464+
SetNamedPipeHandleState(lsp->pipe_fds[n][0], &waitmode, NULL, NULL);
434465

435-
/* don't inherit the pipe side that belongs to the parent */
466+
/* don't inherit the pipe side that belongs to the parent */
436467

437-
if (!SetHandleInformation(&lsp->pipe_fds[n][!n],
438-
HANDLE_FLAG_INHERIT, 0)) {
439-
lwsl_info("%s: SetHandleInformation() failed\n", __func__);
440-
//goto bail1;
468+
if (!SetHandleInformation(&lsp->pipe_fds[n][!n],
469+
HANDLE_FLAG_INHERIT, 0)) {
470+
// lwsl_info("%s: SetHandleInformation() failed\n", __func__);
471+
}
441472
}
442473
}
443474

@@ -459,12 +490,15 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
459490
lsp->stdwsi[n]->a.protocol = pcol;
460491
lsp->stdwsi[n]->a.opaque_user_data = i->opaque;
461492

493+
if (!lsp->pipe_fds[n][!n])
494+
continue;
495+
462496
lsp->stdwsi[n]->desc.filefd = lsp->pipe_fds[n][!n];
463497
lsp->stdwsi[n]->file_desc = 1;
464498

465499
lws_dll2_remove(&lsp->stdwsi[n]->pre_natal);
466500

467-
lwsl_debug("%s: lsp stdwsi %p: pipe idx %d -> fd %d / %d\n",
501+
lwsl_debug("%s: lsp stdwsi %p: pipe idx %d -> fd %p / %p\n",
468502
__func__, lsp->stdwsi[n], n,
469503
lsp->pipe_fds[n][!!(n == 0)],
470504
lsp->pipe_fds[n][!(n == 0)]);
@@ -524,22 +558,75 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
524558
*(--p) = '\0';
525559
// puts(cli);
526560

561+
STARTUPINFOEXA siex;
562+
STARTUPINFOA *psi;
563+
HMODULE hKernel32;
564+
PFN_CREATE_PSEUDO_CONSOLE pCreatePseudoConsole = NULL;
565+
PFN_INITIALIZE_PROC_THREAD_ATTRIBUTE_LIST pInitializeProcThreadAttributeList = NULL;
566+
PFN_UPDATE_PROC_THREAD_ATTRIBUTE pUpdateProcThreadAttribute = NULL;
567+
PFN_DELETE_PROC_THREAD_ATTRIBUTE_LIST pDeleteProcThreadAttributeList = NULL;
568+
SIZE_T attr_list_size = 0;
569+
DWORD creation_flags = CREATE_SUSPENDED;
570+
int pty_active = 0;
571+
527572
memset(&pi, 0, sizeof(pi));
528573
memset(&si, 0, sizeof(si));
574+
memset(&siex, 0, sizeof(siex));
575+
576+
if (i->pty_mode) {
577+
hKernel32 = GetModuleHandleW(L"kernel32.dll");
578+
if (hKernel32) {
579+
pCreatePseudoConsole = (PFN_CREATE_PSEUDO_CONSOLE)GetProcAddress(hKernel32, "CreatePseudoConsole");
580+
pInitializeProcThreadAttributeList = (PFN_INITIALIZE_PROC_THREAD_ATTRIBUTE_LIST)GetProcAddress(hKernel32, "InitializeProcThreadAttributeList");
581+
pUpdateProcThreadAttribute = (PFN_UPDATE_PROC_THREAD_ATTRIBUTE)GetProcAddress(hKernel32, "UpdateProcThreadAttribute");
582+
pDeleteProcThreadAttributeList = (PFN_DELETE_PROC_THREAD_ATTRIBUTE_LIST)GetProcAddress(hKernel32, "DeleteProcThreadAttributeList");
583+
}
584+
585+
if (pCreatePseudoConsole && pInitializeProcThreadAttributeList && pUpdateProcThreadAttribute && pDeleteProcThreadAttributeList) {
586+
COORD size;
587+
size.X = 80;
588+
size.Y = 24;
589+
590+
if (pCreatePseudoConsole(size, lsp->pipe_fds[LWS_STDIN][0], lsp->pipe_fds[LWS_STDOUT][1], 0, &lsp->hPC) == S_OK) {
591+
pty_active = 1;
592+
siex.StartupInfo.cb = sizeof(STARTUPINFOEXA);
593+
pInitializeProcThreadAttributeList(NULL, 1, 0, &attr_list_size);
594+
siex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)lws_malloc(attr_list_size, "ptyattr");
595+
pInitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &attr_list_size);
596+
pUpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, lsp->hPC, sizeof(HPCON), NULL, NULL);
597+
}
598+
}
599+
}
600+
601+
if (pty_active) {
602+
psi = (STARTUPINFOA *)&siex;
603+
creation_flags |= EXTENDED_STARTUPINFO_PRESENT;
604+
} else {
605+
si.cb = sizeof(STARTUPINFOA);
606+
psi = (STARTUPINFOA *)&si;
607+
}
529608

530-
si.cb = sizeof(STARTUPINFO);
531-
si.hStdInput = lsp->pipe_fds[LWS_STDIN][0];
532-
si.hStdOutput = lsp->pipe_fds[LWS_STDOUT][1];
533-
si.hStdError = lsp->pipe_fds[LWS_STDERR][1];
534-
si.dwFlags = STARTF_USESTDHANDLES | CREATE_NO_WINDOW;
535-
si.wShowWindow = TRUE;
609+
psi->hStdInput = lsp->pipe_fds[LWS_STDIN][0];
610+
psi->hStdOutput = lsp->pipe_fds[LWS_STDOUT][1];
611+
psi->hStdError = lsp->pipe_fds[LWS_STDERR][1];
612+
psi->dwFlags = STARTF_USESTDHANDLES | CREATE_NO_WINDOW;
613+
psi->wShowWindow = TRUE;
536614

537-
if (!CreateProcess(NULL, cli, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
538-
lwsl_err("%s: CreateProcess failed 0x%x\n", __func__,
615+
if (!CreateProcessA(NULL, cli, NULL, NULL, TRUE, creation_flags, NULL, NULL, psi, &pi)) {
616+
lwsl_err("%s: CreateProcess failed 0x%lx\n", __func__,
539617
(unsigned long)GetLastError());
618+
if (pty_active && siex.lpAttributeList) {
619+
pDeleteProcThreadAttributeList(siex.lpAttributeList);
620+
lws_free(siex.lpAttributeList);
621+
}
540622
goto bail3;
541623
}
542624

625+
if (pty_active && siex.lpAttributeList) {
626+
pDeleteProcThreadAttributeList(siex.lpAttributeList);
627+
lws_free(siex.lpAttributeList);
628+
}
629+
543630
lsp->child_pid = pi.hProcess;
544631
lsp->hJob = CreateJobObjectW(NULL, NULL);
545632
if (lsp->hJob) {
@@ -563,10 +650,12 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
563650
/*
564651
* close: stdin:r, stdout:w, stderr:w
565652
*/
566-
for (n = 0; n < 3; n++)
567-
CloseHandle(lsp->pipe_fds[n][n != 0]);
653+
for (n = 0; n < 3; n++) {
654+
if (lsp->pipe_fds[n][n != 0] && (!i->pty_mode || n != LWS_STDERR))
655+
CloseHandle(lsp->pipe_fds[n][n != 0]);
656+
}
568657

569-
lsp->pipes_alive = 3;
658+
lsp->pipes_alive = i->pty_mode ? 2 : 3;
570659
lsp->created = lws_now_usecs();
571660

572661
if (i->owner)

lib/roles/http/server/lejp-conf.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,15 +1319,20 @@ lwsws_get_config_vhosts(struct lws_context *context,
13191319
memset(&i, 0, sizeof(i));
13201320
i.vhost_name = "root-monitor-dummy";
13211321
i.port = CONTEXT_PORT_NO_LISTEN;
1322-
i.options = info->options;
1322+
i.options = info->options | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
13231323
i.protocols = info->protocols;
13241324
i.pprotocols = info->pprotocols;
13251325
#if defined(LWS_ROLE_WS)
13261326
i.extensions = info->extensions;
13271327
#endif
1328-
if (!lws_create_vhost(context, &i))
1328+
struct lws_vhost *vh = lws_create_vhost(context, &i);
1329+
if (!vh)
13291330
return 1;
13301331

1332+
extern int lws_context_init_ssl_library(struct lws_context *cx, const struct lws_context_creation_info *info);
1333+
lws_context_init_ssl_library(context, &i);
1334+
lws_init_vhost_client_ssl(&i, vh);
1335+
13311336
return 0;
13321337
}
13331338

lib/tls/openssl/openssl-client.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@ lws_ssl_client_bio_create(struct lws *wsi)
278278

279279
wsi->tls.ssl = SSL_new(wsi->a.vhost->tls.ssl_client_ctx);
280280
if (!wsi->tls.ssl) {
281-
const char *es = ERR_error_string(
282-
LWS_TLS_ERR_CAST(lws_ssl_get_error(wsi, 0)), NULL);
283-
lwsl_err("SSL_new failed: %s\n", es);
281+
unsigned long err = ERR_get_error();
282+
const char *es = ERR_error_string(LWS_TLS_ERR_CAST(err), NULL);
283+
lwsl_err("SSL_new failed: %s (real error %lu)\n", es, err);
284284
lws_tls_err_describe_clear();
285285
return -1;
286286
}

0 commit comments

Comments
 (0)