chiark / gitweb /
9599e891daf34db930f7b8b7772bc8bbf7baf14c
[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 } RequestMeta;
51
52 static 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
58 static 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
72 static 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
93 static 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
103 static 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
121 static 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
154 static 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
252 static 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
275 static 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
342 static 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
374 static 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
387 static 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
412         if (request_parse_arguments(m, connection) < 0)
413                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
414
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
443 static 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);
452         assert(target);
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
472 static 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
507 static 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
588 static 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
620 int 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
668 finish:
669         if (daemon)
670                 MHD_stop_daemon(daemon);
671
672         return r;
673 }