1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2012 Lennart Poettering
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.
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.
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/>.
27 #include <microhttpd.h>
31 #include "sd-journal.h"
32 #include "sd-daemon.h"
33 #include "logs-show.h"
36 typedef struct RequestMeta
{
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"
56 static RequestMeta
*request_meta(void **connection_cls
) {
60 return *connection_cls
;
62 m
= new0(RequestMeta
, 1);
70 static void request_meta_free(
72 struct MHD_Connection
*connection
,
73 void **connection_cls
,
74 enum MHD_RequestTerminationCode toe
) {
76 RequestMeta
*m
= *connection_cls
;
82 sd_journal_close(m
->journal
);
91 static int open_journal(RequestMeta
*m
) {
97 return sd_journal_open(&m
->journal
, SD_JOURNAL_LOCAL_ONLY
|SD_JOURNAL_SYSTEM_ONLY
);
101 static int respond_oom(struct MHD_Connection
*connection
) {
102 struct MHD_Response
*response
;
103 const char m
[] = "Out of memory.\n";
108 response
= MHD_create_response_from_buffer(sizeof(m
)-1, (char*) m
, MHD_RESPMEM_PERSISTENT
);
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
);
119 static int respond_error(
120 struct MHD_Connection
*connection
,
122 const char *format
, ...) {
124 struct MHD_Response
*response
;
132 va_start(ap
, format
);
133 r
= vasprintf(&m
, format
, ap
);
137 return respond_oom(connection
);
139 response
= MHD_create_response_from_buffer(strlen(m
), m
, MHD_RESPMEM_MUST_FREE
);
142 return respond_oom(connection
);
145 MHD_add_response_header(response
, "Content-Type", "text/plain");
146 r
= MHD_queue_response(connection
, code
, response
);
147 MHD_destroy_response(response
);
152 static ssize_t
request_reader_entries(
158 RequestMeta
*m
= cls
;
165 assert(pos
>= m
->delta
);
169 while (pos
>= m
->size
) {
172 /* End of this entry, so let's serialize the next
175 if (m
->n_entries_set
&&
177 return MHD_CONTENT_READER_END_OF_STREAM
;
180 r
= sd_journal_previous_skip(m
->journal
, (uint64_t) -m
->n_skip
);
182 /* We couldn't seek this far backwards? Then
183 * let's try to look forward... */
185 r
= sd_journal_next(m
->journal
);
187 } else if (m
->n_skip
> 0)
188 r
= sd_journal_next_skip(m
->journal
, (uint64_t) m
->n_skip
+ 1);
190 r
= sd_journal_next(m
->journal
);
193 log_error("Failed to advance journal pointer: %s", strerror(-r
));
194 return MHD_CONTENT_READER_END_WITH_ERROR
;
196 return MHD_CONTENT_READER_END_OF_STREAM
;
201 if (m
->n_entries_set
)
211 log_error("Failed to create temporary file: %m");
212 return MHD_CONTENT_READER_END_WITH_ERROR
;;
216 r
= output_journal(m
->tmp
, m
->journal
, m
->mode
, 0, OUTPUT_FULL_WIDTH
);
218 log_error("Failed to serialize item: %s", strerror(-r
));
219 return MHD_CONTENT_READER_END_WITH_ERROR
;
223 if (sz
== (off_t
) -1) {
224 log_error("Failed to retrieve file position: %m");
225 return MHD_CONTENT_READER_END_WITH_ERROR
;
228 m
->size
= (uint64_t) sz
;
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
;
241 k
= fread(buf
, 1, n
, m
->tmp
);
243 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
244 return MHD_CONTENT_READER_END_WITH_ERROR
;
250 static int request_parse_accept(
252 struct MHD_Connection
*connection
) {
259 accept
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Accept");
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
;
268 m
->mode
= OUTPUT_SHORT
;
273 static int request_parse_range(
275 struct MHD_Connection
*connection
) {
277 const char *range
, *colon
, *colon2
;
283 range
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Range");
287 if (!startswith(range
, "entries="))
291 range
+= strspn(range
, WHITESPACE
);
293 colon
= strchr(range
, ':');
295 m
->cursor
= strdup(range
);
299 colon2
= strchr(colon
+ 1, ':');
303 t
= strndup(colon
+ 1, colon2
- colon
- 1);
307 r
= safe_atoi64(t
, &m
->n_skip
);
313 p
= (colon2
? colon2
: colon
) + 1;
315 r
= safe_atou64(p
, &m
->n_entries
);
319 if (m
->n_entries
<= 0)
322 m
->n_entries_set
= true;
325 m
->cursor
= strndup(range
, colon
- range
);
331 m
->cursor
[strcspn(m
->cursor
, WHITESPACE
)] = 0;
332 if (isempty(m
->cursor
)) {
340 static int request_handler_entries(
341 struct MHD_Connection
*connection
,
342 void **connection_cls
) {
344 struct MHD_Response
*response
;
349 assert(connection_cls
);
351 m
= request_meta(connection_cls
);
353 return respond_oom(connection
);
357 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
359 if (request_parse_accept(m
, connection
) < 0)
360 return respond_error(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.\n");
362 if (request_parse_range(m
, connection
) < 0)
363 return respond_error(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.\n");
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!"); */
370 /* log_info("n_entries = %llu", m->n_entries); */
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
);
379 return respond_error(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.\n");
381 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
383 return respond_oom(connection
);
385 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]);
387 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
388 MHD_destroy_response(response
);
393 static int request_handler_redirect(
394 struct MHD_Connection
*connection
,
395 const char *target
) {
398 struct MHD_Response
*response
;
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
);
407 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
410 return respond_oom(connection
);
413 MHD_add_response_header(response
, "Content-Type", "text/html");
414 MHD_add_response_header(response
, "Location", target
);
416 ret
= MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
417 MHD_destroy_response(response
);
422 static int request_handler_file(
423 struct MHD_Connection
*connection
,
425 const char *mime_type
) {
427 struct MHD_Response
*response
;
429 _cleanup_close_
int fd
= -1;
436 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
438 return respond_error(connection
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m\n", path
);
440 if (fstat(fd
, &st
) < 0)
441 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m\n");
443 response
= MHD_create_response_from_fd_at_offset(st
.st_size
, fd
, 0);
445 return respond_oom(connection
);
449 MHD_add_response_header(response
, "Content-Type", mime_type
);
451 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
452 MHD_destroy_response(response
);
457 static int request_handler_machine(
458 struct MHD_Connection
*connection
,
459 void **connection_cls
) {
461 struct MHD_Response
*response
;
464 _cleanup_free_
char* hostname
= NULL
, *os_name
= NULL
;
465 uint64_t cutoff_from
, cutoff_to
, usage
;
468 const char *v
= "bare";
472 m
= request_meta(connection_cls
);
474 return respond_oom(connection
);
478 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %s\n", strerror(-r
));
480 r
= sd_id128_get_machine(&mid
);
482 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %s\n", strerror(-r
));
484 r
= sd_id128_get_boot(&bid
);
486 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %s\n", strerror(-r
));
488 hostname
= gethostname_malloc();
490 return respond_oom(connection
);
492 r
= sd_journal_get_usage(m
->journal
, &usage
);
494 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
496 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
498 return respond_error(connection
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %s\n", strerror(-r
));
500 parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
);
502 detect_virtualization(&v
);
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",
518 (unsigned long long) usage
,
519 (unsigned long long) cutoff_from
,
520 (unsigned long long) cutoff_to
);
523 return respond_oom(connection
);
525 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
528 return respond_oom(connection
);
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
);
538 static int request_handler(
540 struct MHD_Connection
*connection
,
544 const char *upload_data
,
545 size_t *upload_data_size
,
546 void **connection_cls
) {
552 if (!streq(method
, "GET"))
556 return request_handler_redirect(connection
, "/browse");
558 if (streq(url
, "/entries"))
559 return request_handler_entries(connection
, connection_cls
);
561 if (streq(url
, "/browse"))
562 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
564 if (streq(url
, "/machine"))
565 return request_handler_machine(connection
, connection_cls
);
567 return respond_error(connection
, MHD_HTTP_NOT_FOUND
, "Not found.\n");
570 int main(int argc
, char *argv
[]) {
571 struct MHD_Daemon
*daemon
= NULL
;
572 int r
= EXIT_FAILURE
, n
;
575 log_error("This program does not take arguments.");
579 log_set_target(LOG_TARGET_KMSG
);
580 log_parse_environment();
583 n
= sd_listen_fds(1);
585 log_error("Failed to determine passed sockets: %s", strerror(-n
));
588 log_error("Can't listen on more than one socket.");
591 daemon
= MHD_start_daemon(
592 MHD_USE_THREAD_PER_CONNECTION
|MHD_USE_POLL
|MHD_USE_DEBUG
,
595 request_handler
, NULL
,
596 MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
,
597 MHD_OPTION_NOTIFY_COMPLETED
, request_meta_free
, NULL
,
600 daemon
= MHD_start_daemon(
601 MHD_USE_DEBUG
|MHD_USE_THREAD_PER_CONNECTION
|MHD_USE_POLL
,
604 request_handler
, NULL
,
605 MHD_OPTION_NOTIFY_COMPLETED
, request_meta_free
, NULL
,
610 log_error("Failed to start daemon!");
620 MHD_stop_daemon(daemon
);