2.54
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 const 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 const char *host = xs_dict_get(srv_config, "host");
129 xs *list = user_list();
130 xs_list *p = list;
131 const 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 const xs_dict *q_vars = xs_dict_get(req, "q_vars");
175 const 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 const 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 const 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 = HTTP_STATUS_OK;
221 }
222 else
223 if (strcmp(q_path, "/susie.png") == 0 || strcmp(q_path, "/favicon.ico") == 0 ) {
224 status = HTTP_STATUS_OK;
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 = HTTP_STATUS_OK;
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 = HTTP_STATUS_OK;
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 = HTTP_STATUS_OK;
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 = HTTP_STATUS_OK;
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 const 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 const 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, "PATCH") == 0) {
366
367#ifndef NO_MASTODON_API
368 if (status == 0)
369 status = mastoapi_patch_handler(req, q_path,
370 payload, p_size, &body, &b_size, &ctype);
371#endif
372
373 }
374 else
375 if (strcmp(method, "OPTIONS") == 0) {
376 const char *methods = "OPTIONS, GET, HEAD, POST, PUT, DELETE";
377 headers = xs_dict_append(headers, "allow", methods);
378 headers = xs_dict_append(headers, "access-control-allow-methods", methods);
379 status = HTTP_STATUS_OK;
380 }
381 else
382 if (strcmp(method, "DELETE") == 0) {
383#ifndef NO_MASTODON_API
384 if (status == 0)
385 status = mastoapi_delete_handler(req, q_path,
386 payload, p_size, &body, &b_size, &ctype);
387#endif
388 }
389
390 /* unattended? it's an error */
391 if (status == 0) {
392 srv_archive_error("unattended_method", "unattended method", req, payload);
393 srv_debug(1, xs_fmt("httpd_connection unattended %s %s", method, q_path));
394 status = HTTP_STATUS_NOT_FOUND;
395 }
396
397 if (status == HTTP_STATUS_FORBIDDEN)
398 body = xs_str_new("<h1>403 Forbidden</h1>");
399
400 if (status == HTTP_STATUS_NOT_FOUND)
401 body = xs_str_new("<h1>404 Not Found</h1>");
402
403 if (status == HTTP_STATUS_BAD_REQUEST && body != NULL)
404 body = xs_str_new("<h1>400 Bad Request</h1>");
405
406 if (status == HTTP_STATUS_SEE_OTHER)
407 headers = xs_dict_append(headers, "location", body);
408
409 if (status == HTTP_STATUS_UNAUTHORIZED) {
410 xs *www_auth = xs_fmt("Basic realm=\"@%s@%s snac login\"",
411 body, xs_dict_get(srv_config, "host"));
412
413 headers = xs_dict_append(headers, "WWW-Authenticate", www_auth);
414 headers = xs_dict_append(headers, "Cache-Control", "no-cache, must-revalidate, max-age=0");
415 }
416
417 if (ctype == NULL)
418 ctype = "text/html; charset=utf-8";
419
420 headers = xs_dict_append(headers, "content-type", ctype);
421 headers = xs_dict_append(headers, "x-creator", USER_AGENT);
422
423 if (!xs_is_null(etag))
424 headers = xs_dict_append(headers, "etag", etag);
425
426 /* if there are any additional headers, add them */
427 const xs_dict *more_headers = xs_dict_get(srv_config, "http_headers");
428 if (xs_type(more_headers) == XSTYPE_DICT) {
429 const char *k, *v;
430 int c = 0;
431 while (xs_dict_next(more_headers, &k, &v, &c))
432 headers = xs_dict_set(headers, k, v);
433 }
434
435 if (b_size == 0 && body != NULL)
436 b_size = strlen(body);
437
438 /* if it was a HEAD, no body will be sent */
439 if (strcmp(method, "HEAD") == 0)
440 body = xs_free(body);
441
442 headers = xs_dict_append(headers, "access-control-allow-origin", "*");
443 headers = xs_dict_append(headers, "access-control-allow-headers", "*");
444
445 if (p_state->use_fcgi)
446 xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
447 else
448 xs_httpd_response(f, status, http_status_text(status), headers, body, b_size);
449
450 fclose(f);
451
452 srv_archive("RECV", NULL, req, payload, p_size, status, headers, body, b_size);
453
454 /* JSON validation check */
455 if (!xs_is_null(body) && strcmp(ctype, "application/json") == 0) {
456 xs *j = xs_json_loads(body);
457
458 if (j == NULL) {
459 srv_log(xs_fmt("bad JSON"));
460 srv_archive_error("bad_json", "bad JSON", req, body);
461 }
462 }
463
464 xs_free(body);
465}
466
467
468void job_post(const xs_val *job, int urgent)
469/* posts a job for the threads to process it */
470{
471 if (job != NULL) {
472 /* lock the mutex */
473 pthread_mutex_lock(&job_mutex);
474
475 job_fifo_item *i = xs_realloc(NULL, sizeof(job_fifo_item));
476 *i = (job_fifo_item){ NULL, xs_dup(job) };
477
478 if (job_fifo_first == NULL)
479 job_fifo_first = job_fifo_last = i;
480 else
481 if (urgent) {
482 /* prepend */
483 i->next = job_fifo_first;
484 job_fifo_first = i;
485 }
486 else {
487 /* append */
488 job_fifo_last->next = i;
489 job_fifo_last = i;
490 }
491
492 p_state->job_fifo_size++;
493
494 if (p_state->job_fifo_size > p_state->peak_job_fifo_size)
495 p_state->peak_job_fifo_size = p_state->job_fifo_size;
496
497 /* unlock the mutex */
498 pthread_mutex_unlock(&job_mutex);
499
500 /* ask for someone to attend it */
501 sem_post(job_sem);
502 }
503}
504
505
506void job_wait(xs_val **job)
507/* waits for an available job */
508{
509 *job = NULL;
510
511 if (sem_wait(job_sem) == 0) {
512 /* lock the mutex */
513 pthread_mutex_lock(&job_mutex);
514
515 /* dequeue */
516 job_fifo_item *i = job_fifo_first;
517
518 if (i != NULL) {
519 job_fifo_first = i->next;
520
521 if (job_fifo_first == NULL)
522 job_fifo_last = NULL;
523
524 *job = i->job;
525 xs_free(i);
526
527 p_state->job_fifo_size--;
528 }
529
530 /* unlock the mutex */
531 pthread_mutex_unlock(&job_mutex);
532 }
533}
534
535
536static void *job_thread(void *arg)
537/* job thread */
538{
539 int pid = (int)(uintptr_t)arg;
540
541 srv_debug(1, xs_fmt("job thread %d started", pid));
542
543 for (;;) {
544 xs *job = NULL;
545
546 p_state->th_state[pid] = THST_WAIT;
547
548 job_wait(&job);
549
550 if (job == NULL) /* corrupted message? */
551 continue;
552
553 if (xs_type(job) == XSTYPE_FALSE) /* special message: exit */
554 break;
555 else
556 if (xs_type(job) == XSTYPE_DATA) {
557 /* it's a socket */
558 FILE *f = NULL;
559
560 p_state->th_state[pid] = THST_IN;
561
562 xs_data_get(&f, job);
563
564 if (f != NULL)
565 httpd_connection(f);
566 }
567 else {
568 /* it's a q_item */
569 p_state->th_state[pid] = THST_QUEUE;
570
571 process_queue_item(job);
572 }
573 }
574
575 p_state->th_state[pid] = THST_STOP;
576
577 srv_debug(1, xs_fmt("job thread %d stopped", pid));
578
579 return NULL;
580}
581
582/* background thread sleep control */
583static pthread_mutex_t sleep_mutex;
584static pthread_cond_t sleep_cond;
585
586static void *background_thread(void *arg)
587/* background thread (queue management and other things) */
588{
589 time_t purge_time;
590
591 (void)arg;
592
593 /* first purge time */
594 purge_time = time(NULL) + 10 * 60;
595
596 srv_log(xs_fmt("background thread started"));
597
598 while (p_state->srv_running) {
599 time_t t;
600 int cnt = 0;
601
602 p_state->th_state[0] = THST_QUEUE;
603
604 {
605 xs *list = user_list();
606 char *p;
607 const char *uid;
608
609 /* process queues for all users */
610 p = list;
611 while (xs_list_iter(&p, &uid)) {
612 snac snac;
613
614 if (user_open(&snac, uid)) {
615 cnt += process_user_queue(&snac);
616 user_free(&snac);
617 }
618 }
619 }
620
621 /* global queue */
622 cnt += process_queue();
623
624 /* time to purge? */
625 if ((t = time(NULL)) > purge_time) {
626 /* next purge time is tomorrow */
627 purge_time = t + 24 * 60 * 60;
628
629 xs *q_item = xs_dict_new();
630 q_item = xs_dict_append(q_item, "type", "purge");
631 job_post(q_item, 0);
632 }
633
634 if (cnt == 0) {
635 /* sleep 3 seconds */
636
637 p_state->th_state[0] = THST_WAIT;
638
639#ifdef USE_POLL_FOR_SLEEP
640 poll(NULL, 0, 3 * 1000);
641#else
642 struct timespec ts;
643
644 clock_gettime(CLOCK_REALTIME, &ts);
645 ts.tv_sec += 3;
646
647 pthread_mutex_lock(&sleep_mutex);
648 while (pthread_cond_timedwait(&sleep_cond, &sleep_mutex, &ts) == 0);
649 pthread_mutex_unlock(&sleep_mutex);
650#endif
651 }
652 }
653
654 p_state->th_state[0] = THST_STOP;
655
656 srv_log(xs_fmt("background thread stopped"));
657
658 return NULL;
659}
660
661
662void term_handler(int s)
663{
664 (void)s;
665
666 longjmp(on_break, 1);
667}
668
669
670srv_state *srv_state_op(xs_str **fname, int op)
671/* opens or deletes the shared memory object */
672{
673 int fd;
674 srv_state *ss = NULL;
675
676 if (*fname == NULL)
677 *fname = xs_fmt("/%s_snac_state", xs_dict_get(srv_config, "host"));
678
679 switch (op) {
680 case 0: /* open for writing */
681
682#ifdef WITHOUT_SHM
683
684 errno = ENOTSUP;
685
686#else
687
688 if ((fd = shm_open(*fname, O_CREAT | O_RDWR, 0666)) != -1) {
689 ftruncate(fd, sizeof(*ss));
690
691 if ((ss = mmap(0, sizeof(*ss), PROT_READ | PROT_WRITE,
692 MAP_SHARED, fd, 0)) == MAP_FAILED)
693 ss = NULL;
694
695 close(fd);
696 }
697
698#endif
699
700 if (ss == NULL) {
701 /* shared memory error: just create a plain structure */
702 srv_log(xs_fmt("warning: shm object error (%s)", strerror(errno)));
703 ss = malloc(sizeof(*ss));
704 }
705
706 /* init structure */
707 *ss = (srv_state){0};
708 ss->s_size = sizeof(*ss);
709
710 break;
711
712 case 1: /* open for reading */
713
714#ifdef WITHOUT_SHM
715
716 errno = ENOTSUP;
717
718#else
719
720 if ((fd = shm_open(*fname, O_RDONLY, 0666)) != -1) {
721 if ((ss = mmap(0, sizeof(*ss), PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED)
722 ss = NULL;
723
724 close(fd);
725 }
726
727#endif
728
729 if (ss == NULL) {
730 /* shared memory error */
731 srv_log(xs_fmt("error: shm object error (%s) server not running?", strerror(errno)));
732 }
733 else
734 if (ss->s_size != sizeof(*ss)) {
735 srv_log(xs_fmt("error: struct size mismatch (%d != %d)",
736 ss->s_size, sizeof(*ss)));
737
738 munmap(ss, sizeof(*ss));
739
740 ss = NULL;
741 }
742
743 break;
744
745 case 2: /* unlink */
746
747#ifndef WITHOUT_SHM
748
749 if (*fname)
750 shm_unlink(*fname);
751
752#endif
753
754 break;
755 }
756
757 return ss;
758}
759
760
761void httpd(void)
762/* starts the server */
763{
764 const char *address;
765 const char *port;
766 int rs;
767 pthread_t threads[MAX_THREADS] = {0};
768 int n;
769 xs *sem_name = NULL;
770 xs *shm_name = NULL;
771 sem_t anon_job_sem;
772
773 address = xs_dict_get(srv_config, "address");
774 port = xs_number_str(xs_dict_get(srv_config, "port"));
775
776 if ((rs = xs_socket_server(address, port)) == -1) {
777 srv_log(xs_fmt("cannot bind socket to %s:%s", address, port));
778 return;
779 }
780
781 /* setup the server stat structure */
782 p_state = srv_state_op(&shm_name, 0);
783
784 p_state->srv_start_time = time(NULL);
785
786 p_state->use_fcgi = xs_type(xs_dict_get(srv_config, "fastcgi")) == XSTYPE_TRUE;
787
788 p_state->srv_running = 1;
789
790 signal(SIGPIPE, SIG_IGN);
791 signal(SIGTERM, term_handler);
792 signal(SIGINT, term_handler);
793
794 srv_log(xs_fmt("httpd%s start %s:%s %s", p_state->use_fcgi ? " (FastCGI)" : "",
795 address, port, USER_AGENT));
796
797 /* show the number of usable file descriptors */
798 struct rlimit r;
799 getrlimit(RLIMIT_NOFILE, &r);
800 srv_debug(0, xs_fmt("available (rlimit) fds: %d (cur) / %d (max)",
801 (int) r.rlim_cur, (int) r.rlim_max));
802
803 /* initialize the job control engine */
804 pthread_mutex_init(&job_mutex, NULL);
805 sem_name = xs_fmt("/job_%d", getpid());
806 job_sem = sem_open(sem_name, O_CREAT, 0644, 0);
807
808 if (job_sem == NULL) {
809 /* error opening a named semaphore; try with an anonymous one */
810 if (sem_init(&anon_job_sem, 0, 0) != -1)
811 job_sem = &anon_job_sem;
812 }
813
814 if (job_sem == NULL) {
815 srv_log(xs_fmt("fatal error: cannot create semaphore -- cannot continue"));
816 return;
817 }
818
819 /* initialize sleep control */
820 pthread_mutex_init(&sleep_mutex, NULL);
821 pthread_cond_init(&sleep_cond, NULL);
822
823 p_state->n_threads = xs_number_get(xs_dict_get(srv_config, "num_threads"));
824
825#ifdef _SC_NPROCESSORS_ONLN
826 if (p_state->n_threads == 0) {
827 /* get number of CPUs on the machine */
828 p_state->n_threads = sysconf(_SC_NPROCESSORS_ONLN);
829 }
830#endif
831
832 if (p_state->n_threads < 4)
833 p_state->n_threads = 4;
834
835 if (p_state->n_threads > MAX_THREADS)
836 p_state->n_threads = MAX_THREADS;
837
838 srv_debug(0, xs_fmt("using %d threads", p_state->n_threads));
839
840 /* thread #0 is the background thread */
841 pthread_create(&threads[0], NULL, background_thread, NULL);
842
843 /* the rest of threads are for job processing */
844 char *ptr = (char *) 0x1;
845 for (n = 1; n < p_state->n_threads; n++)
846 pthread_create(&threads[n], NULL, job_thread, ptr++);
847
848 if (setjmp(on_break) == 0) {
849 for (;;) {
850 FILE *f = xs_socket_accept(rs);
851
852 if (f != NULL) {
853 xs *job = xs_data_new(&f, sizeof(FILE *));
854 job_post(job, 1);
855 }
856 else
857 break;
858 }
859 }
860
861 p_state->srv_running = 0;
862
863 /* send as many exit jobs as working threads */
864 for (n = 1; n < p_state->n_threads; n++)
865 job_post(xs_stock(XSTYPE_FALSE), 0);
866
867 /* wait for all the threads to exit */
868 for (n = 0; n < p_state->n_threads; n++)
869 pthread_join(threads[n], NULL);
870
871 sem_close(job_sem);
872 sem_unlink(sem_name);
873
874 srv_state_op(&shm_name, 2);
875
876 xs *uptime = xs_str_time_diff(time(NULL) - p_state->srv_start_time);
877
878 srv_log(xs_fmt("httpd%s stop %s:%s (run time: %s)",
879 p_state->use_fcgi ? " (FastCGI)" : "",
880 address, port, uptime));
881}