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}