mwrap 2.3.0 → 3.0.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/COPYING +617 -282
- data/Documentation/.gitignore +2 -0
- data/Documentation/GNUmakefile +63 -0
- data/Documentation/mwrap.1 +242 -0
- data/Documentation/mwrap.pod +123 -0
- data/MANIFEST +13 -1
- data/README +25 -17
- data/Rakefile +10 -2
- data/VERSION-GEN +1 -1
- data/ext/mwrap/check.h +23 -0
- data/ext/mwrap/dlmalloc_c.h +6294 -0
- data/ext/mwrap/extconf.rb +3 -7
- data/ext/mwrap/gcc.h +13 -0
- data/ext/mwrap/httpd.h +1367 -0
- data/ext/mwrap/mwrap.c +44 -1151
- data/ext/mwrap/mwrap_core.h +1095 -0
- data/ext/mwrap/mymalloc.h +299 -0
- data/ext/mwrap/picohttpparser.h +92 -0
- data/ext/mwrap/picohttpparser_c.h +670 -0
- data/lib/mwrap/version.rb +1 -1
- data/lib/mwrap_rack.rb +14 -58
- data/mwrap.gemspec +10 -3
- data/t/httpd.t +191 -0
- data/t/test_common.perl +54 -0
- data/test/test_mwrap.rb +34 -50
- metadata +21 -7
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("&", fp); break;
|
367
|
+
case '<': FPUTS("<", fp); break;
|
368
|
+
case '>': FPUTS(">", fp); break;
|
369
|
+
case '"': FPUTS(""", fp); break;
|
370
|
+
case '\'': FPUTS("'", 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 <= 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 >%lu"
|
655
|
+
"</title></head><body><p>mwrap each >%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 >"
|
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
|
+
}
|