chiark / gitweb /
journal: when browsing the journal via browse.html allow clicking on entries to show...
[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_EXPORT] = "application/vnd.fdo.journal"
59 };
60
61 static 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
75 static 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
96 static 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
106 static 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
124 static 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
157 static 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
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)
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;
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
206                         return MHD_CONTENT_READER_END_OF_STREAM;
207                 }
208
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
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
274 static 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
297 static 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
364 static 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
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
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
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
428 static 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
441 static 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
466         if (request_parse_arguments(m, connection) < 0)
467                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
468
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
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
498 static 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);
507         assert(target);
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
527 static 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
562 static 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
643 static 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
675 int 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
684         log_set_target(LOG_TARGET_AUTO);
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
723 finish:
724         if (daemon)
725                 MHD_stop_daemon(daemon);
726
727         return r;
728 }