/src/libwebsockets/lib/core-net/stub.c
Line | Count | Source |
1 | | /* |
2 | | * libwebsockets - small server side websockets and web server implementation |
3 | | * |
4 | | * Copyright (C) 2010 - 2026 Andy Green <andy@warmcat.com> |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | * of this software and associated documentation files (the "Software"), to |
8 | | * deal in the Software without restriction, including without limitation the |
9 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
10 | | * sell copies of the Software, and to permit persons to whom the Software is |
11 | | * furnished to do so, subject to the following conditions: |
12 | | * |
13 | | * The above copyright notice and this permission notice shall be included in |
14 | | * all copies or substantial portions of the Software. |
15 | | * |
16 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
21 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
22 | | * IN THE SOFTWARE. |
23 | | * |
24 | | * lws_stub - generalized API for spawning and communicating with root stubs |
25 | | * via UDS and JSON-RPC |
26 | | */ |
27 | | |
28 | | #include "private-lib-core.h" |
29 | | #include <string.h> |
30 | | |
31 | | #if defined(WIN32) |
32 | | #include <fcntl.h> |
33 | | #include <io.h> |
34 | | #endif |
35 | | |
36 | | #if defined(__APPLE__) |
37 | | #include <mach-o/dyld.h> |
38 | | #endif |
39 | | |
40 | | #if defined(__FreeBSD__) |
41 | | #include <sys/sysctl.h> |
42 | | #endif |
43 | | |
44 | | #if defined(LWS_WITH_CLIENT) |
45 | | struct lws_stub_req { |
46 | | struct lws_dll2 list; |
47 | | char *tx_buf; |
48 | | size_t tx_len; |
49 | | size_t tx_pos; |
50 | | struct lejp_ctx jctx; |
51 | | signed char (*rx_cb)(struct lejp_ctx *ctx, char reason); |
52 | | void (*raw_cb)(const char *in, size_t len, void *user); |
53 | | void *user; |
54 | | }; |
55 | | |
56 | | struct lws_stub_manager { |
57 | | struct lws_context *cx; |
58 | | struct lws_vhost *vh; |
59 | | struct lws_vhost *vh_client; |
60 | | char uds_path[256]; |
61 | | char stub_name[128]; |
62 | | char secret[129]; |
63 | | struct lws_spawn_piped *lsp; |
64 | | struct lws_stub_config config; |
65 | | |
66 | | struct lws_dll2 cx_list; /* cx->owner_stub_mgrs */ |
67 | | |
68 | | const struct lws_protocols *protocols; |
69 | | |
70 | | struct lws *wsi_client; |
71 | | struct lws_dll2_owner reqs; |
72 | | |
73 | | lws_sorted_usec_list_t sul; |
74 | | uint16_t ctry; |
75 | | char stub_arg[128]; |
76 | | const char *exec_array[5]; |
77 | | char addr[256]; |
78 | | char exe_path[256]; |
79 | | }; |
80 | | |
81 | | static int |
82 | | lws_stub_client_connect(struct lws_stub_manager *mgr); |
83 | | |
84 | | /* |
85 | | * The stub client connection is made on its own private, no-listen vhost, |
86 | | * with this as the vhost's protocols[0]. This has two effects: |
87 | | * |
88 | | * - the client wsi protocol binding to "lws-stub-client" cannot fail |
89 | | * because the caller's vhost lacks it, which would misroute all the |
90 | | * client wsi callbacks elsewhere, and |
91 | | * |
92 | | * - wsi destroy notifications are delivered to the vhost protocols[0], |
93 | | * ie, to lws_callback_stub_client, even for wsi that never established |
94 | | * and even when the context is being destroyed and other callbacks are |
95 | | * suppressed. That is how we reliably learn our wsi was freed, so we |
96 | | * can stop holding a stale pointer to it. |
97 | | */ |
98 | | static const struct lws_protocols stub_client_protocols[] = { |
99 | | { |
100 | | .name = "lws-stub-client", |
101 | | .callback = lws_callback_stub_client, |
102 | | .per_session_data_size = 0, |
103 | | .rx_buffer_size = 4096, |
104 | | }, |
105 | | LWS_PROTOCOL_LIST_TERM |
106 | | }; |
107 | | |
108 | | struct lws_stub_manager * |
109 | | lws_stub_spawn(const struct lws_stub_config *config) |
110 | 0 | { |
111 | 0 | struct lws_stub_manager *mgr; |
112 | 0 | struct lws_spawn_piped_info spawn_info; |
113 | 0 | int n = 0; |
114 | 0 | uint8_t rand[64]; |
115 | |
|
116 | 0 | if (!config->parent_protocol_name) { |
117 | 0 | lwsl_err("%s: stub '%s': parent_protocol_name is required, " |
118 | 0 | "the stub stdio pipes need a protocol that " |
119 | 0 | "drains them\n", __func__, |
120 | 0 | config->stub_name ? config->stub_name : "?"); |
121 | |
|
122 | 0 | return NULL; |
123 | 0 | } |
124 | | |
125 | 0 | mgr = lws_zalloc(sizeof(*mgr), "stub_mgr"); |
126 | 0 | if (!mgr) |
127 | 0 | return NULL; |
128 | | |
129 | 0 | mgr->cx = config->cx; |
130 | 0 | mgr->vh = config->vh; |
131 | 0 | memcpy(&mgr->config, config, sizeof(mgr->config)); |
132 | 0 | if (config->uds_path) |
133 | 0 | lws_strncpy(mgr->uds_path, config->uds_path, sizeof(mgr->uds_path)); |
134 | 0 | mgr->config.uds_path = mgr->uds_path; |
135 | 0 | if (config->stub_name) |
136 | 0 | lws_strncpy(mgr->stub_name, config->stub_name, sizeof(mgr->stub_name)); |
137 | 0 | mgr->config.stub_name = mgr->stub_name; |
138 | 0 | mgr->protocols = config->protocols; |
139 | | |
140 | | /* |
141 | | * track the mgr on the context, so a stub can never outlive either |
142 | | * the context or the vhost it was spawned on, even if the caller |
143 | | * never gets a PROTOCOL_DESTROY to destroy it from (eg, in a |
144 | | * plugins build, his vhost protocol was never instantiated) |
145 | | */ |
146 | 0 | lws_dll2_add_tail(&mgr->cx_list, &config->cx->owner_stub_mgrs); |
147 | | |
148 | | /* Generate a secure 128-char secret */ |
149 | 0 | lws_get_random(mgr->cx, rand, sizeof(rand)); |
150 | 0 | lws_hex_from_byte_array(rand, sizeof(rand), mgr->secret, sizeof(mgr->secret)); |
151 | |
|
152 | 0 | memset(&spawn_info, 0, sizeof(spawn_info)); |
153 | 0 | const char *exe_path = "/usr/local/bin/lwsws"; |
154 | |
|
155 | | #if defined(__APPLE__) |
156 | | { |
157 | | uint32_t size = sizeof(mgr->exe_path); |
158 | | if (_NSGetExecutablePath(mgr->exe_path, &size) == 0) |
159 | | exe_path = mgr->exe_path; |
160 | | } |
161 | | #elif defined(__linux__) |
162 | | { |
163 | 0 | int m = (int)readlink("/proc/self/exe", mgr->exe_path, sizeof(mgr->exe_path) - 1); |
164 | 0 | if (m > 0) { |
165 | 0 | mgr->exe_path[m] = '\0'; |
166 | 0 | exe_path = mgr->exe_path; |
167 | 0 | } |
168 | 0 | } |
169 | | #elif defined(__FreeBSD__) |
170 | | { |
171 | | int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; |
172 | | size_t cb = sizeof(mgr->exe_path); |
173 | | if (sysctl(mib, 4, mgr->exe_path, &cb, NULL, 0) == 0) { |
174 | | mgr->exe_path[cb] = '\0'; |
175 | | exe_path = mgr->exe_path; |
176 | | } |
177 | | } |
178 | | #elif defined(WIN32) |
179 | | { |
180 | | /* |
181 | | * Windows has a first-class way to find our own executable |
182 | | * path, independent of how we were invoked or whether the |
183 | | * caller passed argc / argv into the context |
184 | | */ |
185 | | DWORD m = GetModuleFileNameA(NULL, mgr->exe_path, |
186 | | sizeof(mgr->exe_path)); |
187 | | |
188 | | if (m > 0 && m < sizeof(mgr->exe_path)) { |
189 | | mgr->exe_path[m] = '\0'; |
190 | | exe_path = mgr->exe_path; |
191 | | } else { |
192 | | const char *argv0 = lws_cmdline_option_cx_argv0(mgr->cx); |
193 | | |
194 | | if (argv0) |
195 | | exe_path = argv0; |
196 | | } |
197 | | } |
198 | | #else |
199 | | { |
200 | | const char *argv0 = lws_cmdline_option_cx_argv0(mgr->cx); |
201 | | if (argv0) { |
202 | | if (argv0[0] == '/') { |
203 | | lws_strncpy(mgr->exe_path, argv0, sizeof(mgr->exe_path)); |
204 | | exe_path = mgr->exe_path; |
205 | | } else if (realpath(argv0, mgr->exe_path)) |
206 | | exe_path = mgr->exe_path; |
207 | | else |
208 | | exe_path = argv0; |
209 | | } |
210 | | } |
211 | | #endif |
212 | |
|
213 | 0 | mgr->exec_array[n++] = exe_path; |
214 | 0 | lwsl_vhost_notice(mgr->vh, "%s: Spawning stub '%s' with exe: %s\n", __func__, config->stub_name, exe_path); |
215 | | /* Construct the stub argument dynamically */ |
216 | 0 | lws_snprintf(mgr->stub_arg, sizeof(mgr->stub_arg), "--lws-stub=%s", config->stub_name); |
217 | 0 | mgr->exec_array[n++] = mgr->stub_arg; |
218 | 0 | mgr->exec_array[n++] = NULL; |
219 | |
|
220 | 0 | spawn_info.exec_array = mgr->exec_array; |
221 | 0 | spawn_info.vh = mgr->vh; |
222 | 0 | spawn_info.opaque = mgr; |
223 | 0 | if (config->parent_protocol_name) |
224 | 0 | spawn_info.protocol_name = config->parent_protocol_name; |
225 | |
|
226 | 0 | mgr->lsp = lws_spawn_piped(&spawn_info); |
227 | 0 | if (mgr->lsp) { |
228 | 0 | lws_filefd_type stdin_fd = lws_spawn_get_fd_stdxxx(mgr->lsp, 0); |
229 | | #if defined(WIN32) |
230 | | if (stdin_fd) { |
231 | | DWORD bw; |
232 | | if (!WriteFile(stdin_fd, mgr->secret, 128, &bw, NULL)) { |
233 | | lwsl_vhost_err(mgr->vh, "%s: stub '%s' failed writing secret to pipe\n", __func__, config->stub_name); |
234 | | goto spawn_fail; |
235 | | } |
236 | | if (config->extra_payload && config->extra_payload_len) { |
237 | | if (!WriteFile(stdin_fd, config->extra_payload, (DWORD)config->extra_payload_len, &bw, NULL)) { |
238 | | lwsl_vhost_err(mgr->vh, "%s: stub '%s' failed writing extra payload to pipe\n", __func__, config->stub_name); |
239 | | goto spawn_fail; |
240 | | } |
241 | | } |
242 | | } else { |
243 | | lwsl_vhost_err(mgr->vh, "%s: stub '%s' no stdin pipe available\n", __func__, config->stub_name); |
244 | | goto spawn_fail; |
245 | | } |
246 | | #else |
247 | 0 | if (stdin_fd >= 0) { |
248 | 0 | if (write(stdin_fd, mgr->secret, 128) < 0) { |
249 | 0 | lwsl_vhost_err(mgr->vh, "%s: stub '%s' failed writing secret to pipe\n", __func__, config->stub_name); |
250 | 0 | goto spawn_fail; |
251 | 0 | } |
252 | 0 | if (config->extra_payload && config->extra_payload_len) { |
253 | 0 | if (write(stdin_fd, config->extra_payload, (unsigned int)config->extra_payload_len) < 0) { |
254 | 0 | lwsl_vhost_err(mgr->vh, "%s: stub '%s' failed writing extra payload to pipe\n", __func__, config->stub_name); |
255 | 0 | goto spawn_fail; |
256 | 0 | } |
257 | 0 | } |
258 | 0 | } else { |
259 | 0 | lwsl_vhost_err(mgr->vh, "%s: stub '%s' no stdin pipe available\n", __func__, config->stub_name); |
260 | 0 | goto spawn_fail; |
261 | 0 | } |
262 | 0 | #endif |
263 | 0 | } else { |
264 | 0 | lwsl_vhost_err(mgr->vh, "%s: Failed to spawn stub '%s'\n", __func__, config->stub_name); |
265 | 0 | lws_stub_destroy(&mgr); |
266 | 0 | return NULL; |
267 | 0 | } |
268 | | |
269 | 0 | lwsl_vhost_notice(mgr->vh, "%s: Spawned stub '%s'\n", __func__, config->stub_name); |
270 | |
|
271 | 0 | { |
272 | 0 | struct lws_context_creation_info ci; |
273 | 0 | char name[192]; |
274 | |
|
275 | 0 | memset(&ci, 0, sizeof(ci)); |
276 | 0 | ci.port = CONTEXT_PORT_NO_LISTEN; |
277 | 0 | ci.protocols = stub_client_protocols; |
278 | 0 | lws_snprintf(name, sizeof(name), "%s-client", mgr->stub_name); |
279 | 0 | ci.vhost_name = name; |
280 | |
|
281 | 0 | mgr->vh_client = lws_create_vhost(mgr->cx, &ci); |
282 | 0 | } |
283 | |
|
284 | 0 | if (!mgr->vh_client) { |
285 | 0 | lwsl_vhost_err(mgr->vh, "%s: stub '%s': failed to create client vhost\n", |
286 | 0 | __func__, config->stub_name); |
287 | 0 | goto spawn_fail; |
288 | 0 | } |
289 | | |
290 | 0 | if (!lws_dll2_owner(&mgr->sul.list) && !mgr->wsi_client) |
291 | 0 | lws_stub_client_connect(mgr); |
292 | |
|
293 | 0 | return mgr; |
294 | | |
295 | 0 | spawn_fail: |
296 | 0 | lwsl_vhost_err(mgr->vh, "%s: Failed to initialize spawned stub '%s'\n", __func__, config->stub_name); |
297 | 0 | lws_stub_destroy(&mgr); |
298 | 0 | return NULL; |
299 | 0 | } |
300 | | #endif |
301 | | |
302 | | |
303 | | int |
304 | | lws_stub_server_init(const struct lws_stub_config *config, char *secret_out, void *extra_out, size_t extra_len) |
305 | 0 | { |
306 | 0 | struct lws_context_creation_info info; |
307 | 0 | struct lws_vhost *vh_uds; |
308 | |
|
309 | 0 | size_t rx = 0; |
310 | |
|
311 | | #if defined(WIN32) |
312 | | _setmode(0, _O_BINARY); |
313 | | #endif |
314 | | |
315 | | /* 1. Read secret from stdin */ |
316 | 0 | while (rx < 128) { |
317 | 0 | ssize_t n = read(0, (void *)(secret_out + rx), 128 - (unsigned int)rx); |
318 | 0 | if (n <= 0) |
319 | 0 | break; |
320 | 0 | rx += (size_t)n; |
321 | 0 | } |
322 | |
|
323 | 0 | if (rx < 64) { |
324 | 0 | lwsl_err("%s: stub '%s': Failed to read secret from stdin\n", __func__, config->stub_name ? config->stub_name : "unknown"); |
325 | 0 | return -1; |
326 | 0 | } |
327 | 0 | secret_out[128] = '\0'; |
328 | | |
329 | | /* 1.5. Read extra payload if provided */ |
330 | 0 | if (extra_out && extra_len > 0) { |
331 | | /* We only do a single read here because the payload size is variable |
332 | | * and unknown to the child, and the pipe remains open for future IPC. */ |
333 | 0 | ssize_t n = read(0, (void *)extra_out, (unsigned int)extra_len); |
334 | 0 | if (n < 0) { |
335 | 0 | lwsl_err("%s: stub '%s': Failed to read extra payload\n", __func__, config->stub_name ? config->stub_name : "unknown"); |
336 | | /* Non-fatal */ |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | /* 2. Create UDS server vhost */ |
341 | 0 | memset(&info, 0, sizeof(info)); |
342 | 0 | info.options = LWS_SERVER_OPTION_UNIX_SOCK | LWS_SERVER_OPTION_ONLY_RAW; |
343 | 0 | info.iface = config->uds_path; |
344 | 0 | info.protocols = config->protocols; |
345 | 0 | info.vhost_name = config->stub_name; |
346 | 0 | info.user = config->user; |
347 | |
|
348 | 0 | unlink(info.iface); |
349 | 0 | vh_uds = lws_create_vhost(config->cx, &info); |
350 | 0 | if (!vh_uds) { |
351 | 0 | lwsl_err("%s: stub '%s': Failed to create UDS vhost\n", __func__, config->stub_name ? config->stub_name : "unknown"); |
352 | 0 | return -1; |
353 | 0 | } |
354 | | |
355 | | /* 3. Secure permissions: Only root (and unprivileged clients dropping privs) */ |
356 | 0 | #if !defined(WIN32) |
357 | 0 | chmod(info.iface, 0600); |
358 | 0 | #endif |
359 | | |
360 | | /* Signal ready */ |
361 | 0 | lwsl_notice("STUB-READY (%s)\n", config->stub_name); |
362 | |
|
363 | 0 | return 0; |
364 | 0 | } |
365 | | |
366 | | #if defined(LWS_WITH_CLIENT) |
367 | | static const uint32_t backoff_ms[] = { 100, 250, 500, 1000, 5000 }; |
368 | | |
369 | | static const lws_retry_bo_t stub_retry = { |
370 | | .retry_ms_table = backoff_ms, |
371 | | .retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms), |
372 | | .conceal_count = 1000, |
373 | | .secs_since_valid_ping = 300, |
374 | | .secs_since_valid_hangup = 310, |
375 | | .jitter_percent = 0, |
376 | | }; |
377 | | |
378 | | static void |
379 | | stub_retry_cb(lws_sorted_usec_list_t *sul); |
380 | | |
381 | | #if (_LWS_ENABLED_LOGS & (LLL_NOTICE | LLL_ERR)) |
382 | | static int |
383 | | lws_stub_child_is_alive(struct lws_stub_manager *mgr) |
384 | 0 | { |
385 | 0 | if (!mgr || !mgr->lsp) |
386 | 0 | return 0; |
387 | | |
388 | 0 | #if !defined(WIN32) |
389 | 0 | if (mgr->lsp->child_pid <= 0) |
390 | 0 | return 0; |
391 | 0 | if (kill(mgr->lsp->child_pid, 0) == 0 || errno == EPERM) |
392 | 0 | return 1; |
393 | 0 | return 0; |
394 | | #else |
395 | | return 1; |
396 | | #endif |
397 | 0 | } |
398 | | #endif |
399 | | |
400 | | static int |
401 | | lws_stub_client_connect(struct lws_stub_manager *mgr) |
402 | 0 | { |
403 | 0 | struct lws_client_connect_info i; |
404 | |
|
405 | 0 | if (mgr->cx->being_destroyed) |
406 | | /* nobody will service anything we start from now on */ |
407 | 0 | return -1; |
408 | | |
409 | 0 | memset(&i, 0, sizeof(i)); |
410 | 0 | i.context = mgr->cx; |
411 | 0 | i.vhost = mgr->vh_client; |
412 | | |
413 | | /* UNIX domain socket addresses need a '+' prefix */ |
414 | 0 | lws_snprintf(mgr->addr, sizeof(mgr->addr), "+%s", mgr->uds_path); |
415 | 0 | i.address = mgr->addr; |
416 | |
|
417 | 0 | i.port = 0; |
418 | 0 | i.protocol = "lws-stub-client"; |
419 | 0 | i.local_protocol_name = "lws-stub-client"; |
420 | 0 | i.host = NULL; |
421 | 0 | i.origin = NULL; |
422 | 0 | i.opaque_user_data = mgr; |
423 | 0 | i.retry_and_idle_policy = &stub_retry; |
424 | 0 | i.method = "RAW"; /* RAW connection */ |
425 | |
|
426 | 0 | lwsl_vhost_notice(mgr->vh, "%s: stub '%s', protocol %s, addr %s\n", __func__, mgr->config.stub_name, i.protocol, i.address); |
427 | 0 | mgr->wsi_client = lws_client_connect_via_info(&i); |
428 | 0 | if (!mgr->wsi_client) { |
429 | 0 | if (mgr->ctry < 10) { |
430 | 0 | uint32_t ms = stub_retry.retry_ms_table[ |
431 | 0 | mgr->ctry < stub_retry.retry_ms_table_count ? |
432 | 0 | mgr->ctry : stub_retry.retry_ms_table_count - 1]; |
433 | 0 | mgr->ctry++; |
434 | 0 | if (mgr->ctry > 1) { |
435 | 0 | #if (_LWS_ENABLED_LOGS & LLL_NOTICE) |
436 | 0 | int alive = lws_stub_child_is_alive(mgr); |
437 | 0 | lwsl_vhost_notice(mgr->vh, "%s: stub '%s': Synchronous connect failed (errno %d), stub process %s (PID %d), retrying in %u ms (attempt %d)\n", |
438 | 0 | __func__, mgr->config.stub_name, LWS_ERRNO, |
439 | 0 | alive ? "is alive" : "has DIED/DOES NOT EXIST", |
440 | 0 | mgr->lsp ? (int)(intptr_t)mgr->lsp->child_pid : -1, |
441 | 0 | (unsigned int)ms, mgr->ctry); |
442 | 0 | #endif |
443 | 0 | } |
444 | 0 | lws_sul_schedule(mgr->cx, 0, &mgr->sul, stub_retry_cb, ms * 1000); |
445 | 0 | } |
446 | |
|
447 | 0 | return -1; |
448 | 0 | } |
449 | | |
450 | 0 | return 0; |
451 | 0 | } |
452 | | |
453 | | static void |
454 | | stub_retry_cb(lws_sorted_usec_list_t *sul) |
455 | 0 | { |
456 | 0 | struct lws_stub_manager *mgr = lws_container_of(sul, struct lws_stub_manager, sul); |
457 | |
|
458 | 0 | if (!mgr->wsi_client) |
459 | 0 | lws_stub_client_connect(mgr); |
460 | 0 | } |
461 | | |
462 | | LWS_VISIBLE int |
463 | | lws_callback_stub_client(struct lws *wsi, enum lws_callback_reasons reason, |
464 | | void *user, void *in, size_t len) |
465 | 0 | { |
466 | 0 | struct lws_stub_manager *mgr = (struct lws_stub_manager *)lws_get_opaque_user_data(wsi); |
467 | 0 | if (!mgr) |
468 | 0 | return 0; |
469 | | |
470 | 0 | switch (reason) { |
471 | 0 | case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: { |
472 | 0 | #if (_LWS_ENABLED_LOGS & LLL_ERR) |
473 | 0 | int alive = lws_stub_child_is_alive(mgr); |
474 | 0 | lwsl_vhost_err(mgr->vh, "%s: stub '%s': Client connection failed (stub process %s, PID %d)\n", |
475 | 0 | __func__, mgr->config.stub_name, |
476 | 0 | alive ? "is alive" : "has DIED/DOES NOT EXIST", |
477 | 0 | mgr->lsp ? (int)(intptr_t)mgr->lsp->child_pid : -1); |
478 | 0 | #endif |
479 | 0 | mgr->wsi_client = NULL; |
480 | 0 | lws_retry_sul_schedule(mgr->cx, 0, &mgr->sul, &stub_retry, stub_retry_cb, &mgr->ctry); |
481 | 0 | break; |
482 | 0 | } |
483 | | |
484 | 0 | case LWS_CALLBACK_RAW_CONNECTED: |
485 | 0 | lwsl_vhost_notice(mgr->vh, "%s: stub '%s': UDS connected\n", __func__, mgr->config.stub_name); |
486 | 0 | mgr->ctry = 0; /* Reset retry counter on success */ |
487 | 0 | if (mgr->config.connected_cb) |
488 | 0 | mgr->config.connected_cb(mgr); |
489 | 0 | lws_callback_on_writable(wsi); |
490 | 0 | break; |
491 | | |
492 | 0 | case LWS_CALLBACK_RAW_WRITEABLE: { |
493 | 0 | struct lws_dll2 *d = lws_dll2_get_head(&mgr->reqs); |
494 | 0 | if (!d) |
495 | 0 | break; |
496 | | |
497 | 0 | struct lws_stub_req *req = lws_container_of(d, struct lws_stub_req, list); |
498 | 0 | if (req->tx_pos < req->tx_len) { |
499 | 0 | int n = lws_write(wsi, (unsigned char *)req->tx_buf + LWS_PRE + req->tx_pos, |
500 | 0 | req->tx_len - req->tx_pos, LWS_WRITE_RAW); |
501 | 0 | if (n < 0) |
502 | 0 | return -1; |
503 | 0 | req->tx_pos += (size_t)n; |
504 | 0 | } |
505 | | |
506 | 0 | if (req->tx_pos < req->tx_len) { |
507 | 0 | lws_callback_on_writable(wsi); |
508 | 0 | } else if (!req->rx_cb && !req->raw_cb) { |
509 | | /* No response expected, so we can complete and free the request immediately */ |
510 | 0 | lws_dll2_remove(&req->list); |
511 | 0 | lws_free(req->tx_buf); |
512 | 0 | lws_free(req); |
513 | | |
514 | | /* If there are more requests queued, ask for writable again */ |
515 | 0 | if(!lws_dll2_is_empty(&mgr->reqs)) |
516 | 0 | lws_callback_on_writable(wsi); |
517 | 0 | } |
518 | 0 | break; |
519 | 0 | } |
520 | | |
521 | 0 | case LWS_CALLBACK_RAW_RX: { |
522 | 0 | struct lws_dll2 *d = lws_dll2_get_head(&mgr->reqs); |
523 | 0 | if (!d) |
524 | 0 | break; /* Received RX but no active request? */ |
525 | | |
526 | 0 | struct lws_stub_req *req = lws_container_of(d, struct lws_stub_req, list); |
527 | 0 | if (req->raw_cb) |
528 | 0 | req->raw_cb((const char *)in, len, req->user); |
529 | |
|
530 | 0 | if (req->rx_cb) { |
531 | 0 | int m = lejp_parse(&req->jctx, (uint8_t *)in, (int)len); |
532 | 0 | if (m < 0 && m != LEJP_CONTINUE) { |
533 | 0 | lwsl_vhost_err(mgr->vh, "%s: stub '%s' lejp parse failed: %d\n", __func__, mgr->config.stub_name, m); |
534 | 0 | lws_dll2_remove(&req->list); |
535 | 0 | lws_free(req->tx_buf); |
536 | 0 | lejp_destruct(&req->jctx); |
537 | 0 | lws_free(req); |
538 | 0 | } else if (req->jctx.pst[req->jctx.pst_sp].callback == NULL) { |
539 | | /* If parse complete (or if the callback indicates completion) */ |
540 | | /* Actually, we can just rely on LEJPCB_OBJECT_END in the callback */ |
541 | 0 | } |
542 | 0 | } |
543 | 0 | break; |
544 | 0 | } |
545 | | |
546 | 0 | case LWS_CALLBACK_RAW_CLOSE: |
547 | 0 | case LWS_CALLBACK_CLIENT_CLOSED: |
548 | 0 | mgr->wsi_client = NULL; |
549 | 0 | break; |
550 | | |
551 | 0 | case LWS_CALLBACK_WSI_DESTROY: |
552 | | /* |
553 | | * Unconditional last call before the wsi memory is freed. |
554 | | * For wsi that never established, it's the only notification |
555 | | * we get at all when the context is being destroyed, since |
556 | | * the usual close callbacks are suppressed then. Drop our |
557 | | * pointer to the wsi before its memory goes away. |
558 | | */ |
559 | 0 | if (mgr->wsi_client == wsi) |
560 | 0 | mgr->wsi_client = NULL; |
561 | 0 | break; |
562 | | |
563 | 0 | default: |
564 | 0 | break; |
565 | 0 | } |
566 | | |
567 | 0 | return 0; |
568 | 0 | } |
569 | | |
570 | | int |
571 | | lws_stub_request(struct lws_stub_manager *mgr, |
572 | | const char *json, |
573 | | const char * const *rx_paths, |
574 | | size_t rx_paths_count, |
575 | | signed char (*rx_cb)(struct lejp_ctx *ctx, char reason), |
576 | | void (*raw_cb)(const char *in, size_t len, void *user), |
577 | | void *user) |
578 | 0 | { |
579 | 0 | struct lws_stub_req *req = lws_zalloc(sizeof(*req), "stub_req"); |
580 | 0 | if (!req) |
581 | 0 | return -1; |
582 | | |
583 | 0 | req->rx_cb = rx_cb; |
584 | 0 | req->raw_cb = raw_cb; |
585 | 0 | req->user = user; |
586 | |
|
587 | 0 | if (rx_cb) |
588 | 0 | lejp_construct(&req->jctx, rx_cb, user, rx_paths, (uint8_t)rx_paths_count); |
589 | |
|
590 | 0 | size_t n = strlen(json); |
591 | 0 | req->tx_buf = lws_malloc(n + LWS_PRE + 1, "stub_req_tx"); |
592 | 0 | if (!req->tx_buf) { |
593 | 0 | lws_free(req); |
594 | 0 | return -1; |
595 | 0 | } |
596 | 0 | memcpy((unsigned char *)req->tx_buf + LWS_PRE, json, n); |
597 | 0 | req->tx_len = n; |
598 | |
|
599 | 0 | lws_dll2_add_tail(&req->list, &mgr->reqs); |
600 | |
|
601 | 0 | if (!mgr->wsi_client) { |
602 | 0 | if (!lws_dll2_owner(&mgr->sul.list)) { |
603 | 0 | if (mgr->ctry >= 10) { |
604 | | /* If we hit max retries, reset and try again if new requests come in */ |
605 | 0 | mgr->ctry = 0; |
606 | 0 | } |
607 | 0 | lws_stub_client_connect(mgr); |
608 | 0 | } |
609 | 0 | } else |
610 | 0 | lws_callback_on_writable(mgr->wsi_client); |
611 | |
|
612 | 0 | return 0; |
613 | 0 | } |
614 | | |
615 | | |
616 | | void |
617 | | lws_stub_destroy(struct lws_stub_manager **_mgr) |
618 | 0 | { |
619 | 0 | struct lws_stub_manager *mgr = *_mgr; |
620 | |
|
621 | 0 | if (!mgr) |
622 | 0 | return; |
623 | | |
624 | | /* |
625 | | * Take the caller's pointer away before we start. Destroying the |
626 | | * lsp closes its stdwsi synchronously, and freeing the last stdwsi |
627 | | * can complete a deferred vhost destruction re-entrantly, issuing |
628 | | * PROTOCOL_DESTROY on our protocol; if the caller also destroys us |
629 | | * from there (as lwsws plugins and the api test do), the nested call |
630 | | * must see the pointer already cleared and do nothing, or we will |
631 | | * destroy the same lsp and mgr twice. |
632 | | */ |
633 | 0 | *_mgr = NULL; |
634 | | |
635 | | /* we are tracked on the context, stop that */ |
636 | 0 | lws_dll2_remove(&mgr->cx_list); |
637 | | |
638 | | /* the retry sul is embedded in us: stop it pointing at freed memory */ |
639 | 0 | lws_sul_cancel(&mgr->sul); |
640 | |
|
641 | 0 | lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, lws_dll2_get_head(&mgr->reqs)) { |
642 | 0 | struct lws_stub_req *req = lws_container_of(d, struct lws_stub_req, list); |
643 | 0 | lws_dll2_remove(d); |
644 | 0 | if (req->tx_buf) |
645 | 0 | lws_free(req->tx_buf); |
646 | 0 | if (req->rx_cb) |
647 | 0 | lejp_destruct(&req->jctx); |
648 | 0 | lws_free(req); |
649 | 0 | } lws_end_foreach_dll_safe(d, d1); |
650 | |
|
651 | 0 | if (mgr->wsi_client) |
652 | 0 | lws_set_opaque_user_data(mgr->wsi_client, NULL); |
653 | |
|
654 | 0 | if (mgr->lsp) { |
655 | 0 | lws_spawn_piped_kill_child_process(mgr->lsp); |
656 | | /* |
657 | | * Any still-open stdwsi are marked to close asynchronously |
658 | | * here; stdwsi that already went through close processing |
659 | | * have been removed from lsp->stdwsi[] by their protocol |
660 | | * handler calling lws_spawn_stdwsi_closed(). |
661 | | */ |
662 | 0 | lws_spawn_piped_destroy(&mgr->lsp); |
663 | 0 | } |
664 | |
|
665 | 0 | unlink(mgr->uds_path); |
666 | |
|
667 | 0 | lws_free(mgr); |
668 | 0 | } |
669 | | |
670 | | const char * |
671 | | lws_stub_get_secret(struct lws_stub_manager *mgr) |
672 | 0 | { |
673 | 0 | if (!mgr) |
674 | 0 | return NULL; |
675 | 0 | return mgr->secret; |
676 | 0 | } |
677 | | |
678 | | struct lws_spawn_piped * |
679 | | lws_stub_get_lsp(struct lws_stub_manager *mgr) |
680 | 0 | { |
681 | 0 | if (!mgr) |
682 | 0 | return NULL; |
683 | | |
684 | 0 | return mgr->lsp; |
685 | 0 | } |
686 | | |
687 | | /* |
688 | | * This is called from __lws_vhost_destroy2() for every vhost, so stubs |
689 | | * spawned on the vhost are destroyed with it even if nobody else destroys |
690 | | * them. It is legal (and expected) that PROTOCOL_DESTROY-based destroy |
691 | | * paths already removed the mgr from the tracking list. |
692 | | */ |
693 | | void |
694 | | lws_stub_destroy_all_on_vhost(struct lws_vhost *vh) |
695 | 0 | { |
696 | 0 | lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1, |
697 | 0 | lws_dll2_get_head(&vh->context->owner_stub_mgrs)) { |
698 | 0 | struct lws_stub_manager *mgr = |
699 | 0 | lws_container_of(d, struct lws_stub_manager, cx_list); |
700 | |
|
701 | 0 | if (mgr->vh == vh) |
702 | 0 | lws_stub_destroy(&mgr); |
703 | |
|
704 | 0 | } lws_end_foreach_dll_safe(d, d1); |
705 | 0 | } |
706 | | #endif |