mwrap 2.2.0.1.g867b → 3.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/mwrap/httpd.h ADDED
@@ -0,0 +1,1367 @@
1
+ /*
2
+ * Copyright (C) mwrap hackers <mwrap-perl@80x24.org>
3
+ * License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
4
+ *
5
+ * Single-threaded multiplexing HTTP/1.x AF_UNIX server.
6
+ * Not using epoll|kqueue here since we don't want to be wasting another
7
+ * FD for a few clients.
8
+ *
9
+ * stdio (via open_memstream) is used for all vector management,
10
+ * thus everything is a `FILE *'
11
+ *
12
+ * Buffering is naive: write in full to a memstream to get an accurate
13
+ * Content-Length, then write out the header and sendmsg it off.
14
+ * I'm avoiding a streaming + lazy buffering design based on fopencookie(3)
15
+ * since that adds more complexity and uses icache.
16
+ * Supporting gzip would be nice, but linking zlib is not an option since
17
+ * there's a risk of conflicts if the application links against a different
18
+ * zlib version. posix_spawn+gzip isn't an option, either, since we don't
19
+ * want to generate intrusive SIGCHLD.
20
+ */
21
+ #ifndef _DEFAULT_SOURCE
22
+ # define _DEFAULT_SOURCE
23
+ #endif
24
+ #include <sys/socket.h>
25
+ #include <sys/stat.h>
26
+ #include <sys/types.h>
27
+ #include <sys/un.h>
28
+ #include <poll.h>
29
+ #include <unistd.h>
30
+ #include <stdlib.h>
31
+ #include <stdio.h>
32
+ #include <stdint.h>
33
+ #include <errno.h>
34
+ #include <string.h>
35
+ #include <math.h>
36
+ #include <urcu/list.h>
37
+ #include "picohttpparser.h"
38
+ #include "picohttpparser_c.h"
39
+ #include <pthread.h>
40
+ #include <stdbool.h>
41
+ #if MWRAP_PERL
42
+ # define URL "https://80x24.org/mwrap-perl.git/"
43
+ #else
44
+ # define URL "https://80x24.org/mwrap.git/"
45
+ #endif
46
+ #define TYPE_HTML "text/html; charset=UTF-8"
47
+ #define TYPE_CSV "text/csv"
48
+ #define TYPE_PLAIN "text/plain"
49
+
50
+ enum mw_qev {
51
+ MW_QEV_IGNORE = 0,
52
+ MW_QEV_RD = POLLIN,
53
+ MW_QEV_WR = POLLOUT
54
+ };
55
+
56
+ struct mw_fbuf {
57
+ char *ptr;
58
+ size_t len;
59
+ FILE *fp;
60
+ };
61
+
62
+ struct mw_wbuf { /* for response headers + bodies */
63
+ struct iovec iov[2];
64
+ unsigned iov_nr;
65
+ unsigned iov_written;
66
+ char bytes[];
67
+ };
68
+
69
+ #define MW_RBUF_SIZE 8192
70
+ #define MW_NR_NAME 8
71
+ struct mw_h1req { /* HTTP/1.x request (TSD in common (fast) case) */
72
+ const char *method, *path, *qstr;
73
+ size_t method_len, path_len, qlen;
74
+ uint16_t rbuf_len; /* capped by MW_RBUF_SIZE */
75
+ int pret, minor_ver;
76
+ size_t nr_hdr;
77
+ struct phr_header hdr[MW_NR_NAME];
78
+ char rbuf[MW_RBUF_SIZE]; /* read(2) in to this */
79
+ };
80
+
81
+ struct mw_h1 { /* each HTTP/1.x client (heap) */
82
+ int fd;
83
+ short events; /* for poll */
84
+ unsigned prev_len:13; /* capped by MW_RBUF_SIZE */
85
+ unsigned has_input:1;
86
+ unsigned unused_:2;
87
+ struct mw_h1req *h1r; /* only for slow clients */
88
+ unsigned long in_len;
89
+ struct mw_wbuf *wbuf;
90
+ struct cds_list_head nd; /* <=> mw_h1d.conn */
91
+ };
92
+
93
+ struct mw_h1d { /* the daemon + listener, a singleton */
94
+ int lfd;
95
+ uint8_t alive; /* set by parent */
96
+ uint8_t running; /* cleared by child */
97
+ struct cds_list_head conn; /* <=> mw_h1.nd */
98
+ /* use open_memstream + fwrite to implement a growing pollfd array */
99
+ struct mw_fbuf pb; /* pollfd vector */
100
+ pthread_t tid;
101
+ struct mw_h1req *shared_h1r; /* shared by all fast clients */
102
+ size_t pid_len;
103
+ char pid_str[10];
104
+ };
105
+
106
+ union mw_sockaddr { /* cast-avoiding convenience :> */
107
+ struct sockaddr_un un;
108
+ struct sockaddr any;
109
+ };
110
+
111
+ static struct mw_h1d g_h1d = { .lfd = -1 };
112
+
113
+ /* sortable snapshot version of struct src_loc */
114
+ struct h1_src_loc {
115
+ double mean_life;
116
+ size_t bytes;
117
+ size_t allocations;
118
+ size_t frees;
119
+ size_t live;
120
+ size_t max_life;
121
+ off_t lname_len;
122
+ const struct src_loc *sl;
123
+ char *loc_name;
124
+ };
125
+
126
+ /* sort numeric stuff descending */
127
+ #define CMP_FN(F) static int cmp_##F(const void *x, const void *y) \
128
+ { \
129
+ const struct h1_src_loc *a = x, *b = y; \
130
+ if (a->F < b->F) return 1; \
131
+ return (a->F > b->F) ? -1 : 0; \
132
+ }
133
+ CMP_FN(bytes)
134
+ CMP_FN(allocations)
135
+ CMP_FN(frees)
136
+ CMP_FN(live)
137
+ CMP_FN(max_life)
138
+ CMP_FN(mean_life)
139
+ #undef CMP_FN
140
+
141
+ static int cmp_location(const void *x, const void *y)
142
+ {
143
+ const struct h1_src_loc *a = x, *b = y;
144
+ return strcmp(a->loc_name, b->loc_name);
145
+ }
146
+
147
+ /* fields for /each/$MIN{,.csv} endpoints */
148
+ struct h1_tbl {
149
+ const char *fname;
150
+ size_t flen;
151
+ int (*cmp)(const void *, const void *);
152
+ } fields[] = {
153
+ #define F(n) { #n, sizeof(#n) - 1, cmp_##n }
154
+ F(bytes),
155
+ F(allocations),
156
+ F(frees),
157
+ F(live),
158
+ F(mean_life),
159
+ F(max_life),
160
+ F(location)
161
+ #undef F
162
+ };
163
+
164
+ static enum mw_qev h1_close(struct mw_h1 *h1)
165
+ {
166
+ mwrap_assert(h1->fd >= 0);
167
+ cds_list_del(&h1->nd); /* drop from h1d->conn */
168
+ close(h1->fd);
169
+ free(h1->wbuf);
170
+ free(h1->h1r);
171
+ free(h1);
172
+ return MW_QEV_IGNORE;
173
+ }
174
+
175
+ static enum mw_qev h1_400(struct mw_h1 *h1)
176
+ {
177
+ /* best-effort response, so no checking send() */
178
+ static const char r400[] = "HTTP/1.1 400 Bad Request\r\n"
179
+ "Content-Type: text/html\r\n"
180
+ "Content-Length: 12\r\n"
181
+ "Connection: close\r\n\r\n" "Bad Request\n";
182
+ (void)send(h1->fd, r400, sizeof(r400) - 1, MSG_NOSIGNAL);
183
+ return h1_close(h1);
184
+ }
185
+
186
+ static enum mw_qev h1_send_flush(struct mw_h1 *h1)
187
+ {
188
+ struct mw_wbuf *wbuf = h1->wbuf;
189
+ struct msghdr mh = { 0 };
190
+
191
+ free(h1->h1r);
192
+ h1->h1r = NULL;
193
+
194
+ mh.msg_iov = wbuf->iov + wbuf->iov_written;
195
+ mh.msg_iovlen = wbuf->iov_nr;
196
+ do {
197
+ ssize_t w = sendmsg(h1->fd, &mh, MSG_NOSIGNAL);
198
+ if (w < 0)
199
+ return errno == EAGAIN ? MW_QEV_WR : h1_close(h1);
200
+ if (w == 0)
201
+ return h1_close(h1);
202
+ while (w > 0) {
203
+ if ((size_t)w >= mh.msg_iov->iov_len) {
204
+ w -= mh.msg_iov->iov_len;
205
+ ++mh.msg_iov;
206
+ --mh.msg_iovlen;
207
+ ++wbuf->iov_written;
208
+ --wbuf->iov_nr;
209
+ } else {
210
+ uintptr_t x = (uintptr_t)mh.msg_iov->iov_base;
211
+ mh.msg_iov->iov_base = (void *)(x + w);
212
+ mh.msg_iov->iov_len -= w;
213
+ w = 0;
214
+ }
215
+ }
216
+ } while (mh.msg_iovlen);
217
+ return h1_close(h1);
218
+ }
219
+
220
+ static FILE *fbuf_init(struct mw_fbuf *fb)
221
+ {
222
+ fb->ptr = NULL;
223
+ fb->fp = open_memstream(&fb->ptr, &fb->len);
224
+ if (!fb->fp) fprintf(stderr, "open_memstream: %m\n");
225
+ return fb->fp;
226
+ }
227
+
228
+ static FILE *wbuf_init(struct mw_fbuf *fb)
229
+ {
230
+ static const struct mw_wbuf pad;
231
+ if (fbuf_init(fb)) /* pad space is populated before h1_send_flush */
232
+ fwrite(&pad, 1, sizeof(pad), fb->fp);
233
+ return fb->fp;
234
+ }
235
+
236
+ static int fbuf_close(struct mw_fbuf *fb)
237
+ {
238
+ int e = ferror(fb->fp) | fclose(fb->fp);
239
+ fb->fp = NULL;
240
+ if (e) fprintf(stderr, "ferror|fclose: %m\n");
241
+ return e;
242
+ }
243
+
244
+ /* supported by modern gcc + clang */
245
+ #define AUTO_CLOFREE __attribute__((__cleanup__(cleanup_clofree)))
246
+ static void cleanup_clofree(void *ptr)
247
+ {
248
+ struct mw_fbuf *fb = ptr;
249
+ if (fb->fp) fclose(fb->fp);
250
+ free(fb->ptr);
251
+ }
252
+
253
+ static enum mw_qev h1_res_oneshot(struct mw_h1 *h1, const char *buf, size_t len)
254
+ {
255
+ struct mw_fbuf fb;
256
+
257
+ if (!wbuf_init(&fb))
258
+ return h1_close(h1);
259
+
260
+ fwrite(buf, 1, len, fb.fp);
261
+ if (fbuf_close(&fb))
262
+ return h1_close(h1);
263
+
264
+ /* fill in the zero padding we added at wbuf_init */
265
+ mwrap_assert(!h1->wbuf);
266
+ struct mw_wbuf *wbuf = h1->wbuf = (struct mw_wbuf *)fb.ptr;
267
+ wbuf->iov_nr = 1;
268
+ wbuf->iov[0].iov_len = fb.len - sizeof(*wbuf);
269
+ wbuf->iov[0].iov_base = wbuf->bytes;
270
+ return h1_send_flush(h1);
271
+ }
272
+
273
+ #define FPUTS(STR, fp) fwrite(STR, sizeof(STR) - 1, 1, fp)
274
+ static enum mw_qev h1_200(struct mw_h1 *h1, struct mw_fbuf *fb, const char *ct)
275
+ {
276
+ /*
277
+ * the HTTP header goes at the END of the body buffer,
278
+ * we'll rely on iovecs via sendmsg(2) to reorder and clamp it
279
+ */
280
+ off_t clen = ftello(fb->fp);
281
+ if (clen < 0) {
282
+ fprintf(stderr, "ftello: %m\n");
283
+ fbuf_close(fb);
284
+ return h1_close(h1);
285
+ }
286
+ clen -= sizeof(struct mw_wbuf);
287
+ mwrap_assert(clen >= 0);
288
+ FPUTS("HTTP/1.1 200 OK\r\n"
289
+ "Connection: close\r\n"
290
+ "Expires: Fri, 01 Jan 1980 00:00:00 GMT\r\n"
291
+ "Pragma: no-cache\r\n"
292
+ "Cache-Control: no-cache, max-age=0, must-revalidate\r\n"
293
+ "Content-Type: ", fb->fp);
294
+ fprintf(fb->fp, "%s\r\nContent-Length: %zu\r\n\r\n", ct, (size_t)clen);
295
+
296
+ if (fbuf_close(fb))
297
+ return h1_close(h1);
298
+
299
+ /* fill in the zero-padding we added at wbuf_init */
300
+ mwrap_assert(!h1->wbuf);
301
+ struct mw_wbuf *wbuf = h1->wbuf = (struct mw_wbuf *)fb->ptr;
302
+ wbuf->iov_nr = 2;
303
+ wbuf->iov[0].iov_len = fb->len - ((size_t)clen + sizeof(*wbuf));
304
+ wbuf->iov[0].iov_base = wbuf->bytes + (size_t)clen;
305
+ wbuf->iov[1].iov_len = clen;
306
+ wbuf->iov[1].iov_base = wbuf->bytes;
307
+ return h1_send_flush(h1);
308
+ }
309
+
310
+ static enum mw_qev h1_404(struct mw_h1 *h1)
311
+ {
312
+ static const char r404[] = "HTTP/1.1 404 Not Found\r\n"
313
+ "Content-Type: text/html\r\n"
314
+ "Connection: close\r\n"
315
+ "Content-Length: 10\r\n\r\n" "Not Found\n";
316
+ return h1_res_oneshot(h1, r404, sizeof(r404) - 1);
317
+ }
318
+
319
+ #define NAME_EQ(h, NAME) name_eq(h, NAME, sizeof(NAME)-1)
320
+ static int name_eq(const struct phr_header *h, const char *name, size_t len)
321
+ {
322
+ return h->name_len == len && !strncasecmp(name, h->name, len);
323
+ }
324
+
325
+ static enum mw_qev h1_do_reset(struct mw_h1 *h1)
326
+ {
327
+ static const char r200[] = "HTTP/1.1 200 OK\r\n"
328
+ "Content-Type: text/plain\r\n"
329
+ "Connection: close\r\n"
330
+ "Content-Length: 6\r\n\r\n" "reset\n";
331
+ mwrap_reset();
332
+ return h1_res_oneshot(h1, r200, sizeof(r200) - 1);
333
+ }
334
+
335
+ static enum mw_qev h1_do_trim(struct mw_h1 *h1)
336
+ {
337
+ static const char r200[] = "HTTP/1.1 200 OK\r\n"
338
+ "Content-Type: text/plain\r\n"
339
+ "Connection: close\r\n"
340
+ "Content-Length: 9\r\n\r\n" "trimming\n";
341
+ malloc_trim(0);
342
+ return h1_res_oneshot(h1, r200, sizeof(r200) - 1);
343
+ }
344
+
345
+ static enum mw_qev h1_do_ctl_finish(struct mw_h1 *h1)
346
+ {
347
+ struct mw_fbuf plain;
348
+ FILE *fp = wbuf_init(&plain);
349
+ if (!fp) return h1_close(h1);
350
+ fprintf(fp, "MWRAP=bt:%u\n", (unsigned)CMM_LOAD_SHARED(bt_req_depth));
351
+ return h1_200(h1, &plain, TYPE_PLAIN);
352
+ }
353
+
354
+ #define PATH_SKIP(h1r, pfx) path_skip(h1r, pfx, sizeof(pfx) - 1)
355
+ static const char *path_skip(struct mw_h1req *h1r, const char *pfx, size_t len)
356
+ {
357
+ if (h1r->path_len > len && !memcmp(pfx, h1r->path, len))
358
+ return h1r->path + len;
359
+ return NULL;
360
+ }
361
+
362
+ static void write_html(FILE *fp, const char *s, size_t len)
363
+ {
364
+ for (; len--; ++s) {
365
+ switch (*s) {
366
+ case '&': FPUTS("&amp;", fp); break;
367
+ case '<': FPUTS("&lt;", fp); break;
368
+ case '>': FPUTS("&gt;", fp); break;
369
+ case '"': FPUTS("&quot;", fp); break;
370
+ case '\'': FPUTS("&#39;", fp); break;
371
+ case '\n': FPUTS("<br>", fp); break;
372
+ default: fputc(*s, fp);
373
+ }
374
+ }
375
+ }
376
+
377
+ /*
378
+ * quotes multi-line backtraces for CSV (and `\' and `"' in case
379
+ * we encounter nasty file names).
380
+ */
381
+ static void write_q_csv(FILE *fp, const char *s, size_t len)
382
+ {
383
+ fputc('"', fp);
384
+ for (; len--; ++s) {
385
+ switch (*s) {
386
+ case '\n': fputs("\\n", fp); break;
387
+ case '\\': fputs("\\\\", fp); break;
388
+ case '"': fputs("\\\"", fp); break;
389
+ default: fputc(*s, fp);
390
+ }
391
+ }
392
+ fputc('"', fp);
393
+ }
394
+
395
+
396
+ /* URI-safe base-64 (RFC 4648) */
397
+ static void write_b64_url(FILE *fp, const uint8_t *in, size_t len)
398
+ {
399
+ static const uint8_t b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
400
+ "abcdefghijklmnopqrstuvwxyz" "0123456789-_";
401
+ uint8_t o[4];
402
+ while (len > 3) {
403
+ o[0] = b64[in[0] >> 2];
404
+ o[1] = b64[((in[0] << 4) | (in[1] >> 4)) & 0x3f];
405
+ o[2] = b64[((in[1] << 2) | (in[2] >> 6)) & 0x3f];
406
+ o[3] = b64[in[2] & 0x3f];
407
+ fwrite(o, sizeof(o), 1, fp);
408
+ len -= 3;
409
+ in += 3;
410
+ }
411
+ if (len) {
412
+ size_t i = 2;
413
+
414
+ o[0] = b64[in[0] >> 2];
415
+ o[1] = b64[((in[0] << 4) | (--len ? (in[1] >> 4) : 0)) & 0x3f];
416
+ if (len)
417
+ o[i++] = b64[((in[1] << 2) |
418
+ (--len ? in[2] >> 6 : 0)) & 0x3f];
419
+ if (len)
420
+ o[i++] = b64[in[2] & 0x3f];
421
+ fwrite(o, i, 1, fp);
422
+ }
423
+ }
424
+
425
+ /* unescapes @s in-place and adjusts @len */
426
+ static bool b64_url_decode(const void *ptr, size_t *len)
427
+ {
428
+ union { const void *in; uint8_t *out; } deconst;
429
+ const uint8_t *in = ptr;
430
+ uint8_t u = 0;
431
+
432
+ deconst.in = ptr;
433
+ uint8_t *out = deconst.out;
434
+
435
+ for (size_t i = 0; i < *len; ++i) {
436
+ uint8_t c = in[i];
437
+
438
+ switch (c) {
439
+ case 'A' ... 'Z': c -= 'A'; break;
440
+ case 'a' ... 'z': c -= ('a' - 26); break;
441
+ case '0' ... '9': c -= ('0' - 52); break;
442
+ case '-': c = 62; break;
443
+ case '_': c = 63; break;
444
+ default: return false;
445
+ }
446
+
447
+ mwrap_assert(c <= 63);
448
+ switch (i % 4) {
449
+ case 0: u = c << 2; break;
450
+ case 1:
451
+ *out++ = u | c >> 4;
452
+ u = c << 4;
453
+ break;
454
+ case 2:
455
+ *out++ = u | c >> 2;
456
+ u = c << 6;
457
+ break;
458
+ case 3: *out++ = u | c;
459
+ }
460
+ }
461
+ *len = out - in;
462
+ return true;
463
+ }
464
+
465
+ /* keep this consistent with Mwrap.xs location_string */
466
+ static off_t write_loc_name(FILE *fp, const struct src_loc *l)
467
+ {
468
+ off_t beg = ftello(fp);
469
+
470
+ if (beg < 0) {
471
+ fprintf(stderr, "ftello: %m\n");
472
+ return beg;
473
+ }
474
+ if (l->f) {
475
+ fputs(l->f->fn, fp);
476
+ if (l->lineno == U24_MAX)
477
+ FPUTS(":-", fp);
478
+ else
479
+ fprintf(fp, ":%u", l->lineno);
480
+ }
481
+ if (l->bt_len) {
482
+ AUTO_FREE char **s = bt_syms(l->bt, l->bt_len);
483
+ if (!s) return -1;
484
+ if (l->f) fputc('\n', fp);
485
+
486
+ /* omit local " [RETURN_ADDRESS]" if doing deep backtraces */
487
+ for (uint32_t i = 0; i < l->bt_len; ++i) {
488
+ char *c = memrchr(s[i], '[', strlen(s[i]));
489
+ if (c && c > (s[i] + 2) && c[-1] == ' ')
490
+ c[-1] = '\0';
491
+ }
492
+
493
+ fputs(s[0], fp);
494
+ for (uint32_t i = 1; i < l->bt_len; ++i) {
495
+ fputc('\n', fp);
496
+ fputs(s[i], fp);
497
+ }
498
+ }
499
+ off_t end = ftello(fp);
500
+ if (end < 0) {
501
+ fprintf(stderr, "ftello: %m\n");
502
+ return end;
503
+ }
504
+ return end - beg;
505
+ }
506
+
507
+ static struct h1_src_loc *accumulate(unsigned long min, size_t *hslc, FILE *lp)
508
+ {
509
+ struct mw_fbuf fb;
510
+ if (!fbuf_init(&fb)) return NULL;
511
+ rcu_read_lock();
512
+ struct cds_lfht *t = CMM_LOAD_SHARED(totals);
513
+ struct cds_lfht_iter iter;
514
+ struct src_loc *l;
515
+ if (t) cds_lfht_for_each_entry(t, &iter, l, hnode) {
516
+ size_t freed = uatomic_read(&l->freed_bytes);
517
+ size_t total = uatomic_read(&l->total);
518
+ struct h1_src_loc hsl;
519
+
520
+ if (total < min) continue;
521
+ hsl.bytes = total - freed;
522
+ hsl.allocations = uatomic_read(&l->allocations);
523
+ hsl.frees = uatomic_read(&l->frees);
524
+ hsl.live = hsl.allocations - hsl.frees;
525
+ hsl.mean_life = hsl.frees ?
526
+ ((double)uatomic_read(&l->age_total) /
527
+ (double)hsl.frees) :
528
+ HUGE_VAL;
529
+ hsl.max_life = uatomic_read(&l->max_lifespan);
530
+ hsl.sl = l;
531
+ hsl.lname_len = write_loc_name(lp, l);
532
+ fwrite(&hsl, sizeof(hsl), 1, fb.fp);
533
+ }
534
+ rcu_read_unlock();
535
+
536
+ struct h1_src_loc *hslv;
537
+ if (fbuf_close(&fb)) {
538
+ hslv = NULL;
539
+ } else {
540
+ *hslc = fb.len / sizeof(*hslv);
541
+ mwrap_assert((fb.len % sizeof(*hslv)) == 0);
542
+ hslv = (struct h1_src_loc *)fb.ptr;
543
+ }
544
+ return hslv;
545
+ }
546
+
547
+ static void show_stats(FILE *fp)
548
+ {
549
+ size_t dec = uatomic_read(&total_bytes_dec);
550
+ size_t inc = uatomic_read(&total_bytes_inc);
551
+ fprintf(fp, "<p>Current age: %zu (live: %zu) "
552
+ "/ files: %zu / locations: %zu",
553
+ inc , inc - dec,
554
+ uatomic_read(&nr_file), uatomic_read(&nr_src_loc));
555
+ #if MWRAP_RUBY
556
+ fprintf(fp, " / GC: %zu", uatomic_read(&last_gc_count));
557
+ #endif
558
+ }
559
+
560
+ /* /$PID/at/$LOCATION endpoint */
561
+ static enum mw_qev each_at(struct mw_h1 *h1, struct mw_h1req *h1r)
562
+ {
563
+ const char *loc = h1r->path + sizeof("/at/") - 1;
564
+ size_t len = h1r->path_len - (sizeof("/at/") - 1);
565
+ size_t min = 0;
566
+
567
+ if (!b64_url_decode(loc, &len) || len >= PATH_MAX)
568
+ return h1_400(h1);
569
+
570
+ struct src_loc *l = mwrap_get_bin(loc, len);
571
+
572
+ if (!l) return h1_404(h1);
573
+
574
+ AUTO_CLOFREE struct mw_fbuf lb;
575
+ if (!fbuf_init(&lb)) return h1_close(h1);
576
+ if (write_loc_name(lb.fp, l) < 0) return h1_close(h1);
577
+ if (fbuf_close(&lb))
578
+ return h1_close(h1);
579
+
580
+ struct mw_fbuf html;
581
+ FILE *fp = wbuf_init(&html);
582
+ if (!fp) return h1_close(h1);
583
+ FPUTS("<html><head><title>", fp);
584
+ write_html(fp, lb.ptr, lb.len);
585
+ FPUTS("</title></head><body><p>live allocations at:", fp);
586
+ if (l->bt_len > 1 || (l->bt_len == 1 && l->f)) FPUTS("<br/>", fp);
587
+ else fputc(' ', fp);
588
+ write_html(fp, lb.ptr, lb.len);
589
+
590
+ show_stats(fp);
591
+ FPUTS("<table><tr><th>size</th><th>generation</th>"
592
+ "<th>address</th></tr>", fp);
593
+
594
+ rcu_read_lock();
595
+ struct alloc_hdr *h;
596
+ cds_list_for_each_entry_rcu(h, &l->allocs, anode) {
597
+ size_t size = uatomic_read(&h->size);
598
+ if (size > min)
599
+ fprintf(fp, "<tr><td>%zu</td><td>%zu</td><td>%p</td>\n",
600
+ size, h->as.live.gen, h->real);
601
+ }
602
+ rcu_read_unlock();
603
+ FPUTS("</table><pre>\nNotes:\n"
604
+ "* 16344-byte (64-bit) or 16344-byte (32-bit) allocations in\n"
605
+ " Ruby &lt;= 3.0 aligned to 0x4000 are likely for object heap slots.\n"
606
+ "* 4080-byte allocations in Perl 5 are likely for arenas\n"
607
+ " (set via the PERL_ARENA_SIZE compile-time macro)"
608
+ "</pre></body></html>", fp);
609
+ return h1_200(h1, &html, TYPE_HTML);
610
+ }
611
+
612
+ /* /$PID/each/$MIN endpoint */
613
+ static enum mw_qev each_gt(struct mw_h1 *h1, struct mw_h1req *h1r,
614
+ unsigned long min, bool csv)
615
+ {
616
+ static const char default_sort[] = "bytes";
617
+ const char *sort;
618
+ size_t sort_len = 0;
619
+
620
+ if (!csv) {
621
+ sort = default_sort;
622
+ sort_len = sizeof(default_sort) - 1;
623
+ }
624
+
625
+ if (h1r->qstr && h1r->qlen > 5 && !memcmp(h1r->qstr, "sort=", 5)) {
626
+ sort = h1r->qstr + 5;
627
+ sort_len = h1r->qlen - 5;
628
+ }
629
+
630
+ size_t hslc;
631
+ AUTO_CLOFREE struct mw_fbuf lb;
632
+ if (!fbuf_init(&lb)) return h1_close(h1);
633
+ AUTO_FREE struct h1_src_loc *hslv = accumulate(min, &hslc, lb.fp);
634
+ if (!hslv)
635
+ return h1_close(h1);
636
+
637
+ if (fbuf_close(&lb))
638
+ return h1_close(h1);
639
+
640
+ char *n = lb.ptr;
641
+ for (size_t i = 0; i < hslc; ++i) {
642
+ hslv[i].loc_name = n;
643
+ n += hslv[i].lname_len;
644
+ if (hslv[i].lname_len < 0)
645
+ return h1_close(h1);
646
+ }
647
+
648
+ struct mw_fbuf bdy;
649
+ FILE *fp = wbuf_init(&bdy);
650
+ if (!fp) return h1_close(h1);
651
+
652
+ if (!csv) {
653
+ unsigned depth = (unsigned)CMM_LOAD_SHARED(bt_req_depth);
654
+ fprintf(fp, "<html><head><title>mwrap each &gt;%lu"
655
+ "</title></head><body><p>mwrap each &gt;%lu "
656
+ "(change `%lu' in URL to adjust filtering) - "
657
+ "MWRAP=bt:%u <a href=\"%lu.csv\">.csv</a>",
658
+ min, min, min, depth, min);
659
+ show_stats(fp);
660
+ /* need borders to distinguish multi-level traces */
661
+ if (depth)
662
+ FPUTS("<table\nborder=1><tr>", fp);
663
+ else /* save screen space if only tracing one line */
664
+ FPUTS("<table><tr>", fp);
665
+ }
666
+
667
+ int (*cmp)(const void *, const void *) = NULL;
668
+ if (csv) {
669
+ for (size_t i = 0; i < CAA_ARRAY_SIZE(fields); i++) {
670
+ const char *fn = fields[i].fname;
671
+ if (i)
672
+ fputc(',', fp);
673
+ fputs(fn, fp);
674
+ if (fields[i].flen == sort_len &&
675
+ !memcmp(fn, sort, sort_len))
676
+ cmp = fields[i].cmp;
677
+ }
678
+ fputc('\n', fp);
679
+ } else {
680
+ for (size_t i = 0; i < CAA_ARRAY_SIZE(fields); i++) {
681
+ const char *fn = fields[i].fname;
682
+ FPUTS("<th>", fp);
683
+ if (fields[i].flen == sort_len &&
684
+ !memcmp(fn, sort, sort_len)) {
685
+ cmp = fields[i].cmp;
686
+ fprintf(fp, "<b>%s</b>", fields[i].fname);
687
+ } else {
688
+ fprintf(fp, "<a\nhref=\"./%lu?sort=%s\">%s</a>",
689
+ min, fn, fn);
690
+ }
691
+ FPUTS("</th>", fp);
692
+ }
693
+ }
694
+ if (!csv)
695
+ FPUTS("</tr>", fp);
696
+ if (cmp)
697
+ qsort(hslv, hslc, sizeof(*hslv), cmp);
698
+ else if (!csv)
699
+ FPUTS("<tr><td>sort= not understood</td></tr>", fp);
700
+ if (csv) {
701
+ for (size_t i = 0; i < hslc; i++) {
702
+ struct h1_src_loc *hsl = &hslv[i];
703
+
704
+ fprintf(fp, "%zu,%zu,%zu,%zu,%0.3f,%zu,",
705
+ hsl->bytes, hsl->allocations, hsl->frees,
706
+ hsl->live, hsl->mean_life, hsl->max_life);
707
+ write_q_csv(fp, hsl->loc_name, hsl->lname_len);
708
+ fputc('\n', fp);
709
+ }
710
+ } else {
711
+ for (size_t i = 0; i < hslc; i++) {
712
+ struct h1_src_loc *hsl = &hslv[i];
713
+
714
+ fprintf(fp, "<tr><td>%zu</td><td>%zu</td><td>%zu</td>"
715
+ "<td>%zu</td><td>%0.3f</td><td>%zu</td>",
716
+ hsl->bytes, hsl->allocations, hsl->frees,
717
+ hsl->live, hsl->mean_life, hsl->max_life);
718
+ FPUTS("<td><a\nhref=\"../at/", fp);
719
+
720
+ write_b64_url(fp, src_loc_hash_tip(hsl->sl),
721
+ src_loc_hash_len(hsl->sl));
722
+
723
+ FPUTS("\">", fp);
724
+ write_html(fp, hsl->loc_name, hsl->lname_len);
725
+ FPUTS("</a></td></tr>", fp);
726
+ }
727
+ FPUTS("</table></body></html>", fp);
728
+ }
729
+ return h1_200(h1, &bdy, csv ? TYPE_CSV : TYPE_HTML);
730
+ }
731
+
732
+ /* /$PID/ root endpoint */
733
+ static enum mw_qev pid_root(struct mw_h1 *h1, struct mw_h1req *h1r)
734
+ {
735
+ struct mw_fbuf html;
736
+ FILE *fp = wbuf_init(&html);
737
+ if (!fp) return h1_close(h1);
738
+ #define default_min "2000"
739
+
740
+ int pid = (int)getpid();
741
+ fprintf(fp, "<html><head><title>mwrap PID:%d</title></head><body>"
742
+ "<pre>mwrap PID:%d", pid, pid);
743
+ show_stats(fp);
744
+ FPUTS("\n\n<a\nhref=\"each/" default_min "\">allocations &gt;"
745
+ default_min " bytes</a>""</pre><pre\nid=help>"
746
+ "To get source file and line info for native backtraces, consult your\n"
747
+ "distro for -dbg, -dbgsym, or -debug packages.\n"
748
+ "And/or rebuild your code with debug flags (e.g. `-ggdb3' if using gcc)\n"
749
+ "and don't strip the resulting binaries.\n"
750
+ "You should see locations from the backtrace_symbols(3) function\n"
751
+ "in the form of FILENAME(+OFFSET) or FILENAME(SYMBOL+OFFSET)\n"
752
+ "(e.g. /usr/lib/foo.so(+0xdead) or /usr/lib/foo.so(func+(0xbeef))\n"
753
+ "\n"
754
+ "Any version of addr2line should decode FILENAME(+OFFSET) locations:\n"
755
+ "\n"
756
+ " addr2line -e FILENAME OFFSET\n"
757
+ "\n"
758
+ "SYMBOL+OFFSET requires addr2line from GNU binutils 2.39+ (Aug 2022):\n"
759
+ "\n"
760
+ " addr2line -e FILENAME SYMBOL+OFFSET\n", fp);
761
+
762
+ FPUTS("\n<a\nhref=\"" URL "\">" URL "</a></pre></body></html>", fp);
763
+ return h1_200(h1, &html, TYPE_HTML);
764
+ #undef default_min
765
+ }
766
+
767
+ /* @e is not NUL-terminated */
768
+ static bool sfx_eq(const char *e, const char *sfx)
769
+ {
770
+ for (const char *m = sfx; *m; m++, e++)
771
+ if (*e != *m)
772
+ return false;
773
+ return true;
774
+ }
775
+
776
+ static enum mw_qev h1_dispatch(struct mw_h1 *h1, struct mw_h1req *h1r)
777
+ {
778
+ if (h1r->method_len == 3 && !memcmp(h1r->method, "GET", 3)) {
779
+ const char *c;
780
+
781
+ if ((c = PATH_SKIP(h1r, "/each/"))) {
782
+ errno = 0;
783
+ char *e;
784
+ unsigned long min = strtoul(c, &e, 10);
785
+ if (!errno) {
786
+ if (*e == ' ' || *e == '?')
787
+ return each_gt(h1, h1r, min, false);
788
+ if (sfx_eq(e, ".csv") &&
789
+ (e[4] == ' ' || e[4] == '?'))
790
+ return each_gt(h1, h1r, min, true);
791
+ }
792
+ } else if ((PATH_SKIP(h1r, "/at/"))) {
793
+ return each_at(h1, h1r);
794
+ } else if (h1r->path_len == 1 && h1r->path[0] == '/') {
795
+ return pid_root(h1, h1r);
796
+ }
797
+ } else if (h1r->method_len == 4 && !memcmp(h1r->method, "POST", 4)) {
798
+ if (h1r->path_len == 6 && !memcmp(h1r->path, "/reset", 6))
799
+ return h1_do_reset(h1);
800
+ if (h1r->path_len == 5 && !memcmp(h1r->path, "/trim", 5))
801
+ return h1_do_trim(h1);
802
+ if (h1r->path_len == 4 && !memcmp(h1r->path, "/ctl", 4))
803
+ return h1_do_ctl_finish(h1);
804
+ }
805
+ return h1_404(h1);
806
+ }
807
+
808
+ static void
809
+ prep_trickle(struct mw_h1 *h1, struct mw_h1req *h1r, struct mw_h1d *h1d)
810
+ {
811
+ if (h1->h1r) return; /* already trickling */
812
+ h1->h1r = h1r;
813
+ mwrap_assert(h1d->shared_h1r == h1r);
814
+ h1d->shared_h1r = NULL;
815
+ }
816
+
817
+ /*
818
+ * nothing in the PSGI app actually reads input, but clients tend
819
+ * to send something in the body of POST requests anyways, so we
820
+ * just drain it
821
+ */
822
+ static enum mw_qev h1_drain_input(struct mw_h1 *h1, struct mw_h1req *h1r,
823
+ struct mw_h1d *h1d)
824
+ {
825
+ if (h1r) { /* initial */
826
+ ssize_t overread = h1r->rbuf_len - h1r->pret;
827
+ mwrap_assert(overread >= 0);
828
+ if ((size_t)overread <= h1->in_len)
829
+ h1->in_len -= overread;
830
+ else /* pipelining not supported */
831
+ return h1_400(h1);
832
+ } else { /* continue dealing with a trickle */
833
+ h1r = h1->h1r;
834
+ mwrap_assert(h1r);
835
+ }
836
+ while (h1->in_len > 0) {
837
+ char ibuf[BUFSIZ];
838
+ size_t len = h1->in_len;
839
+ ssize_t r;
840
+
841
+ mwrap_assert(h1->has_input);
842
+ if (len > sizeof(ibuf))
843
+ len = sizeof(ibuf);
844
+
845
+ r = read(h1->fd, ibuf, len);
846
+ if (r > 0) { /* just discard the input */
847
+ h1->in_len -= r;
848
+ } else if (r == 0) {
849
+ return h1_close(h1);
850
+ } else {
851
+ switch (errno) {
852
+ case EAGAIN:
853
+ prep_trickle(h1, h1r, h1d);
854
+ return MW_QEV_RD;
855
+ case ECONNRESET: /* common */
856
+ case ENOTCONN:
857
+ return h1_close(h1);
858
+ default: /* ENOMEM, ENOBUFS, ... */
859
+ assert(errno != EBADF);
860
+ fprintf(stderr, "read: %m\n");
861
+ return h1_close(h1);
862
+ }
863
+ }
864
+ }
865
+ h1->has_input = 0; /* all done with input */
866
+ return h1_dispatch(h1, h1r);
867
+ }
868
+
869
+ static bool valid_end(const char *end)
870
+ {
871
+ switch (*end) {
872
+ case '\r': case ' ': case '\t': case '\n': return true;
873
+ default: return false;
874
+ }
875
+ }
876
+
877
+ /* no error reporting, too much code */
878
+ static void ctl_set(struct mw_h1 *h1, long n)
879
+ {
880
+ if (n >= 0) {
881
+ if (n > MWRAP_BT_MAX)
882
+ n = MWRAP_BT_MAX;
883
+ CMM_STORE_SHARED(bt_req_depth, (uint32_t)n);
884
+ }
885
+ }
886
+
887
+ static enum mw_qev h1_parse_harder(struct mw_h1 *h1, struct mw_h1req *h1r,
888
+ struct mw_h1d *h1d)
889
+ {
890
+ enum { HDR_IGN, HDR_XENC, HDR_CLEN } cur = HDR_IGN;
891
+ char *end;
892
+ struct phr_header *hdr = h1r->hdr;
893
+ long depth = -1;
894
+
895
+ h1->prev_len = 0;
896
+ h1->has_input = 0;
897
+ h1->in_len = 0;
898
+
899
+ for (hdr = h1r->hdr; h1r->nr_hdr--; hdr++) {
900
+ if (NAME_EQ(hdr, "Transfer-Encoding"))
901
+ cur = HDR_XENC;
902
+ else if (NAME_EQ(hdr, "Content-Length"))
903
+ cur = HDR_CLEN;
904
+ else if (NAME_EQ(hdr, "Trailer"))
905
+ return h1_400(h1);
906
+ else if (hdr->name) {
907
+ cur = HDR_IGN;
908
+ /*
909
+ * don't want to increase code to deal with POST
910
+ * request bodies, so let pico handle parameters in
911
+ * HTTP request headers, instead.
912
+ */
913
+ if (NAME_EQ(hdr, "X-Mwrap-BT")) {
914
+ errno = 0;
915
+ depth = strtol(hdr->value, &end, 10);
916
+ if (errno || !valid_end(end))
917
+ depth = -1;
918
+ }
919
+ }
920
+
921
+ /* else: continuation line */
922
+ if (!hdr->value_len)
923
+ continue;
924
+ switch (cur) {
925
+ case HDR_XENC:
926
+ return h1_400(h1);
927
+ case HDR_CLEN:
928
+ if (h1->has_input) return h1_400(h1);
929
+ h1->has_input = 1;
930
+ errno = 0;
931
+ h1->in_len = strtoul(hdr->value, &end, 10);
932
+ if (errno || !valid_end(end))
933
+ return h1_400(h1);
934
+ break;
935
+ case HDR_IGN:
936
+ break;
937
+ }
938
+ }
939
+ if (h1r->path_len < (g_h1d.pid_len + 2))
940
+ return h1_404(h1);
941
+
942
+ /* skip "/$PID" prefix */
943
+ if (*h1r->path == '/' &&
944
+ !memcmp(h1r->path+1, g_h1d.pid_str, g_h1d.pid_len) &&
945
+ h1r->path[1 + g_h1d.pid_len] == '/') {
946
+ h1r->path += 1 + g_h1d.pid_len;
947
+ h1r->path_len -= 1 + g_h1d.pid_len;
948
+ } else {
949
+ return h1_404(h1);
950
+ }
951
+
952
+ /*
953
+ * special case for /ctl, since I don't feel like parsing queries
954
+ * in the request body (ensure no query string, too)
955
+ */
956
+ if (h1r->method_len == 4 && !memcmp(h1r->method, "POST", 4)) {
957
+ if (h1r->path_len == 4 && !memcmp(h1r->path, "/ctl", 4))
958
+ ctl_set(h1, depth);
959
+ }
960
+
961
+ /* break off QUERY_STRING */
962
+ h1r->qstr = memchr(h1r->path, '?', h1r->path_len);
963
+ if (h1r->qstr) {
964
+ ++h1r->qstr; /* ignore '?' */
965
+ h1r->qlen = h1r->path + h1r->path_len - h1r->qstr;
966
+ h1r->path_len -= (h1r->qlen + 1);
967
+ }
968
+ return h1_drain_input(h1, h1r, h1d);
969
+ }
970
+
971
+ static enum mw_qev h1_event_step(struct mw_h1 *h1, struct mw_h1d *h1d)
972
+ {
973
+ struct mw_h1req *h1r;
974
+
975
+ /*
976
+ * simple rule to avoid trivial DoS in HTTP/1.x: never process a
977
+ * new request until you've written out your previous response
978
+ * (and this is why I'm too stupid to do HTTP/2)
979
+ */
980
+ if (h1->wbuf)
981
+ return h1_send_flush(h1);
982
+
983
+ if (h1->has_input)
984
+ return h1_drain_input(h1, NULL, h1d);
985
+ /*
986
+ * The majority of requests can be served using per-daemon rbuf,
987
+ * no need for per-client allocations unless a client trickles
988
+ */
989
+ h1r = h1->h1r ? h1->h1r : h1d->shared_h1r;
990
+ if (!h1r) {
991
+ h1r = h1d->shared_h1r = malloc(sizeof(*h1r));
992
+ if (!h1r) {
993
+ fprintf(stderr, "h1r malloc: %m\n");
994
+ return h1_close(h1);
995
+ }
996
+ }
997
+ for (;;) {
998
+ size_t n = MW_RBUF_SIZE - h1->prev_len;
999
+ ssize_t r = read(h1->fd, &h1r->rbuf[h1->prev_len], n);
1000
+
1001
+ if (r > 0) {
1002
+ h1r->rbuf_len = h1->prev_len + r;
1003
+ h1r->nr_hdr = MW_NR_NAME;
1004
+ h1r->pret = phr_parse_request(h1r->rbuf, h1r->rbuf_len,
1005
+ &h1r->method, &h1r->method_len,
1006
+ &h1r->path, &h1r->path_len,
1007
+ &h1r->minor_ver, h1r->hdr,
1008
+ &h1r->nr_hdr, h1->prev_len);
1009
+ if (h1r->pret > 0)
1010
+ return h1_parse_harder(h1, h1r, h1d);
1011
+ if (h1r->pret == -1)
1012
+ return h1_400(h1); /* parser error */
1013
+
1014
+ mwrap_assert(h1r->pret == -2); /* incomplete */
1015
+ mwrap_assert(h1r->rbuf_len <= MW_RBUF_SIZE &&
1016
+ "bad math");
1017
+
1018
+ /* this should be 413 or 414, don't need the bloat */
1019
+ if (h1r->rbuf_len == MW_RBUF_SIZE)
1020
+ return h1_400(h1);
1021
+ mwrap_assert(h1r->rbuf_len < MW_RBUF_SIZE);
1022
+ h1->prev_len = h1r->rbuf_len;
1023
+ /* loop again */
1024
+ } else if (r == 0) {
1025
+ return h1_close(h1);
1026
+ } else { /* r < 0 */
1027
+ switch (errno) {
1028
+ case EAGAIN: /* likely, detach to per-client buffer */
1029
+ if (h1->prev_len)
1030
+ prep_trickle(h1, h1r, h1d);
1031
+ return MW_QEV_RD;
1032
+ case ECONNRESET: /* common */
1033
+ case ENOTCONN:
1034
+ return h1_close(h1);
1035
+ default: /* ENOMEM, ENOBUFS, ... */
1036
+ assert(errno != EBADF);
1037
+ fprintf(stderr, "read: %m\n");
1038
+ return h1_close(h1);
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ return MW_QEV_RD;
1044
+ }
1045
+
1046
+ static int poll_add(struct mw_h1d *h1d, int fd, short events)
1047
+ {
1048
+ struct pollfd pfd;
1049
+
1050
+ if (!h1d->pb.fp && !fbuf_init(&h1d->pb))
1051
+ return -1;
1052
+ pfd.fd = fd;
1053
+ pfd.events = events;
1054
+ fwrite(&pfd, 1, sizeof(pfd), h1d->pb.fp);
1055
+ return 0; /* success */
1056
+ }
1057
+
1058
+ static struct pollfd *poll_detach(struct mw_h1d *h1d, nfds_t *nfds)
1059
+ {
1060
+ struct pollfd *pfd = NULL; /* our return value */
1061
+
1062
+ /* not sure how to best recover from ENOMEM errors in stdio */
1063
+ if (h1d->pb.fp) {
1064
+ if (fbuf_close(&h1d->pb)) {
1065
+ exit(EXIT_FAILURE);
1066
+ } else {
1067
+ mwrap_assert(h1d->pb.len % sizeof(*pfd) == 0);
1068
+ pfd = (struct pollfd *)h1d->pb.ptr;
1069
+ *nfds = h1d->pb.len / sizeof(*pfd);
1070
+ }
1071
+ }
1072
+
1073
+ /* prepare a new poll buffer the next loop */
1074
+ memset(&h1d->pb, 0, sizeof(h1d->pb));
1075
+
1076
+ return pfd;
1077
+ }
1078
+
1079
+ static void non_fatal_pause(const char *fail_fn)
1080
+ {
1081
+ fprintf(stderr, "%s: %m (non-fatal, pausing mwrap-httpd)\n", fail_fn);
1082
+ poll(NULL, 0, 1000);
1083
+ }
1084
+
1085
+ static void h1d_event_step(struct mw_h1d *h1d)
1086
+ {
1087
+ union mw_sockaddr sa;
1088
+ const char *fail_fn = NULL;
1089
+
1090
+ while (!fail_fn) {
1091
+ socklen_t len = (socklen_t)sizeof(sa);
1092
+ int fd = accept4(h1d->lfd, &sa.any, &len,
1093
+ SOCK_NONBLOCK|SOCK_CLOEXEC);
1094
+
1095
+ if (fd >= 0) {
1096
+ struct mw_h1 *h1 = calloc(1, sizeof(*h1));
1097
+
1098
+ if (h1) {
1099
+ h1->fd = fd;
1100
+ h1->events = POLLIN;
1101
+ cds_list_add_tail(&h1->nd, &h1d->conn);
1102
+ } else {
1103
+ int err = errno;
1104
+ fail_fn = "malloc";
1105
+ close(fd);
1106
+ errno = err;
1107
+ }
1108
+ } else {
1109
+ switch (errno) {
1110
+ case EAGAIN: /* likely */
1111
+ return;
1112
+ case ECONNABORTED: /* common w/ TCP */
1113
+ continue;
1114
+ case EMFILE:
1115
+ case ENFILE:
1116
+ case ENOBUFS:
1117
+ case ENOMEM:
1118
+ case EPERM:
1119
+ fail_fn = "accept4";
1120
+ break;
1121
+ /*
1122
+ * EINVAL, EBADF, ENOTSOCK, EOPNOTSUPP are all fatal
1123
+ * bugs. The last 3 would be wayward closes in the
1124
+ * application being traced
1125
+ */
1126
+ default:
1127
+ fprintf(stderr,
1128
+ "accept4: %m (fatal in mwrap-httpd)\n");
1129
+ abort();
1130
+ }
1131
+ }
1132
+ }
1133
+ /* hope other cleanup work gets done by other threads: */
1134
+ non_fatal_pause(fail_fn);
1135
+ }
1136
+
1137
+ static void h1d_unlink(struct mw_h1d *h1d, bool do_close)
1138
+ {
1139
+ union mw_sockaddr sa;
1140
+ socklen_t len = (socklen_t)sizeof(sa);
1141
+
1142
+ if (h1d->lfd < 0 || !h1d->pid_len)
1143
+ return;
1144
+ if (getsockname(h1d->lfd, &sa.any, &len) < 0) {
1145
+ fprintf(stderr, "getsockname: %m\n");
1146
+ return;
1147
+ }
1148
+ if (do_close) { /* only safe to close if thread isn't running */
1149
+ (void)close(h1d->lfd);
1150
+ h1d->lfd = -1;
1151
+ }
1152
+
1153
+ char p[sizeof(h1d->pid_str)];
1154
+ int rc = snprintf(p, sizeof(p), "%d", (int)getpid());
1155
+
1156
+ if (rc == (int)h1d->pid_len && !memcmp(p, h1d->pid_str, rc))
1157
+ if (unlink(sa.un.sun_path) && errno != ENOENT)
1158
+ fprintf(stderr, "unlink(%s): %m\n", sa.un.sun_path);
1159
+ h1d->pid_len = 0;
1160
+ }
1161
+
1162
+ /* @env is getenv("MWRAP") */
1163
+ static int h1d_init(struct mw_h1d *h1d, const char *menv)
1164
+ {
1165
+ union mw_sockaddr sa = { .un = { .sun_family = AF_UNIX } };
1166
+ #if defined(HAS_SOCKADDR_SA_LEN) || defined(HAVE_STRUCT_SOCKADDR_UN_SUN_LEN)
1167
+ sa.un.sun_len = (unsigned char)sizeof(struct sockaddr_un);
1168
+ #endif
1169
+ const char *env = strstr(menv, "socket_dir:");
1170
+ if (!env) return 1;
1171
+ if (env != menv && env[-1] != ',')
1172
+ return 1;
1173
+ env += sizeof("socket_dir");
1174
+ if (!*env) return 1;
1175
+ const char *end = strchr(env, ',');
1176
+ size_t len = end ? (size_t)(end - env) : strlen(env);
1177
+ if (len == 0)
1178
+ return fprintf(stderr, "socket_dir: cannot be empty\n");
1179
+ if (len >= sizeof(sa.un.sun_path))
1180
+ return fprintf(stderr, "socket_dir:%s too long(%zu)\n",
1181
+ env, len);
1182
+
1183
+ char *p = mempcpy(sa.un.sun_path, env, len);
1184
+ if (p[-1] != '/')
1185
+ *p++ = '/';
1186
+ struct stat sb;
1187
+ if (stat(sa.un.sun_path, &sb) < 0) {
1188
+ if (errno != ENOENT)
1189
+ return fprintf(stderr, "stat(%s): %m\n",
1190
+ sa.un.sun_path);
1191
+ if (mkdir(sa.un.sun_path, 0700) < 0)
1192
+ return fprintf(stderr, "mkdir(%s): %m\n",
1193
+ sa.un.sun_path);
1194
+ } else if (!S_ISDIR(sb.st_mode)) {
1195
+ return fprintf(stderr, "socket_dir:%s is not a directory\n",
1196
+ sa.un.sun_path);
1197
+ }
1198
+ len = sizeof(sa.un.sun_path) - (p - sa.un.sun_path);
1199
+ int rc = snprintf(p, len, "%d.sock", (int)getpid());
1200
+ if (rc >= (int)len)
1201
+ return fprintf(stderr,
1202
+ "socket_dir too long rc=%d > len=%zu\n", rc, len);
1203
+ if (rc < 0)
1204
+ return fprintf(stderr, "we suck at snprintf: %m\n");
1205
+ h1d->pid_len = rc - sizeof(".sock") + 1;
1206
+ memcpy(h1d->pid_str, p, h1d->pid_len);
1207
+ if (unlink(sa.un.sun_path) && errno != ENOENT)
1208
+ return fprintf(stderr, "unlink(%s): %m\n", sa.un.sun_path);
1209
+ h1d->lfd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
1210
+ if (h1d->lfd < 0)
1211
+ return fprintf(stderr, "socket: %m\n");
1212
+ if (bind(h1d->lfd, &sa.any, (socklen_t)sizeof(sa)) < 0) {
1213
+ fprintf(stderr, "bind: %m\n");
1214
+ goto close_fail;
1215
+ }
1216
+ if (listen(h1d->lfd, 1024) < 0) {
1217
+ fprintf(stderr, "listen: %m\n");
1218
+ goto close_fail;
1219
+ }
1220
+ h1d->alive = 1; /* runs in parent, before pthread_create */
1221
+ h1d->running = 1;
1222
+ CDS_INIT_LIST_HEAD(&h1d->conn);
1223
+ return 0;
1224
+ close_fail:
1225
+ h1d_unlink(h1d, true);
1226
+ return 1;
1227
+ }
1228
+
1229
+ /*
1230
+ * epoll|kqueue would make this O(n) function unnecessary, but our (n) is
1231
+ * expected to be tiny (<10): no need to waste kernel memory on epoll|kqueue
1232
+ */
1233
+ static struct mw_h1 *h1_lookup(const struct mw_h1d *h1d, int fd)
1234
+ {
1235
+ struct mw_h1 *h1 = NULL;
1236
+
1237
+ cds_list_for_each_entry(h1, &h1d->conn, nd)
1238
+ if (h1->fd == fd)
1239
+ break;
1240
+ mwrap_assert(h1 && h1->fd == fd && "bad FD");
1241
+ return h1;
1242
+ }
1243
+
1244
+ static void *h1d_run(void *x) /* pthread_create cb */
1245
+ {
1246
+ struct mw_h1d *h1d = x;
1247
+ nfds_t i, nfds;
1248
+ int rc;
1249
+ struct mw_h1 *h1, *nxt;
1250
+ enum mw_qev ev;
1251
+ locating = 1; /* don't report our own memory use */
1252
+
1253
+ for (; uatomic_read(&h1d->alive); ) {
1254
+ while (poll_add(h1d, h1d->lfd, POLLIN))
1255
+ non_fatal_pause("poll_add(lfd)");
1256
+ cds_list_for_each_entry_safe(h1, nxt, &h1d->conn, nd)
1257
+ if (poll_add(h1d, h1->fd, h1->events))
1258
+ h1_close(h1);
1259
+ AUTO_FREE struct pollfd *pfd = poll_detach(h1d, &nfds);
1260
+ rc = pfd ? poll(pfd, nfds, -1) : -1;
1261
+
1262
+ if (rc < 0) {
1263
+ switch (errno) {
1264
+ case EINTR: break; /* shouldn't happen, actually */
1265
+ case ENOMEM: /* may be common */
1266
+ case EINVAL: /* RLIMIT_NOFILE hit */
1267
+ non_fatal_pause("poll");
1268
+ break; /* to forloop where rc<0 */
1269
+ default: /* EFAULT is a fatal bug */
1270
+ fprintf(stderr,
1271
+ "poll: %m (fatal in mwrap-httpd)\n");
1272
+ abort();
1273
+ }
1274
+ } else {
1275
+ for (i = 0; i < nfds &&
1276
+ uatomic_read(&h1d->alive); i++) {
1277
+ if (!pfd[i].revents)
1278
+ continue;
1279
+ if (pfd[i].fd == h1d->lfd) {
1280
+ h1d_event_step(h1d);
1281
+ } else {
1282
+ h1 = h1_lookup(h1d, pfd[i].fd);
1283
+ ev = h1_event_step(h1, h1d);
1284
+ if (ev == MW_QEV_IGNORE)
1285
+ continue;
1286
+ h1->events = ev;
1287
+ }
1288
+ }
1289
+ }
1290
+ }
1291
+ uatomic_set(&h1d->running, 0);
1292
+ free(poll_detach(h1d, &nfds));
1293
+ cds_list_for_each_entry_safe(h1, nxt, &h1d->conn, nd)
1294
+ h1_close(h1);
1295
+ return NULL;
1296
+ }
1297
+
1298
+ static void h1d_atexit(void)
1299
+ {
1300
+ h1d_unlink(&g_h1d, false);
1301
+ }
1302
+
1303
+ static void h1d_stop_join(struct mw_h1d *h1d)
1304
+ {
1305
+ union mw_sockaddr sa;
1306
+ socklen_t len = (socklen_t)sizeof(sa);
1307
+ int e, sfd;
1308
+ void *ret;
1309
+ #define ERR ": (stopping mwrap-httpd before fork): "
1310
+
1311
+ mwrap_assert(uatomic_read(&h1d->alive) == 0);
1312
+ while (getsockname(h1d->lfd, &sa.any, &len) < 0) {
1313
+ non_fatal_pause("getsockname"ERR);
1314
+ if (!uatomic_read(&h1d->running))
1315
+ goto join_thread;
1316
+ }
1317
+ retry_socket:
1318
+ while ((sfd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)) < 0) {
1319
+ non_fatal_pause("socket"ERR);
1320
+ if (!uatomic_read(&h1d->running))
1321
+ goto join_thread;
1322
+ }
1323
+ if (connect(sfd, &sa.any, len) < 0) {
1324
+ int e = errno;
1325
+ close(sfd);
1326
+ errno = e;
1327
+ non_fatal_pause("connect"ERR);
1328
+ if (!uatomic_read(&h1d->running))
1329
+ goto join_thread;
1330
+ goto retry_socket;
1331
+ }
1332
+ #undef ERR
1333
+ (void)close(sfd);
1334
+ join_thread:
1335
+ e = pthread_join(h1d->tid, &ret);
1336
+ if (e) { /* EDEADLK, EINVAL, ESRCH are all fatal bugs */
1337
+ fprintf(stderr, "BUG? pthread_join: %s\n", strerror(e));
1338
+ abort();
1339
+ }
1340
+ h1d_unlink(h1d, true);
1341
+ }
1342
+
1343
+ static void h1d_atfork_prepare(void)
1344
+ {
1345
+ if (uatomic_cmpxchg(&g_h1d.alive, 1, 0))
1346
+ h1d_stop_join(&g_h1d);
1347
+ }
1348
+
1349
+ static void h1d_start(void) /* may be called as pthread_atfork child cb */
1350
+ {
1351
+ if (mwrap_env && !h1d_init(&g_h1d, mwrap_env) && g_h1d.alive) {
1352
+ int rc = pthread_create(&g_h1d.tid, NULL, h1d_run, &g_h1d);
1353
+ if (rc) { /* non-fatal */
1354
+ fprintf(stderr, "pthread_create: %s\n", strerror(rc));
1355
+ g_h1d.alive = 0;
1356
+ g_h1d.running = 0;
1357
+ h1d_unlink(&g_h1d, true);
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ /* must be called with global_mtx held */
1363
+ static void h1d_atfork_parent(void)
1364
+ {
1365
+ if (g_h1d.lfd < 0)
1366
+ h1d_start();
1367
+ }