chiark / gitweb /
journal: implement follow mode for HTTP GET in gatewayd
[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 } RequestMeta;
53
54 static 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
60 static 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
74 static 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
95 static 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
105 static 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
123 static 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
156 static 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;
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
211                         return MHD_CONTENT_READER_END_OF_STREAM;
212                 }
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
266 static 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
289 static 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
356 static 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
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
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
404 static 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
417 static 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
442         if (request_parse_arguments(m, connection) < 0)
443                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
444
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
473 static 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);
482         assert(target);
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
502 static 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
537 static 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
618 static 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
650 int 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
698 finish:
699         if (daemon)
700                 MHD_stop_daemon(daemon);
701
702         return r;
703 }