journal-gatewayd.c revision 40ca29a1370379d43e44c0ed425eecc7218dcbca
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2012 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>
#include <microhttpd.h>
#include "log.h"
#include "util.h"
#include "sd-journal.h"
#include "sd-daemon.h"
#include "sd-bus.h"
#include "bus-util.h"
#include "logs-show.h"
#include "microhttpd-util.h"
#include "build.h"
#include "fileio.h"
typedef struct RequestMeta {
char *cursor;
bool n_entries_set;
int argument_parse_error;
bool follow;
bool discrete;
bool n_fields_set;
} RequestMeta;
static const char* const mime_types[_OUTPUT_MODE_MAX] = {
[OUTPUT_SHORT] = "text/plain",
[OUTPUT_JSON] = "application/json",
[OUTPUT_JSON_SSE] = "text/event-stream",
};
RequestMeta *m;
if (*connection_cls)
return *connection_cls;
if (!m)
return NULL;
*connection_cls = m;
return m;
}
static void request_meta_free(
void *cls,
struct MHD_Connection *connection,
void **connection_cls,
enum MHD_RequestTerminationCode toe) {
RequestMeta *m = *connection_cls;
if (!m)
return;
if (m->journal)
sd_journal_close(m->journal);
if (m->tmp)
free(m);
}
static int open_journal(RequestMeta *m) {
assert(m);
if (m->journal)
return 0;
}
struct MHD_Response *response;
const char m[] = "Out of memory.\n";
int ret;
if (!response)
return MHD_NO;
return ret;
}
static int respond_error(
struct MHD_Connection *connection,
unsigned code,
const char *format, ...) {
struct MHD_Response *response;
char *m;
int r;
if (r < 0)
return respond_oom(connection);
if (!response) {
free(m);
return respond_oom(connection);
}
return r;
}
static ssize_t request_reader_entries(
void *cls,
char *buf,
RequestMeta *m = cls;
int r;
size_t n, k;
assert(m);
/* End of this entry, so let's serialize the next
* one */
if (m->n_entries_set &&
m->n_entries <= 0)
return MHD_CONTENT_READER_END_OF_STREAM;
if (m->n_skip < 0)
else if (m->n_skip > 0)
else
r = sd_journal_next(m->journal);
if (r < 0) {
} else if (r == 0) {
if (m->follow) {
if (r < 0) {
}
continue;
}
return MHD_CONTENT_READER_END_OF_STREAM;
}
if (m->discrete) {
if (r < 0) {
}
if (r == 0)
return MHD_CONTENT_READER_END_OF_STREAM;
}
if (m->n_entries_set)
m->n_entries -= 1;
m->n_skip = 0;
if (m->tmp)
else {
if (!m->tmp) {
log_error("Failed to create temporary file: %m");
}
}
if (r < 0) {
}
log_error("Failed to retrieve file position: %m");
}
}
log_error("Failed to seek to position: %m");
}
if (n > max)
n = max;
errno = 0;
if (k != n) {
}
return (ssize_t) k;
}
static int request_parse_accept(
RequestMeta *m,
struct MHD_Connection *connection) {
const char *header;
assert(m);
if (!header)
return 0;
m->mode = OUTPUT_JSON;
m->mode = OUTPUT_JSON_SSE;
m->mode = OUTPUT_EXPORT;
else
m->mode = OUTPUT_SHORT;
return 0;
}
static int request_parse_range(
RequestMeta *m,
struct MHD_Connection *connection) {
int r;
assert(m);
if (!range)
return 0;
return 0;
range += 8;
if (!colon)
else {
const char *p;
if (colon2) {
_cleanup_free_ char *t;
if (!t)
return -ENOMEM;
r = safe_atoi64(t, &m->n_skip);
if (r < 0)
return r;
}
if (*p) {
r = safe_atou64(p, &m->n_entries);
if (r < 0)
return r;
if (m->n_entries <= 0)
return -EINVAL;
m->n_entries_set = true;
}
}
if (!m->cursor)
return -ENOMEM;
}
return 0;
}
static int request_parse_arguments_iterator(
void *cls,
enum MHD_ValueKind kind,
const char *key,
const char *value) {
RequestMeta *m = cls;
_cleanup_free_ char *p = NULL;
int r;
assert(m);
m->argument_parse_error = -EINVAL;
return MHD_NO;
}
m->follow = true;
return MHD_YES;
}
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
m->follow = r;
return MHD_YES;
}
m->discrete = true;
return MHD_YES;
}
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
m->discrete = r;
return MHD_YES;
}
r = true;
else {
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
}
if (r) {
r = sd_id128_get_boot(&bid);
if (r < 0) {
return MHD_NO;
}
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
}
return MHD_YES;
}
if (!p) {
m->argument_parse_error = log_oom();
return MHD_NO;
}
r = sd_journal_add_match(m->journal, p, 0);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
return MHD_YES;
}
static int request_parse_arguments(
RequestMeta *m,
struct MHD_Connection *connection) {
assert(m);
m->argument_parse_error = 0;
return m->argument_parse_error;
}
static int request_handler_entries(
struct MHD_Connection *connection,
void *connection_cls) {
struct MHD_Response *response;
RequestMeta *m = connection_cls;
int r;
assert(m);
r = open_journal(m);
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
if (request_parse_accept(m, connection) < 0)
if (request_parse_range(m, connection) < 0)
if (request_parse_arguments(m, connection) < 0)
if (m->discrete) {
if (!m->cursor)
return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
m->n_entries = 1;
m->n_entries_set = true;
}
if (m->cursor)
else if (m->n_skip >= 0)
r = sd_journal_seek_head(m->journal);
else if (m->n_skip < 0)
r = sd_journal_seek_tail(m->journal);
if (r < 0)
response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
if (!response)
return respond_oom(connection);
return r;
}
const char *eq;
size_t j;
if (!eq)
return -EINVAL;
j = l - (eq - d + 1);
if (m == OUTPUT_JSON) {
fputs(" }\n", f);
} else {
fputc('\n', f);
}
return 0;
}
static ssize_t request_reader_fields(
void *cls,
char *buf,
RequestMeta *m = cls;
int r;
size_t n, k;
assert(m);
const void *d;
size_t l;
/* End of this field, so let's serialize the next
* one */
if (m->n_fields_set &&
m->n_fields <= 0)
return MHD_CONTENT_READER_END_OF_STREAM;
r = sd_journal_enumerate_unique(m->journal, &d, &l);
if (r < 0) {
} else if (r == 0)
return MHD_CONTENT_READER_END_OF_STREAM;
if (m->n_fields_set)
m->n_fields -= 1;
if (m->tmp)
else {
if (!m->tmp) {
log_error("Failed to create temporary file: %m");
}
}
if (r < 0) {
}
log_error("Failed to retrieve file position: %m");
}
}
log_error("Failed to seek to position: %m");
}
if (n > max)
n = max;
errno = 0;
if (k != n) {
}
return (ssize_t) k;
}
static int request_handler_fields(
struct MHD_Connection *connection,
const char *field,
void *connection_cls) {
struct MHD_Response *response;
RequestMeta *m = connection_cls;
int r;
assert(m);
r = open_journal(m);
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
if (request_parse_accept(m, connection) < 0)
if (r < 0)
response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
if (!response)
return respond_oom(connection);
MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
return r;
}
static int request_handler_redirect(
struct MHD_Connection *connection,
const char *target) {
char *page;
struct MHD_Response *response;
int ret;
if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
return respond_oom(connection);
if (!response) {
return respond_oom(connection);
}
return ret;
}
static int request_handler_file(
struct MHD_Connection *connection,
const char *path,
const char *mime_type) {
struct MHD_Response *response;
int ret;
if (fd < 0)
if (!response)
return respond_oom(connection);
fd = -1;
return ret;
}
static int get_virtualization(char **v) {
const char *t;
char *b;
int r;
r = sd_bus_open_system(&bus);
if (r < 0)
return r;
r = sd_bus_call_method(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.DBus.Properties",
"Get",
NULL,
&reply,
"ss",
"org.freedesktop.systemd1.Manager",
"Virtualization");
if (r < 0)
return r;
if (r < 0)
return r;
if (isempty(t)) {
*v = NULL;
return 0;
}
b = strdup(t);
if (!b)
return -ENOMEM;
*v = b;
return 1;
}
static int request_handler_machine(
struct MHD_Connection *connection,
void *connection_cls) {
struct MHD_Response *response;
RequestMeta *m = connection_cls;
int r;
char *json;
_cleanup_free_ char *v = NULL;
assert(m);
r = open_journal(m);
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
r = sd_id128_get_machine(&mid);
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
r = sd_id128_get_boot(&bid);
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
if (!hostname)
return respond_oom(connection);
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
if (r < 0)
return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
get_virtualization(&v);
"\"hostname\" : \"%s\","
"\"os_pretty_name\" : \"%s\","
"\"virtualization\" : \"%s\","
hostname_cleanup(hostname, false),
v ? v : "bare",
if (r < 0)
return respond_oom(connection);
if (!response) {
return respond_oom(connection);
}
return r;
}
static int request_handler(
void *cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
void **connection_cls) {
"Unsupported method.\n");
if (!*connection_cls) {
if (!request_meta(connection_cls))
return respond_oom(connection);
return MHD_YES;
}
}
static int help(void) {
printf("%s [OPTIONS...] ...\n\n"
"HTTP server for journal events.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --cert=CERT.PEM Specify server certificate in PEM format\n"
" --key=KEY.PEM Specify server key in PEM format\n",
return 0;
}
enum {
ARG_VERSION = 0x100,
};
int r, c;
};
switch(c) {
case ARG_VERSION:
return 0;
case 'h':
return help();
case ARG_KEY:
if (key_pem) {
log_error("Key file specified twice");
return -EINVAL;
}
if (r < 0) {
return r;
}
break;
case ARG_CERT:
if (cert_pem) {
log_error("Certificate file specified twice");
return -EINVAL;
}
if (r < 0) {
return r;
}
break;
case '?':
return -EINVAL;
default:
log_error("Unknown option code %c", c);
return -EINVAL;
}
log_error("This program does not take arguments.");
return -EINVAL;
}
log_error("Certificate and key files must be specified together");
return -EINVAL;
}
return 1;
}
struct MHD_Daemon *d = NULL;
int r, n;
log_open();
if (r < 0)
return EXIT_FAILURE;
if (r == 0)
return EXIT_SUCCESS;
n = sd_listen_fds(1);
if (n < 0) {
goto finish;
} else if (n > 1) {
log_error("Can't listen on more than one socket.");
goto finish;
} else {
struct MHD_OptionItem opts[] = {
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL }};
int opts_pos = 2;
if (n > 0)
if (key_pem) {
{MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
flags |= MHD_USE_SSL;
}
}
if (!d) {
log_error("Failed to start daemon!");
goto finish;
}
pause();
r = EXIT_SUCCESS;
if (d)
MHD_stop_daemon(d);
return r;
}