2.53
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 xs *page = xs_fmt("?t=%s", t);
205 xs *title = xs_fmt(L("Search results for tag #%s"), t);
206 *body = html_timeline(NULL, tl, 0, skip, show, more, title, page, 0);
207 }
208 }
209 else
210 if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) {
211 /** instance timeline **/
212 xs *tl = timeline_instance_list(0, 30);
213 *body = html_timeline(NULL, tl, 0, 0, 0, 0,
214 L("Recent posts by users in this instance"), NULL, 0);
215 }
216 else
217 *body = greeting_html();
218
219 if (*body)
220 status = 200;
221 }
222 else
223 if (strcmp(q_path, "/susie.png") == 0 || strcmp(q_path, "/favicon.ico") == 0 ) {
224 status = 200;
225 *body = xs_base64_dec(default_avatar_base64(), b_size);
226 *ctype = "image/png";
227 }
228 else
229 if (strcmp(q_path, "/.well-known/nodeinfo") == 0) {
230 status = 200;
231 *ctype = "application/json; charset=utf-8";
232 *body = xs_fmt("{\"links\":["
233 "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.0\","
234 "\"href\":\"%s/nodeinfo_2_0\"}]}",
235 srv_baseurl);
236 }
237 else
238 if (strcmp(q_path, "/.well-known/host-meta") == 0) {
239 status = 200;
240 *ctype = "application/xrd+xml";
241 *body = xs_fmt("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
242 "<XRD>"
243 "<Link rel=\"lrdd\" type=\"application/xrd+xml\" template=\"https://%s/.well-known/webfinger?resource={uri}\"/>"
244 "</XRD>", xs_dict_get(srv_config, "host"));
245 }
246 else
247 if (strcmp(q_path, "/nodeinfo_2_0") == 0) {
248 status = 200;
249 *ctype = "application/json; charset=utf-8";
250 *body = nodeinfo_2_0();
251 }
252 else
253 if (strcmp(q_path, "/robots.txt") == 0) {
254 status = 200;
255 *ctype = "text/plain";
256 *body = xs_str_new("User-agent: *\n"
257 "Disallow: /\n");
258 }
259
260 if (status != 0)
261 srv_debug(1, xs_fmt("server_get_handler serving '%s' %d", q_path, status));
262
263 return status;
264}
265
266
267void httpd_connection(FILE *f)
268/* the connection processor */
269{
270 xs *req;
271 char *method;
272 int status = 0;
273 xs_str *body = NULL;
274 int b_size = 0;
275 char *ctype = NULL;
276 xs *headers = xs_dict_new();
277 xs *q_path = NULL;
278 xs *payload = NULL;
279 xs *etag = NULL;
280 int p_size = 0;
281 char *p;
282 int fcgi_id;
283
284 if (p_state->use_fcgi)
285 req = xs_fcgi_request(f, &payload, &p_size, &fcgi_id);
286 else
287 req = xs_httpd_request(f, &payload, &p_size);
288
289 if (req == NULL) {
290 /* probably because a timeout */
291 fclose(f);
292 return;
293 }
294
295 if (!(method = xs_dict_get(req, "method")) || !(p = xs_dict_get(req, "path"))) {
296 /* missing needed headers; discard */
297 fclose(f);
298 return;
299 }
300
301 q_path = xs_dup(p);
302
303 /* crop the q_path from leading / and the prefix */
304 if (xs_endswith(q_path, "/"))
305 q_path = xs_crop_i(q_path, 0, -1);
306
307 p = xs_dict_get(srv_config, "prefix");
308 if (xs_startswith(q_path, p))
309 q_path = xs_crop_i(q_path, strlen(p), 0);
310
311 if (strcmp(method, "GET") == 0 || strcmp(method, "HEAD") == 0) {
312 /* cascade through */
313 if (status == 0)
314 status = server_get_handler(req, q_path, &body, &b_size, &ctype);
315
316 if (status == 0)
317 status = webfinger_get_handler(req, q_path, &body, &b_size, &ctype);
318
319 if (status == 0)
320 status = activitypub_get_handler(req, q_path, &body, &b_size, &ctype);
321
322#ifndef NO_MASTODON_API
323 if (status == 0)
324 status = oauth_get_handler(req, q_path, &body, &b_size, &ctype);
325
326 if (status == 0)
327 status = mastoapi_get_handler(req, q_path, &body, &b_size, &ctype);
328#endif /* NO_MASTODON_API */
329
330 if (status == 0)
331 status = html_get_handler(req, q_path, &body, &b_size, &ctype, &etag);
332 }
333 else
334 if (strcmp(method, "POST") == 0) {
335
336#ifndef NO_MASTODON_API
337 if (status == 0)
338 status = oauth_post_handler(req, q_path,
339 payload, p_size, &body, &b_size, &ctype);
340
341 if (status == 0)
342 status = mastoapi_post_handler(req, q_path,
343 payload, p_size, &body, &b_size, &ctype);
344#endif
345
346 if (status == 0)
347 status = activitypub_post_handler(req, q_path,
348 payload, p_size, &body, &b_size, &ctype);
349
350 if (status == 0)
351 status = html_post_handler(req, q_path,
352 payload, p_size, &body, &b_size, &ctype);
353 }
354 else
355 if (strcmp(method, "PUT") == 0) {
356
357#ifndef NO_MASTODON_API
358 if (status == 0)
359 status = mastoapi_put_handler(req, q_path,
360 payload, p_size, &body, &b_size, &ctype);
361#endif
362
363 }
364 else
365 if (strcmp(method, "OPTIONS") == 0) {
366 status = 200;
367 }
368 else
369 if (strcmp(method, "DELETE") == 0) {
370#ifndef NO_MASTODON_API
371 if (status == 0)
372 status = mastoapi_delete_handler(req, q_path,
373 payload, p_size, &body, &b_size, &ctype);
374#endif
375 }
376
377 /* unattended? it's an error */
378 if (status == 0) {
379 srv_archive_error("unattended_method", "unattended method", req, payload);
380 srv_debug(1, xs_fmt("httpd_connection unattended %s %s", method, q_path));
381 status = 404;
382 }
383
384 if (status == 403)
385 body = xs_str_new("<h1>403 Forbidden</h1>");
386
387 if (status == 404)
388 body = xs_str_new("<h1>404 Not Found</h1>");
389
390 if (status == 400 && body != NULL)
391 body = xs_str_new("<h1>400 Bad Request</h1>");
392
393 if (status == 303)
394 headers = xs_dict_append(headers, "location", body);
395
396 if (status == 401) {
397 xs *www_auth = xs_fmt("Basic realm=\"@%s@%s snac login\"",
398 body, xs_dict_get(srv_config, "host"));
399
400 headers = xs_dict_append(headers, "WWW-Authenticate", www_auth);
401 headers = xs_dict_append(headers, "Cache-Control", "no-cache, must-revalidate, max-age=0");
402 }
403
404 if (ctype == NULL)
405 ctype = "text/html; charset=utf-8";
406
407 headers = xs_dict_append(headers, "content-type", ctype);
408 headers = xs_dict_append(headers, "x-creator", USER_AGENT);
409
410 if (!xs_is_null(etag))
411 headers = xs_dict_append(headers, "etag", etag);
412
413 /* if there are any additional headers, add them */
414 xs_dict *more_headers = xs_dict_get(srv_config, "http_headers");
415 if (xs_type(more_headers) == XSTYPE_DICT) {
416 char *k, *v;
417 int c = 0;
418 while (xs_dict_next(more_headers, &k, &v, &c))
419 headers = xs_dict_set(headers, k, v);
420 }
421
422 if (b_size == 0 && body != NULL)
423 b_size = strlen(body);
424
425 /* if it was a HEAD, no body will be sent */
426 if (strcmp(method, "HEAD") == 0)
427 body = xs_free(body);
428
429 headers = xs_dict_append(headers, "access-control-allow-origin", "*");
430 headers = xs_dict_append(headers, "access-control-allow-headers", "*");
431
432 if (p_state->use_fcgi)
433 xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
434 else
435 xs_httpd_response(f, status, headers, body, b_size);
436
437 fclose(f);
438
439 srv_archive("RECV", NULL, req, payload, p_size, status, headers, body, b_size);
440
441 /* JSON validation check */
442 if (!xs_is_null(body) && strcmp(ctype, "application/json") == 0) {
443 xs *j = xs_json_loads(body);
444
445 if (j == NULL) {
446 srv_log(xs_fmt("bad JSON"));
447 srv_archive_error("bad_json", "bad JSON", req, body);
448 }
449 }
450
451 xs_free(body);
452}
453
454
455void job_post(const xs_val *job, int urgent)
456/* posts a job for the threads to process it */
457{
458 if (job != NULL) {
459 /* lock the mutex */
460 pthread_mutex_lock(&job_mutex);
461
462 job_fifo_item *i = xs_realloc(NULL, sizeof(job_fifo_item));
463 *i = (job_fifo_item){ NULL, xs_dup(job) };
464
465 if (job_fifo_first == NULL)
466 job_fifo_first = job_fifo_last = i;
467 else
468 if (urgent) {
469 /* prepend */
470 i->next = job_fifo_first;
471 job_fifo_first = i;
472 }
473 else {
474 /* append */
475 job_fifo_last->next = i;
476 job_fifo_last = i;
477 }
478
479 p_state->job_fifo_size++;
480
481 if (p_state->job_fifo_size > p_state->peak_job_fifo_size)
482 p_state->peak_job_fifo_size = p_state->job_fifo_size;
483
484 /* unlock the mutex */
485 pthread_mutex_unlock(&job_mutex);
486
487 /* ask for someone to attend it */
488 sem_post(job_sem);
489 }
490}
491
492
493void job_wait(xs_val **job)
494/* waits for an available job */
495{
496 *job = NULL;
497
498 if (sem_wait(job_sem) == 0) {
499 /* lock the mutex */
500 pthread_mutex_lock(&job_mutex);
501
502 /* dequeue */
503 job_fifo_item *i = job_fifo_first;
504
505 if (i != NULL) {
506 job_fifo_first = i->next;
507
508 if (job_fifo_first == NULL)
509 job_fifo_last = NULL;
510
511 *job = i->job;
512 xs_free(i);
513
514 p_state->job_fifo_size--;
515 }
516
517 /* unlock the mutex */
518 pthread_mutex_unlock(&job_mutex);
519 }
520}
521
522
523static void *job_thread(void *arg)
524/* job thread */
525{
526 int pid = (int)(uintptr_t)arg;
527
528 srv_debug(1, xs_fmt("job thread %d started", pid));
529
530 for (;;) {
531 xs *job = NULL;
532
533 p_state->th_state[pid] = THST_WAIT;
534
535 job_wait(&job);
536
537 if (job == NULL) /* corrupted message? */
538 continue;
539
540 if (xs_type(job) == XSTYPE_FALSE) /* special message: exit */
541 break;
542 else
543 if (xs_type(job) == XSTYPE_DATA) {
544 /* it's a socket */
545 FILE *f = NULL;
546
547 p_state->th_state[pid] = THST_IN;
548
549 xs_data_get(&f, job);
550
551 if (f != NULL)
552 httpd_connection(f);
553 }
554 else {
555 /* it's a q_item */
556 p_state->th_state[pid] = THST_QUEUE;
557
558 process_queue_item(job);
559 }
560 }
561
562 p_state->th_state[pid] = THST_STOP;
563
564 srv_debug(1, xs_fmt("job thread %d stopped", pid));
565
566 return NULL;
567}
568
569/* background thread sleep control */
570static pthread_mutex_t sleep_mutex;
571static pthread_cond_t sleep_cond;
572
573static void *background_thread(void *arg)
574/* background thread (queue management and other things) */
575{
576 time_t purge_time;
577
578 (void)arg;
579
580 /* first purge time */
581 purge_time = time(NULL) + 10 * 60;
582
583 srv_log(xs_fmt("background thread started"));
584
585 while (p_state->srv_running) {
586 time_t t;
587 int cnt = 0;
588
589 p_state->th_state[0] = THST_QUEUE;
590
591 {
592 xs *list = user_list();
593 char *p, *uid;
594
595 /* process queues for all users */
596 p = list;
597 while (xs_list_iter(&p, &uid)) {
598 snac snac;
599
600 if (user_open(&snac, uid)) {
601 cnt += process_user_queue(&snac);
602 user_free(&snac);
603 }
604 }
605 }
606
607 /* global queue */
608 cnt += process_queue();
609
610 /* time to purge? */
611 if ((t = time(NULL)) > purge_time) {
612 /* next purge time is tomorrow */
613 purge_time = t + 24 * 60 * 60;
614
615 xs *q_item = xs_dict_new();
616 q_item = xs_dict_append(q_item, "type", "purge");
617 job_post(q_item, 0);
618 }
619
620 if (cnt == 0) {
621 /* sleep 3 seconds */
622
623 p_state->th_state[0] = THST_WAIT;
624
625#ifdef USE_POLL_FOR_SLEEP
626 poll(NULL, 0, 3 * 1000);
627#else
628 struct timespec ts;
629
630 clock_gettime(CLOCK_REALTIME, &ts);
631 ts.tv_sec += 3;
632
633 pthread_mutex_lock(&sleep_mutex);
634 while (pthread_cond_timedwait(&sleep_cond, &sleep_mutex, &ts) == 0);
635 pthread_mutex_unlock(&sleep_mutex);
636#endif
637 }
638 }
639
640 p_state->th_state[0] = THST_STOP;
641
642 srv_log(xs_fmt("background thread stopped"));
643
644 return NULL;
645}
646
647
648void term_handler(int s)
649{
650 (void)s;
651
652 longjmp(on_break, 1);
653}
654
655
656srv_state *srv_state_op(xs_str **fname, int op)
657/* opens or deletes the shared memory object */
658{
659 int fd;
660 srv_state *ss = NULL;
661
662 if (*fname == NULL)
663 *fname = xs_fmt("/%s_snac_state", xs_dict_get(srv_config, "host"));
664
665 switch (op) {
666 case 0: /* open for writing */
667 if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) {
668 ftruncate(fd, sizeof(*ss));
669
670 if ((ss = mmap(0, sizeof(*ss), PROT_READ | PROT_WRITE,
671 MAP_SHARED, fd, 0)) == MAP_FAILED)
672 ss = NULL;
673
674 close(fd);
675 }
676
677 if (ss == NULL) {
678 /* shared memory error: just create a plain structure */
679 srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno)));
680 ss = malloc(sizeof(*ss));
681 }
682
683 /* init structure */
684 *ss = (srv_state){0};
685 ss->s_size = sizeof(*ss);
686
687 break;
688
689 case 1: /* open for reading */
690 if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) {
691 if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED)
692 ss = NULL;
693
694 close(fd);
695 }
696
697 if (ss == NULL) {
698 /* shared memory error */
699 srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno)));
700 }
701 else
702 if (ss->s_size != sizeof(*ss)) {
703 srv_log(xs_fmt("error: struct size mismatch (%d != %d)",
704 ss->s_size, sizeof(*ss)));
705
706 munmap(ss, sizeof(*ss));
707
708 ss = NULL;
709 }
710
711 break;
712
713 case 2: /* unlink */
714 if (*fname)
715 shm_unlink(*fname);
716
717 break;
718 }
719
720 return ss;
721}
722
723
724void httpd(void)
725/* starts the server */
726{
727 const char *address;
728 const char *port;
729 int rs;
730 pthread_t threads[MAX_THREADS] = {0};
731 int n;
732 xs *sem_name = NULL;
733 xs *shm_name = NULL;
734 sem_t anon_job_sem;
735
736 address = xs_dict_get(srv_config, "address");
737 port = xs_number_str(xs_dict_get(srv_config, "port"));
738
739 if ((rs = xs_socket_server(address, port)) == -1) {
740 srv_log(xs_fmt("cannot bind socket to %s:%s", address, port));
741 return;
742 }
743
744 /* setup the server stat structure */
745 p_state = srv_state_op(&shm_name, 0);
746
747 p_state->srv_start_time = time(NULL);
748
749 p_state->use_fcgi = xs_type(xs_dict_get(srv_config, "fastcgi")) == XSTYPE_TRUE;
750
751 p_state->srv_running = 1;
752
753 signal(SIGPIPE, SIG_IGN);
754 signal(SIGTERM, term_handler);
755 signal(SIGINT, term_handler);
756
757 srv_log(xs_fmt("httpd%s start %s:%s %s", p_state->use_fcgi ? " (FastCGI)" : "",
758 address, port, USER_AGENT));
759
760 /* show the number of usable file descriptors */
761 struct rlimit r;
762 getrlimit(RLIMIT_NOFILE, &r);
763 srv_debug(0, xs_fmt("available (rlimit) fds: %d (cur) / %d (max)",
764 (int) r.rlim_cur, (int) r.rlim_max));
765
766 /* initialize the job control engine */
767 pthread_mutex_init(&job_mutex, NULL);
768 sem_name = xs_fmt("/job_%d", getpid());
769 job_sem = sem_open(sem_name, O_CREAT, 0644, 0);
770
771 if (job_sem == NULL) {
772 /* error opening a named semaphore; try with an anonymous one */
773 if (sem_init(&anon_job_sem, 0, 0) != -1)
774 job_sem = &anon_job_sem;
775 }
776
777 if (job_sem == NULL) {
778 srv_log(xs_fmt("fatal error: cannot create semaphore -- cannot continue"));
779 return;
780 }
781
782 /* initialize sleep control */
783 pthread_mutex_init(&sleep_mutex, NULL);
784 pthread_cond_init(&sleep_cond, NULL);
785
786 p_state->n_threads = xs_number_get(xs_dict_get(srv_config, "num_threads"));
787
788#ifdef _SC_NPROCESSORS_ONLN
789 if (p_state->n_threads == 0) {
790 /* get number of CPUs on the machine */
791 p_state->n_threads = sysconf(_SC_NPROCESSORS_ONLN);
792 }
793#endif
794
795 if (p_state->n_threads < 4)
796 p_state->n_threads = 4;
797
798 if (p_state->n_threads > MAX_THREADS)
799 p_state->n_threads = MAX_THREADS;
800
801 srv_debug(0, xs_fmt("using %d threads", p_state->n_threads));
802
803 /* thread #0 is the background thread */
804 pthread_create(&threads[0], NULL, background_thread, NULL);
805
806 /* the rest of threads are for job processing */
807 char *ptr = (char *) 0x1;
808 for (n = 1; n < p_state->n_threads; n++)
809 pthread_create(&threads[n], NULL, job_thread, ptr++);
810
811 if (setjmp(on_break) == 0) {
812 for (;;) {
813 FILE *f = xs_socket_accept(rs);
814
815 if (f != NULL) {
816 xs *job = xs_data_new(&f, sizeof(FILE *));
817 job_post(job, 1);
818 }
819 else
820 break;
821 }
822 }
823
824 p_state->srv_running = 0;
825
826 /* send as many exit jobs as working threads */
827 for (n = 1; n < p_state->n_threads; n++)
828 job_post(xs_stock(XSTYPE_FALSE), 0);
829
830 /* wait for all the threads to exit */
831 for (n = 0; n < p_state->n_threads; n++)
832 pthread_join(threads[n], NULL);
833
834 sem_close(job_sem);
835 sem_unlink(sem_name);
836
837 srv_state_op(&shm_name, 2);
838
839 xs *uptime = xs_str_time_diff(time(NULL) - p_state->srv_start_time);
840
841 srv_log(xs_fmt("httpd%s stop %s:%s (run time: %s)",
842 p_state->use_fcgi ? " (FastCGI)" : "",
843 address, port, uptime));
844}