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