Coverage Report

Created: 2026-08-31 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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