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