chiark / gitweb /
docs: install README files into /var/log and 7etc/rc.d/init.d
[elogind.git] / src / journal / journal-gatewayd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2012 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <fcntl.h>
26
27 #include <microhttpd.h>
28
29 #include "log.h"
30 #include "util.h"
31 #include "sd-journal.h"
32 #include "sd-daemon.h"
33 #include "logs-show.h"
34 #include "virt.h"
35
36 typedef struct RequestMeta {
37         sd_journal *journal;
38
39         OutputMode mode;
40
41         char *cursor;
42         int64_t n_skip;
43         uint64_t n_entries;
44         bool n_entries_set;
45
46         FILE *tmp;
47         uint64_t delta, size;
48
49         int argument_parse_error;
50
51         bool follow;
52         bool discrete;
53 } RequestMeta;
54
55 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
56         [OUTPUT_SHORT] = "text/plain",
57         [OUTPUT_JSON] = "application/json",
58         [OUTPUT_JSON_SSE] = "text/event-stream",
59         [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
60 };
61
62 static RequestMeta *request_meta(void **connection_cls) {
63         RequestMeta *m;
64
65         if (*connection_cls)
66                 return *connection_cls;
67
68         m = new0(RequestMeta, 1);
69         if (!m)
70                 return NULL;
71
72         *connection_cls = m;
73         return m;
74 }
75
76 static void request_meta_free(
77                 void *cls,
78                 struct MHD_Connection *connection,
79                 void **connection_cls,
80                 enum MHD_RequestTerminationCode toe) {
81
82         RequestMeta *m = *connection_cls;
83
84         if (!m)
85                 return;
86
87         if (m->journal)
88                 sd_journal_close(m->journal);
89
90         if (m->tmp)
91                 fclose(m->tmp);
92
93         free(m->cursor);
94         free(m);
95 }
96
97 static int open_journal(RequestMeta *m) {
98         assert(m);
99
100         if (m->journal)
101                 return 0;
102
103         return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM_ONLY);
104 }
105
106
107 static int respond_oom(struct MHD_Connection *connection) {
108         struct MHD_Response *response;
109         const char m[] = "Out of memory.\n";
110         int ret;
111
112         assert(connection);
113
114         response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
115         if (!response)
116                 return MHD_NO;
117
118         MHD_add_response_header(response, "Content-Type", "text/plain");
119         ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
120         MHD_destroy_response(response);
121
122         return ret;
123 }
124
125 static int respond_error(
126                 struct MHD_Connection *connection,
127                 unsigned code,
128                 const char *format, ...) {
129
130         struct MHD_Response *response;
131         char *m;
132         int r;
133         va_list ap;
134
135         assert(connection);
136         assert(format);
137
138         va_start(ap, format);
139         r = vasprintf(&m, format, ap);
140         va_end(ap);
141
142         if (r < 0)
143                 return respond_oom(connection);
144
145         response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
146         if (!response) {
147                 free(m);
148                 return respond_oom(connection);
149         }
150
151         MHD_add_response_header(response, "Content-Type", "text/plain");
152         r = MHD_queue_response(connection, code, response);
153         MHD_destroy_response(response);
154
155         return r;
156 }
157
158 static ssize_t request_reader_entries(
159                 void *cls,
160                 uint64_t pos,
161                 char *buf,
162                 size_t max) {
163
164         RequestMeta *m = cls;
165         int r;
166         size_t n, k;
167
168         assert(m);
169         assert(buf);
170         assert(max > 0);
171         assert(pos >= m->delta);
172
173         pos -= m->delta;
174
175         while (pos >= m->size) {
176                 off_t sz;
177
178                 /* End of this entry, so let's serialize the next
179                  * one */
180
181                 if (m->n_entries_set &&
182                     m->n_entries <= 0)
183                         return MHD_CONTENT_READER_END_OF_STREAM;
184
185                 if (m->n_skip < 0)
186                         r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
187                 else if (m->n_skip > 0)
188                         r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
189                 else
190                         r = sd_journal_next(m->journal);
191
192                 if (r < 0) {
193                         log_error("Failed to advance journal pointer: %s", strerror(-r));
194                         return MHD_CONTENT_READER_END_WITH_ERROR;
195                 } else if (r == 0) {
196
197                         if (m->follow) {
198                                 r = sd_journal_wait(m->journal, (uint64_t) -1);
199                                 if (r < 0) {
200                                         log_error("Couldn't wait for journal event: %s", strerror(-r));
201                                         return MHD_CONTENT_READER_END_WITH_ERROR;
202                                 }
203
204                                 continue;
205                         }
206
207                         return MHD_CONTENT_READER_END_OF_STREAM;
208                 }
209
210                 if (m->discrete) {
211                         assert(m->cursor);
212
213                         r = sd_journal_test_cursor(m->journal, m->cursor);
214                         if (r < 0) {
215                                 log_error("Failed to test cursor: %s", strerror(-r));
216                                 return MHD_CONTENT_READER_END_WITH_ERROR;
217                         }
218
219                         if (r == 0)
220                                 return MHD_CONTENT_READER_END_OF_STREAM;
221                 }
222
223                 pos -= m->size;
224                 m->delta += m->size;
225
226                 if (m->n_entries_set)
227                         m->n_entries -= 1;
228
229                 m->n_skip = 0;
230
231                 if (m->tmp)
232                         rewind(m->tmp);
233                 else {
234                         m->tmp = tmpfile();
235                         if (!m->tmp) {
236                                 log_error("Failed to create temporary file: %m");
237                                 return MHD_CONTENT_READER_END_WITH_ERROR;;
238                         }
239                 }
240
241                 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH);
242                 if (r < 0) {
243                         log_error("Failed to serialize item: %s", strerror(-r));
244                         return MHD_CONTENT_READER_END_WITH_ERROR;
245                 }
246
247                 sz = ftello(m->tmp);
248                 if (sz == (off_t) -1) {
249                         log_error("Failed to retrieve file position: %m");
250                         return MHD_CONTENT_READER_END_WITH_ERROR;
251                 }
252
253                 m->size = (uint64_t) sz;
254         }
255
256         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
257                 log_error("Failed to seek to position: %m");
258                 return MHD_CONTENT_READER_END_WITH_ERROR;
259         }
260
261         n = m->size - pos;
262         if (n > max)
263                 n = max;
264
265         errno = 0;
266         k = fread(buf, 1, n, m->tmp);
267         if (k != n) {
268                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
269                 return MHD_CONTENT_READER_END_WITH_ERROR;
270         }
271
272         return (ssize_t) k;
273 }
274
275 static int request_parse_accept(
276                 RequestMeta *m,
277                 struct MHD_Connection *connection) {
278
279         const char *header;
280
281         assert(m);
282         assert(connection);
283
284         header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
285         if (!header)
286                 return 0;
287
288         if (streq(header, mime_types[OUTPUT_JSON]))
289                 m->mode = OUTPUT_JSON;
290         else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
291                 m->mode = OUTPUT_JSON_SSE;
292         else if (streq(header, mime_types[OUTPUT_EXPORT]))
293                 m->mode = OUTPUT_EXPORT;
294         else
295                 m->mode = OUTPUT_SHORT;
296
297         return 0;
298 }
299
300 static int request_parse_range(
301                 RequestMeta *m,
302                 struct MHD_Connection *connection) {
303
304         const char *range, *colon, *colon2;
305         int r;
306
307         assert(m);
308         assert(connection);
309
310         range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
311         if (!range)
312                 return 0;
313
314         if (!startswith(range, "entries="))
315                 return 0;
316
317         range += 8;
318         range += strspn(range, WHITESPACE);
319
320         colon = strchr(range, ':');
321         if (!colon)
322                 m->cursor = strdup(range);
323         else {
324                 const char *p;
325
326                 colon2 = strchr(colon + 1, ':');
327                 if (colon2) {
328                         char *t;
329
330                         t = strndup(colon + 1, colon2 - colon - 1);
331                         if (!t)
332                                 return -ENOMEM;
333
334                         r = safe_atoi64(t, &m->n_skip);
335                         free(t);
336                         if (r < 0)
337                                 return r;
338                 }
339
340                 p = (colon2 ? colon2 : colon) + 1;
341                 if (*p) {
342                         r = safe_atou64(p, &m->n_entries);
343                         if (r < 0)
344                                 return r;
345
346                         if (m->n_entries <= 0)
347                                 return -EINVAL;
348
349                         m->n_entries_set = true;
350                 }
351
352                 m->cursor = strndup(range, colon - range);
353         }
354
355         if (!m->cursor)
356                 return -ENOMEM;
357
358         m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
359         if (isempty(m->cursor)) {
360                 free(m->cursor);
361                 m->cursor = NULL;
362         }
363
364         return 0;
365 }
366
367 static int request_parse_arguments_iterator(
368                 void *cls,
369                 enum MHD_ValueKind kind,
370                 const char *key,
371                 const char *value) {
372
373         RequestMeta *m = cls;
374         _cleanup_free_ char *p = NULL;
375         int r;
376
377         assert(m);
378
379         if (isempty(key)) {
380                 m->argument_parse_error = -EINVAL;
381                 return MHD_NO;
382         }
383
384         if (streq(key, "follow")) {
385                 if (isempty(value)) {
386                         m->follow = true;
387                         return MHD_YES;
388                 }
389
390                 r = parse_boolean(value);
391                 if (r < 0) {
392                         m->argument_parse_error = r;
393                         return MHD_NO;
394                 }
395
396                 m->follow = r;
397                 return MHD_YES;
398         }
399
400         if (streq(key, "discrete")) {
401                 if (isempty(value)) {
402                         m->discrete = true;
403                         return MHD_YES;
404                 }
405
406                 r = parse_boolean(value);
407                 if (r < 0) {
408                         m->argument_parse_error = r;
409                         return MHD_NO;
410                 }
411
412                 m->discrete = r;
413                 return MHD_YES;
414         }
415
416         p = strjoin(key, "=", strempty(value), NULL);
417         if (!p) {
418                 m->argument_parse_error = log_oom();
419                 return MHD_NO;
420         }
421
422         r = sd_journal_add_match(m->journal, p, 0);
423         if (r < 0) {
424                 m->argument_parse_error = r;
425                 return MHD_NO;
426         }
427
428         return MHD_YES;
429 }
430
431 static int request_parse_arguments(
432                 RequestMeta *m,
433                 struct MHD_Connection *connection) {
434
435         assert(m);
436         assert(connection);
437
438         m->argument_parse_error = 0;
439         MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
440
441         return m->argument_parse_error;
442 }
443
444 static int request_handler_entries(
445                 struct MHD_Connection *connection,
446                 void **connection_cls) {
447
448         struct MHD_Response *response;
449         RequestMeta *m;
450         int r;
451
452         assert(connection);
453         assert(connection_cls);
454
455         m = request_meta(connection_cls);
456         if (!m)
457                 return respond_oom(connection);
458
459         r = open_journal(m);
460         if (r < 0)
461                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
462
463         if (request_parse_accept(m, connection) < 0)
464                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
465
466         if (request_parse_range(m, connection) < 0)
467                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
468
469         if (request_parse_arguments(m, connection) < 0)
470                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
471
472         if (m->discrete) {
473                 if (!m->cursor)
474                         return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
475
476                 m->n_entries = 1;
477                 m->n_entries_set = true;
478         }
479
480         if (m->cursor)
481                 r = sd_journal_seek_cursor(m->journal, m->cursor);
482         else if (m->n_skip >= 0)
483                 r = sd_journal_seek_head(m->journal);
484         else if (m->n_skip < 0)
485                 r = sd_journal_seek_tail(m->journal);
486         if (r < 0)
487                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
488
489         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
490         if (!response)
491                 return respond_oom(connection);
492
493         MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
494
495         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
496         MHD_destroy_response(response);
497
498         return r;
499 }
500
501 static int request_handler_redirect(
502                 struct MHD_Connection *connection,
503                 const char *target) {
504
505         char *page;
506         struct MHD_Response *response;
507         int ret;
508
509         assert(connection);
510         assert(target);
511
512         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
513                 return respond_oom(connection);
514
515         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
516         if (!response) {
517                 free(page);
518                 return respond_oom(connection);
519         }
520
521         MHD_add_response_header(response, "Content-Type", "text/html");
522         MHD_add_response_header(response, "Location", target);
523
524         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
525         MHD_destroy_response(response);
526
527         return ret;
528 }
529
530 static int request_handler_file(
531                 struct MHD_Connection *connection,
532                 const char *path,
533                 const char *mime_type) {
534
535         struct MHD_Response *response;
536         int ret;
537         _cleanup_close_ int fd = -1;
538         struct stat st;
539
540         assert(connection);
541         assert(path);
542         assert(mime_type);
543
544         fd = open(path, O_RDONLY|O_CLOEXEC);
545         if (fd < 0)
546                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
547
548         if (fstat(fd, &st) < 0)
549                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
550
551         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
552         if (!response)
553                 return respond_oom(connection);
554
555         fd = -1;
556
557         MHD_add_response_header(response, "Content-Type", mime_type);
558
559         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
560         MHD_destroy_response(response);
561
562         return ret;
563 }
564
565 static int request_handler_machine(
566                 struct MHD_Connection *connection,
567                 void **connection_cls) {
568
569         struct MHD_Response *response;
570         RequestMeta *m;
571         int r;
572         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
573         uint64_t cutoff_from, cutoff_to, usage;
574         char *json;
575         sd_id128_t mid, bid;
576         const char *v = "bare";
577
578         assert(connection);
579
580         m = request_meta(connection_cls);
581         if (!m)
582                 return respond_oom(connection);
583
584         r = open_journal(m);
585         if (r < 0)
586                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
587
588         r = sd_id128_get_machine(&mid);
589         if (r < 0)
590                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
591
592         r = sd_id128_get_boot(&bid);
593         if (r < 0)
594                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
595
596         hostname = gethostname_malloc();
597         if (!hostname)
598                 return respond_oom(connection);
599
600         r = sd_journal_get_usage(m->journal, &usage);
601         if (r < 0)
602                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
603
604         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
605         if (r < 0)
606                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
607
608         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
609
610         detect_virtualization(&v);
611
612         r = asprintf(&json,
613                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
614                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
615                      "\"hostname\" : \"%s\","
616                      "\"os_pretty_name\" : \"%s\","
617                      "\"virtualization\" : \"%s\","
618                      "\"usage\" : \"%llu\","
619                      "\"cutoff_from_realtime\" : \"%llu\","
620                      "\"cutoff_to_realtime\" : \"%llu\" }\n",
621                      SD_ID128_FORMAT_VAL(mid),
622                      SD_ID128_FORMAT_VAL(bid),
623                      hostname_cleanup(hostname),
624                      os_name ? os_name : "Linux",
625                      v,
626                      (unsigned long long) usage,
627                      (unsigned long long) cutoff_from,
628                      (unsigned long long) cutoff_to);
629
630         if (r < 0)
631                 return respond_oom(connection);
632
633         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
634         if (!response) {
635                 free(json);
636                 return respond_oom(connection);
637         }
638
639         MHD_add_response_header(response, "Content-Type", "application/json");
640         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
641         MHD_destroy_response(response);
642
643         return r;
644 }
645
646 static int request_handler(
647                 void *cls,
648                 struct MHD_Connection *connection,
649                 const char *url,
650                 const char *method,
651                 const char *version,
652                 const char *upload_data,
653                 size_t *upload_data_size,
654                 void **connection_cls) {
655
656         assert(connection);
657         assert(url);
658         assert(method);
659
660         if (!streq(method, "GET"))
661                 return MHD_NO;
662
663         if (streq(url, "/"))
664                 return request_handler_redirect(connection, "/browse");
665
666         if (streq(url, "/entries"))
667                 return request_handler_entries(connection, connection_cls);
668
669         if (streq(url, "/browse"))
670                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
671
672         if (streq(url, "/machine"))
673                 return request_handler_machine(connection, connection_cls);
674
675         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
676 }
677
678 int main(int argc, char *argv[]) {
679         struct MHD_Daemon *d = NULL;
680         int r = EXIT_FAILURE, n;
681
682         if (argc > 1) {
683                 log_error("This program does not take arguments.");
684                 goto finish;
685         }
686
687         log_set_target(LOG_TARGET_AUTO);
688         log_parse_environment();
689         log_open();
690
691         n = sd_listen_fds(1);
692         if (n < 0) {
693                 log_error("Failed to determine passed sockets: %s", strerror(-n));
694                 goto finish;
695         } else if (n > 1) {
696                 log_error("Can't listen on more than one socket.");
697                 goto finish;
698         } else if (n > 0) {
699                 d = MHD_start_daemon(
700                                 MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG,
701                                 19531,
702                                 NULL, NULL,
703                                 request_handler, NULL,
704                                 MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START,
705                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
706                                 MHD_OPTION_END);
707         } else {
708                 d = MHD_start_daemon(
709                                 MHD_USE_DEBUG|MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL,
710                                 19531,
711                                 NULL, NULL,
712                                 request_handler, NULL,
713                                 MHD_OPTION_NOTIFY_COMPLETED, request_meta_free, NULL,
714                                 MHD_OPTION_END);
715         }
716
717         if (!d) {
718                 log_error("Failed to start daemon!");
719                 goto finish;
720         }
721
722         pause();
723
724         r = EXIT_SUCCESS;
725
726 finish:
727         if (d)
728                 MHD_stop_daemon(d);
729
730         return r;
731 }