chiark / gitweb /
0957dcbe5a6f423d4d447bcf43b9ca5944033e1b
[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 } RequestMeta;
49
50 static 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
56 static 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
70 static 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
91 static 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
101 static 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
119 static 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
152 static 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
250 static 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
273 static 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
340 static 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
393 static 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);
402         assert(target);
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
422 static 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
457 static 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
538 static 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
570 int 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
618 finish:
619         if (daemon)
620                 MHD_stop_daemon(daemon);
621
622         return r;
623 }