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