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