2.52
httpd.c
1/* snac - A simple, minimalistic ActivityPub instance */
2/* copyright (c) 2022 - 2024 grunfink et al. / MIT license */
3
4#include "xs.h"
5#include "xs_io.h"
6#include "xs_json.h"
7#include "xs_socket.h"
8#include "xs_httpd.h"
9#include "xs_mime.h"
10#include "xs_time.h"
11#include "xs_openssl.h"
12#include "xs_fcgi.h"
13#include "xs_html.h"
14
15#include "snac.h"
16
17#include <setjmp.h>
18#include <pthread.h>
19#include <semaphore.h>
20#include <fcntl.h>
21#include <stdint.h>
22
23#include <sys/resource.h> // for getrlimit()
24
25#include <sys/mman.h>
26
27#ifdef USE_POLL_FOR_SLEEP
28#include <poll.h>
29#endif
30
31/** server state **/
32srv_state *p_state = NULL;
33
34
35/** job control **/
36
37/* mutex to access the lists of jobs */
38static pthread_mutex_t job_mutex;
39
40/* semaphore to trigger job processing */
41static sem_t *job_sem;
42
43typedef struct job_fifo_item {
44 struct job_fifo_item *next;
45 xs_val *job;
46} job_fifo_item;
47
48static job_fifo_item *job_fifo_first = NULL;
49static job_fifo_item *job_fifo_last = NULL;
50
51
52/** other global data **/
53
54static jmp_buf on_break;
55
56
57/** code **/
58
59/* nodeinfo 2.0 template */
60const char *nodeinfo_2_0_template = ""
61 "{\"version\":\"2.0\","
62 "\"software\":{\"name\":\"snac\",\"version\":\"" VERSION "\"},"
63 "\"protocols\":[\"activitypub\"],"
64 "\"services\":{\"outbound\":[],\"inbound\":[]},"
65 "\"usage\":{\"users\":{\"total\":%d,\"activeMonth\":%d,\"activeHalfyear\":%d},"
66 "\"localPosts\":%d},"
67 "\"openRegistrations\":false,\"metadata\":{}}";
68
69xs_str *nodeinfo_2_0(void)
70/* builds a nodeinfo json object */
71{
72 int n_utotal = 0;
73 int n_umonth = 0;
74 int n_uhyear = 0;
75 int n_posts = 0;
76 xs *users = user_list();
77 xs_list *p = users;
78 char *v;
79 double now = (double)time(NULL);
80
81 while (xs_list_iter(&p, &v)) {
82 /* build the full path name to the last usage log */
83 xs *llfn = xs_fmt("%s/user/%s/lastlog.txt", srv_basedir, v);
84 double llsecs = now - mtime(llfn);
85
86 if (llsecs < 60 * 60 * 24 * 30 * 6) {
87 n_uhyear++;
88
89 if (llsecs < 60 * 60 * 24 * 30)
90 n_umonth++;
91 }
92
93 n_utotal++;
94
95 /* build the file to each user public.idx */
96 xs *pidxfn = xs_fmt("%s/user/%s/public.idx", srv_basedir, v);
97 n_posts += index_len(pidxfn);
98 }
99
100 return xs_fmt(nodeinfo_2_0_template, n_utotal, n_umonth, n_uhyear, n_posts);
101}
102
103
104static xs_str *greeting_html(void)
105/* processes and returns greeting.html */
106{
107 /* try to open greeting.html */
108 xs *fn = xs_fmt("%s/greeting.html", srv_basedir);
109 FILE *f;
110 xs_str *s = NULL;
111
112 if ((f = fopen(fn, "r")) != NULL) {
113 s = xs_readall(f);
114 fclose(f);
115
116 /* replace %host% */
117 s = xs_replace_i(s, "%host%", xs_dict_get(srv_config, "host"));
118
119 const char *adm_email = xs_dict_get(srv_config, "admin_email");
120 if (xs_is_null(adm_email) || *adm_email == '\0')
121 adm_email = "the administrator of this instance";
122
123 /* replace %admin_email */
124 s = xs_replace_i(s, "%admin_email%", adm_email);
125
126 /* does it have a %userlist% mark? */
127 if (xs_str_in(s, "%userlist%") != -1) {
128 char *host = xs_dict_get(srv_config, "host");
129 xs *list = user_list();
130 xs_list *p = list;
131 xs_str *uid;
132
133 xs_html *ul = xs_html_tag("ul",
134 xs_html_attr("class", "snac-user-list"));
135
136 p = list;
137 while (xs_list_iter(&p, &uid)) {
138 snac user;
139
140 if (user_open(&user, uid)) {
141 xs_html_add(ul,
142 xs_html_tag("li",
143 xs_html_tag("a",
144 xs_html_attr("href", user.actor),
145 xs_html_text("@"),
146 xs_html_text(uid),
147 xs_html_text("@"),
148 xs_html_text(host),
149 xs_html_text(" ("),
150 xs_html_text(xs_dict_get(user.config, "name")),
151 xs_html_text(")"))));
152
153 user_free(&user);
154 }
155 }
156
157 xs *s1 = xs_html_render(ul);
158 s = xs_replace_i(s, "%userlist%", s1);
159 }
160 }
161
162 return s;
163}
164
165
166int server_get_handler(xs_dict *req, const char *q_path,
167 char **body, int *b_size, char **ctype)
168/* basic server services */
169{
170 int status = 0;
171
172 /* is it the server root? */
173 if (*q_path == '\0') {
174 xs_dict *q_vars = xs_dict_get(req, "q_vars");
175 char *t = NULL;
176
177 if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) {
178 /** search by tag **/
179 int skip = 0;
180 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries"));
181 char *v;
182
183 if ((v = xs_dict_get(q_vars, "skip")) != NULL)
184 skip = atoi(v);
185 if ((v = xs_dict_get(q_vars, "show")) != NULL)
186 show = atoi(v);
187
188 xs *tl = tag_search(t, skip, show + 1);
189 int more = 0;
190 if (xs_list_len(tl) >= show + 1) {
191 /* drop the last one */
192 tl = xs_list_del(tl, -1);
193 more = 1;
194 }
195
196 char *accept = xs_dict_get(req, "accept");
197 if (!xs_is_null(accept) && strcmp(accept, "application/rss+xml") == 0) {
198 xs *link = xs_fmt("%s/?t=%s", srv_baseurl, t);
199
200 *body = timeline_to_rss(NULL, tl, link, link, link);
201 *ctype = "application/rss+xml; charset=utf-8";
202 }
203 else
204 *body = html_timeline(NULL, tl, 0, skip, show, more, t, NULL, 0);
205 }
206 else
207 if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) {
208 /** instance timeline **/
209 xs *tl = timeline_instance_list(0, 30);
210 *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL, NULL, 0);
211 }
212 else
213 *body = greeting_html();
214
215 if (*body)
216 status = 200;
217 }
218 else
219 if (strcmp(q_path, "/susie.png") == 0 || strcmp(q_path, "/favicon.ico") == 0 ) {
220 status = 200;
221 *body = xs_base64_dec(default_avatar_base64(), b_size);
222 *ctype = "image/png";
223 }
224 else
225 if (strcmp(q_path, "/.well-known/nodeinfo") == 0) {
226 status = 200;
227 *ctype = "application/json; charset=utf-8";
228 *body = xs_fmt("{\"links\":["
229 "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.0\","
230 "\"href\":\"%s/nodeinfo_2_0\"}]}",
231 srv_baseurl);
232 }
233 else
234 if (strcmp(q_path, "/.well-known/host-meta") == 0) {
235 status = 200;
236 *ctype = "application/xrd+xml";
237 *body = xs_fmt("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
238 "<XRD>"
239 "<Link rel=\"lrdd\" type=\"application/xrd+xml\" template=\"https://%s/.well-known/webfinger?resource={uri}\"/>"
240 "</XRD>", xs_dict_get(srv_config, "host"));
241 }
242 else
243 if (strcmp(q_path, "/nodeinfo_2_0") == 0) {
244 status = 200;
245 *ctype = "application/json; charset=utf-8";
246 *body = nodeinfo_2_0();
247 }
248 else
249 if (strcmp(q_path, "/robots.txt") == 0) {
250 status = 200;
251 *ctype = "text/plain";
252 *body = xs_str_new("User-agent: *\n"
253 "Disallow: /\n");
254 }
255
256 if (status != 0)
257 srv_debug(1, xs_fmt("server_get_handler serving '%s' %d", q_path, status));
258
259 return status;
260}
261
262
263void httpd_connection(FILE *f)
264/* the connection processor */
265{
266 xs *req;
267 char *method;
268 int status = 0;
269 xs_str *body = NULL;
270 int b_size = 0;
271 char *ctype = NULL;
272 xs *headers = xs_dict_new();
273 xs *q_path = NULL;
274 xs *payload = NULL;
275 xs *etag = NULL;
276 int p_size = 0;
277 char *p;
278 int fcgi_id;
279
280 if (p_state->use_fcgi)
281 req = xs_fcgi_request(f, &payload, &p_size, &fcgi_id);
282 else
283 req = xs_httpd_request(f, &payload, &p_size);
284
285 if (req == NULL) {
286 /* probably because a timeout */
287 fclose(f);
288 return;
289 }
290
291 if (!(method = xs_dict_get(req, "method")) || !(p = xs_dict_get(req, "path"))) {
292 /* missing needed headers; discard */
293 fclose(f);
294 return;
295 }
296
297 q_path = xs_dup(p);
298
299 /* crop the q_path from leading / and the prefix */
300 if (xs_endswith(q_path, "/"))
301 q_path = xs_crop_i(q_path, 0, -1);
302
303 p = xs_dict_get(srv_config, "prefix");
304 if (xs_startswith(q_path, p))
305 q_path = xs_crop_i(q_path, strlen(p), 0);
306
307 if (strcmp(method, "GET") == 0 || strcmp(method, "HEAD") == 0) {
308 /* cascade through */
309 if (status == 0)
310 status = server_get_handler(req, q_path, &body, &b_size, &ctype);
311
312 if (status == 0)
313 status = webfinger_get_handler(req, q_path, &body, &b_size, &ctype);
314
315 if (status == 0)
316 status = activitypub_get_handler(req, q_path, &body, &b_size, &ctype);
317
318#ifndef NO_MASTODON_API
319 if (status == 0)
320 status = oauth_get_handler(req, q_path, &body, &b_size, &ctype);
321
322 if (status == 0)
323 status = mastoapi_get_handler(req, q_path, &body, &b_size, &ctype);
324#endif /* NO_MASTODON_API */
325
326 if (status == 0)
327 status = html_get_handler(req, q_path, &body, &b_size, &ctype, &etag);
328 }
329 else
330 if (strcmp(method, "POST") == 0) {
331
332#ifndef NO_MASTODON_API
333 if (status == 0)
334 status = oauth_post_handler(req, q_path,
335 payload, p_size, &body, &b_size, &ctype);
336
337 if (status == 0)
338 status = mastoapi_post_handler(req, q_path,
339 payload, p_size, &body, &b_size, &ctype);
340#endif
341
342 if (status == 0)
343 status = activitypub_post_handler(req, q_path,
344 payload, p_size, &body, &b_size, &ctype);
345
346 if (status == 0)
347 status = html_post_handler(req, q_path,
348 payload, p_size, &body, &b_size, &ctype);
349 }
350 else
351 if (strcmp(method, "PUT") == 0) {
352
353#ifndef NO_MASTODON_API
354 if (status == 0)
355 status = mastoapi_put_handler(req, q_path,
356 payload, p_size, &body, &b_size, &ctype);
357#endif
358
359 }
360 else
361 if (strcmp(method, "OPTIONS") == 0) {
362 status = 200;
363 }
364 else
365 if (strcmp(method, "DELETE") == 0) {
366#ifndef NO_MASTODON_API
367 if (status == 0)
368 status = mastoapi_delete_handler(req, q_path,
369 payload, p_size, &body, &b_size, &ctype);
370#endif
371 }
372
373 /* unattended? it's an error */
374 if (status == 0) {
375 srv_archive_error("unattended_method", "unattended method", req, payload);
376 srv_debug(1, xs_fmt("httpd_connection unattended %s %s", method, q_path));
377 status = 404;
378 }
379
380 if (status == 403)
381 body = xs_str_new("<h1>403 Forbidden</h1>");
382
383 if (status == 404)
384 body = xs_str_new("<h1>404 Not Found</h1>");
385
386 if (status == 400 && body != NULL)
387 body = xs_str_new("<h1>400 Bad Request</h1>");
388
389 if (status == 303)
390 headers = xs_dict_append(headers, "location", body);
391
392 if (status == 401) {
393 xs *www_auth = xs_fmt("Basic realm=\"@%s@%s snac login\"",
394 body, xs_dict_get(srv_config, "host"));
395
396 headers = xs_dict_append(headers, "WWW-Authenticate", www_auth);
397 headers = xs_dict_append(headers, "Cache-Control", "no-cache, must-revalidate, max-age=0");
398 }
399
400 if (ctype == NULL)
401 ctype = "text/html; charset=utf-8";
402
403 headers = xs_dict_append(headers, "content-type", ctype);
404 headers = xs_dict_append(headers, "x-creator", USER_AGENT);
405
406 if (!xs_is_null(etag))
407 headers = xs_dict_append(headers, "etag", etag);
408
409 /* if there are any additional headers, add them */
410 xs_dict *more_headers = xs_dict_get(srv_config, "http_headers");
411 if (xs_type(more_headers) == XSTYPE_DICT) {
412 char *k, *v;
413 int c = 0;
414 while (xs_dict_next(more_headers, &k, &v, &c))
415 headers = xs_dict_set(headers, k, v);
416 }
417
418 if (b_size == 0 && body != NULL)
419 b_size = strlen(body);
420
421 /* if it was a HEAD, no body will be sent */
422 if (strcmp(method, "HEAD") == 0)
423 body = xs_free(body);
424
425 headers = xs_dict_append(headers, "access-control-allow-origin", "*");
426 headers = xs_dict_append(headers, "access-control-allow-headers", "*");
427
428 if (p_state->use_fcgi)
429 xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
430 else
431 xs_httpd_response(f, status, headers, body, b_size);
432
433 fclose(f);
434
435 srv_archive("RECV", NULL, req, payload, p_size, status, headers, body, b_size);
436
437 /* JSON validation check */
438 if (!xs_is_null(body) && strcmp(ctype, "application/json") == 0) {
439 xs *j = xs_json_loads(body);
440
441 if (j == NULL) {
442 srv_log(xs_fmt("bad JSON"));
443 srv_archive_error("bad_json", "bad JSON", req, body);
444 }
445 }
446
447 xs_free(body);
448}
449
450
451void job_post(const xs_val *job, int urgent)
452/* posts a job for the threads to process it */
453{
454 if (job != NULL) {
455 /* lock the mutex */
456 pthread_mutex_lock(&job_mutex);
457
458 job_fifo_item *i = xs_realloc(NULL, sizeof(job_fifo_item));
459 *i = (job_fifo_item){ NULL, xs_dup(job) };
460
461 if (job_fifo_first == NULL)
462 job_fifo_first = job_fifo_last = i;
463 else
464 if (urgent) {
465 /* prepend */
466 i->next = job_fifo_first;
467 job_fifo_first = i;
468 }
469 else {
470 /* append */
471 job_fifo_last->next = i;
472 job_fifo_last = i;
473 }
474
475 p_state->job_fifo_size++;
476
477 if (p_state->job_fifo_size > p_state->peak_job_fifo_size)
478 p_state->peak_job_fifo_size = p_state->job_fifo_size;
479
480 /* unlock the mutex */
481 pthread_mutex_unlock(&job_mutex);
482
483 /* ask for someone to attend it */
484 sem_post(job_sem);
485 }
486}
487
488
489void job_wait(xs_val **job)
490/* waits for an available job */
491{
492 *job = NULL;
493
494 if (sem_wait(job_sem) == 0) {
495 /* lock the mutex */
496 pthread_mutex_lock(&job_mutex);
497
498 /* dequeue */
499 job_fifo_item *i = job_fifo_first;
500
501 if (i != NULL) {
502 job_fifo_first = i->next;
503
504 if (job_fifo_first == NULL)
505 job_fifo_last = NULL;
506
507 *job = i->job;
508 xs_free(i);
509
510 p_state->job_fifo_size--;
511 }
512
513 /* unlock the mutex */
514 pthread_mutex_unlock(&job_mutex);
515 }
516}
517
518
519static void *job_thread(void *arg)
520/* job thread */
521{
522 int pid = (int)(uintptr_t)arg;
523
524 srv_debug(1, xs_fmt("job thread %d started", pid));
525
526 for (;;) {
527 xs *job = NULL;
528
529 p_state->th_state[pid] = THST_WAIT;
530
531 job_wait(&job);
532
533 if (job == NULL) /* corrupted message? */
534 continue;
535
536 if (xs_type(job) == XSTYPE_FALSE) /* special message: exit */
537 break;
538 else
539 if (xs_type(job) == XSTYPE_DATA) {
540 /* it's a socket */
541 FILE *f = NULL;
542
543 p_state->th_state[pid] = THST_IN;
544
545 xs_data_get(&f, job);
546
547 if (f != NULL)
548 httpd_connection(f);
549 }
550 else {
551 /* it's a q_item */
552 p_state->th_state[pid] = THST_QUEUE;
553
554 process_queue_item(job);
555 }
556 }
557
558 p_state->th_state[pid] = THST_STOP;
559
560 srv_debug(1, xs_fmt("job thread %d stopped", pid));
561
562 return NULL;
563}
564
565/* background thread sleep control */
566static pthread_mutex_t sleep_mutex;
567static pthread_cond_t sleep_cond;
568
569static void *background_thread(void *arg)
570/* background thread (queue management and other things) */
571{
572 time_t purge_time;
573
574 (void)arg;
575
576 /* first purge time */
577 purge_time = time(NULL) + 10 * 60;
578
579 srv_log(xs_fmt("background thread started"));
580
581 while (p_state->srv_running) {
582 time_t t;
583 int cnt = 0;
584
585 p_state->th_state[0] = THST_QUEUE;
586
587 {
588 xs *list = user_list();
589 char *p, *uid;
590
591 /* process queues for all users */
592 p = list;
593 while (xs_list_iter(&p, &uid)) {
594 snac snac;
595
596 if (user_open(&snac, uid)) {
597 cnt += process_user_queue(&snac);
598 user_free(&snac);
599 }
600 }
601 }
602
603 /* global queue */
604 cnt += process_queue();
605
606 /* time to purge? */
607 if ((t = time(NULL)) > purge_time) {
608 /* next purge time is tomorrow */
609 purge_time = t + 24 * 60 * 60;
610
611 xs *q_item = xs_dict_new();
612 q_item = xs_dict_append(q_item, "type", "purge");
613 job_post(q_item, 0);
614 }
615
616 if (cnt == 0) {
617 /* sleep 3 seconds */
618
619 p_state->th_state[0] = THST_WAIT;
620
621#ifdef USE_POLL_FOR_SLEEP
622 poll(NULL, 0, 3 * 1000);
623#else
624 struct timespec ts;
625
626 clock_gettime(CLOCK_REALTIME, &ts);
627 ts.tv_sec += 3;
628
629 pthread_mutex_lock(&sleep_mutex);
630 while (pthread_cond_timedwait(&sleep_cond, &sleep_mutex, &ts) == 0);
631 pthread_mutex_unlock(&sleep_mutex);
632#endif
633 }
634 }
635
636 p_state->th_state[0] = THST_STOP;
637
638 srv_log(xs_fmt("background thread stopped"));
639
640 return NULL;
641}
642
643
644void term_handler(int s)
645{
646 (void)s;
647
648 longjmp(on_break, 1);
649}
650
651
652srv_state *srv_state_op(xs_str **fname, int op)
653/* opens or deletes the shared memory object */
654{
655 int fd;
656 srv_state *ss = NULL;
657
658 if (*fname == NULL)
659 *fname = xs_fmt("/%s_snac_state", xs_dict_get(srv_config, "host"));
660
661 switch (op) {
662 case 0: /* open for writing */
663 if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) {
664 ftruncate(fd, sizeof(*ss));
665
666 if ((ss = mmap(0, sizeof(*ss), PROT_READ | PROT_WRITE,
667 MAP_SHARED, fd, 0)) == MAP_FAILED)
668 ss = NULL;
669
670 close(fd);
671 }
672
673 if (ss == NULL) {
674 /* shared memory error: just create a plain structure */
675 srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno)));
676 ss = malloc(sizeof(*ss));
677 }
678
679 /* init structure */
680 *ss = (srv_state){0};
681 ss->s_size = sizeof(*ss);
682
683 break;
684
685 case 1: /* open for reading */
686 if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) {
687 if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED)
688 ss = NULL;
689
690 close(fd);
691 }
692
693 if (ss == NULL) {
694 /* shared memory error */
695 srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno)));
696 }
697 else
698 if (ss->s_size != sizeof(*ss)) {
699 srv_log(xs_fmt("error: struct size mismatch (%d != %d)",
700 ss->s_size, sizeof(*ss)));
701
702 munmap(ss, sizeof(*ss));
703
704 ss = NULL;
705 }
706
707 break;
708
709 case 2: /* unlink */
710 if (*fname)
711 shm_unlink(*fname);
712
713 break;
714 }
715
716 return ss;
717}
718
719
720void httpd(void)
721/* starts the server */
722{
723 const char *address;
724 const char *port;
725 int rs;
726 pthread_t threads[MAX_THREADS] = {0};
727 int n;
728 xs *sem_name = NULL;
729 xs *shm_name = NULL;
730 sem_t anon_job_sem;
731
732 address = xs_dict_get(srv_config, "address");
733 port = xs_number_str(xs_dict_get(srv_config, "port"));
734
735 if ((rs = xs_socket_server(address, port)) == -1) {
736 srv_log(xs_fmt("cannot bind socket to %s:%s", address, port));
737 return;
738 }
739
740 /* setup the server stat structure */
741 p_state = srv_state_op(&shm_name, 0);
742
743 p_state->srv_start_time = time(NULL);
744
745 p_state->use_fcgi = xs_type(xs_dict_get(srv_config, "fastcgi")) == XSTYPE_TRUE;
746
747 p_state->srv_running = 1;
748
749 signal(SIGPIPE, SIG_IGN);
750 signal(SIGTERM, term_handler);
751 signal(SIGINT, term_handler);
752
753 srv_log(xs_fmt("httpd%s start %s:%s %s", p_state->use_fcgi ? " (FastCGI)" : "",
754 address, port, USER_AGENT));
755
756 /* show the number of usable file descriptors */
757 struct rlimit r;
758 getrlimit(RLIMIT_NOFILE, &r);
759 srv_debug(0, xs_fmt("available (rlimit) fds: %d (cur) / %d (max)",
760 (int) r.rlim_cur, (int) r.rlim_max));
761
762 /* initialize the job control engine */
763 pthread_mutex_init(&job_mutex, NULL);
764 sem_name = xs_fmt("/job_%d", getpid());
765 job_sem = sem_open(sem_name, O_CREAT, 0644, 0);
766
767 if (job_sem == NULL) {
768 /* error opening a named semaphore; try with an anonymous one */
769 if (sem_init(&anon_job_sem, 0, 0) != -1)
770 job_sem = &anon_job_sem;
771 }
772
773 if (job_sem == NULL) {
774 srv_log(xs_fmt("fatal error: cannot create semaphore -- cannot continue"));
775 return;
776 }
777
778 /* initialize sleep control */
779 pthread_mutex_init(&sleep_mutex, NULL);
780 pthread_cond_init(&sleep_cond, NULL);
781
782 p_state->n_threads = xs_number_get(xs_dict_get(srv_config, "num_threads"));
783
784#ifdef _SC_NPROCESSORS_ONLN
785 if (p_state->n_threads == 0) {
786 /* get number of CPUs on the machine */
787 p_state->n_threads = sysconf(_SC_NPROCESSORS_ONLN);
788 }
789#endif
790
791 if (p_state->n_threads < 4)
792 p_state->n_threads = 4;
793
794 if (p_state->n_threads > MAX_THREADS)
795 p_state->n_threads = MAX_THREADS;
796
797 srv_debug(0, xs_fmt("using %d threads", p_state->n_threads));
798
799 /* thread #0 is the background thread */
800 pthread_create(&threads[0], NULL, background_thread, NULL);
801
802 /* the rest of threads are for job processing */
803 char *ptr = (char *) 0x1;
804 for (n = 1; n < p_state->n_threads; n++)
805 pthread_create(&threads[n], NULL, job_thread, ptr++);
806
807 if (setjmp(on_break) == 0) {
808 for (;;) {
809 FILE *f = xs_socket_accept(rs);
810
811 if (f != NULL) {
812 xs *job = xs_data_new(&f, sizeof(FILE *));
813 job_post(job, 1);
814 }
815 else
816 break;
817 }
818 }
819
820 p_state->srv_running = 0;
821
822 /* send as many exit jobs as working threads */
823 for (n = 1; n < p_state->n_threads; n++)
824 job_post(xs_stock(XSTYPE_FALSE), 0);
825
826 /* wait for all the threads to exit */
827 for (n = 0; n < p_state->n_threads; n++)
828 pthread_join(threads[n], NULL);
829
830 sem_close(job_sem);
831 sem_unlink(sem_name);
832
833 srv_state_op(&shm_name, 2);
834
835 xs *uptime = xs_str_time_diff(time(NULL) - p_state->srv_start_time);
836
837 srv_log(xs_fmt("httpd%s stop %s:%s (run time: %s)",
838 p_state->use_fcgi ? " (FastCGI)" : "",
839 address, port, uptime));
840}