journal-remote.c revision 1e4e7b71e1938daa9b2b9718a9f54c69017a9ef5
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2012 Zbigniew Jędrzejewski-Szmek
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 <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include "sd-daemon.h"
#include "sd-event.h"
#include "journal-file.h"
#include "journald-native.h"
#include "socket-util.h"
#include "mkdir.h"
#include "build.h"
#include "macro.h"
#include "strv.h"
#include "fileio.h"
#include "microhttpd-util.h"
#ifdef HAVE_GNUTLS
#endif
#include "journal-remote-parse.h"
#include "journal-remote-write.h"
static char* arg_output = NULL;
static char* arg_getter = NULL;
static char* arg_listen_raw = NULL;
static char* arg_listen_http = NULL;
static char* arg_listen_https = NULL;
static int arg_compress = true;
static int arg_seal = false;
static char** arg_gnutls_log = NULL;
/**********************************************************************
**********************************************************************
**********************************************************************/
int fd[2];
int r;
log_error("Failed to create pager pipe: %m");
return -errno;
}
parent_pid = getpid();
if (child_pid < 0) {
r = -errno;
log_error("Failed to fork: %m");
return r;
}
/* In the child */
if (child_pid == 0) {
if (r < 0) {
log_error("Failed to dup pipe to stdout: %m");
}
/* Make sure the child goes away when the parent dies */
/* Check whether our parent died before we were able
* to set the death signal */
if (getppid() != parent_pid)
}
if (r < 0)
log_warning("Failed to close write end of pipe: %m");
return fd[0];
}
static int spawn_curl(char* url) {
"-HAccept: application/vnd.fdo.journal",
"--silent",
"--show-error",
url);
int r;
if (r < 0)
log_error("Failed to spawn curl: %m");
return r;
}
int r;
if (!words)
return log_oom();
if (r < 0)
return r;
}
char *c;
int r;
if (!name)
return log_oom();
for(c = name; *c; c++) {
if (*c == '/' || *c == ':' || *c == ' ')
*c = '~';
else if (*c == '?') {
*c = '\0';
break;
}
}
if (!arg_output) {
r = sd_id128_get_machine(&machine);
if (r < 0) {
return r;
}
if (r < 0)
return log_oom();
} else {
r = is_dir(arg_output, true);
if (r > 0) {
if (r < 0)
return log_oom();
} else {
if (!output)
return log_oom();
}
}
&s->metrics,
s->mmap,
if (r < 0)
log_error("Failed to open output journal %s: %s",
arg_output, strerror(-r));
else
return r;
}
/**********************************************************************
**********************************************************************
**********************************************************************/
typedef struct MHDDaemonWrapper {
struct MHD_Daemon *daemon;
typedef struct RemoteServer {
} RemoteServer;
/* This should go away as soon as µhttpd allows state to be passed around. */
static RemoteServer *server;
int fd,
void *userdata);
int fd,
void *userdata);
int fd,
void *userdata);
return log_oom();
return log_oom();
s->active++;
}
return 0;
}
assert(s);
if (source) {
s->active--;
}
return 0;
}
int r;
assert(s);
if (name) {
if (!realname)
return log_oom();
} else {
if (r < 0)
return log_oom();
}
if (r < 0) {
return r;
}
if (r < 0) {
log_error("Failed to register event source for fd:%d: %s",
goto error;
}
return 1; /* work to do */
remove_source(s, fd);
return r;
}
int r;
if (r < 0) {
return r;
}
s->active ++;
return 0;
}
int fd;
if (fd < 0)
return fd;
return add_raw_socket(s, fd);
}
/**********************************************************************
**********************************************************************
**********************************************************************/
if (*connection_cls)
return *connection_cls;
if (!source)
return NULL;
*connection_cls = source;
return source;
}
static void request_meta_free(void *cls,
struct MHD_Connection *connection,
void **connection_cls,
enum MHD_RequestTerminationCode toe) {
RemoteSource *s;
s = *connection_cls;
log_debug("Cleaning up connection metadata %p", s);
source_free(s);
*connection_cls = NULL;
}
static int process_http_upload(
struct MHD_Connection *connection,
const char *upload_data,
RemoteSource *source) {
bool finished = false;
int r;
log_debug("request_handler_upload: connection %p, %zu bytes",
if (*upload_data_size) {
if (r < 0) {
log_error("Failed to store received data of size %zu: %s",
*upload_data_size, strerror(-r));
return mhd_respond_oom(connection);
}
*upload_data_size = 0;
} else
finished = true;
while (true) {
if (r == -E2BIG)
log_warning("Entry too big, skipped");
else if (r == -EAGAIN || r == -EWOULDBLOCK)
break;
else if (r < 0) {
"Processing failed: %s", strerror(-r));
}
}
if (!finished)
return MHD_YES;
/* The upload is finished */
if (source_non_empty(source)) {
log_warning("EOF reached with incomplete data");
"Trailing data not processed.");
}
};
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) {
const char *header;
int r ,code;
if (*connection_cls)
return process_http_upload(connection,
"Unsupported method.\n");
"Not found.\n");
MHD_HEADER_KIND, "Content-Type");
"Content-Type: application/vnd.fdo.journal"
" is required.\n");
if (trust_pem) {
if (r < 0)
return code;
}
if (!request_meta(connection_cls))
return respond_oom(connection);
return MHD_YES;
}
struct MHD_OptionItem opts[] = {
{ MHD_OPTION_END},
{ MHD_OPTION_END},
{ MHD_OPTION_END},
{ MHD_OPTION_END}};
int opts_pos = 3;
int flags =
const union MHD_DaemonInfo *info;
int r, epoll_fd;
MHDDaemonWrapper *d;
r = fd_nonblock(fd, true);
if (r < 0) {
return r;
}
if (https) {
{MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
flags |= MHD_USE_SSL;
if (trust_pem)
}
if (!d)
return log_oom();
if (!d->daemon) {
log_error("Failed to start µhttp daemon");
r = -EINVAL;
goto error;
}
log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
if (!info) {
log_error("µhttp returned NULL daemon info");
r = -ENOTSUP;
goto error;
}
if (epoll_fd < 0) {
log_error("µhttp epoll fd is invalid");
r = -EUCLEAN;
goto error;
}
if (r < 0) {
goto error;
}
if (r < 0) {
log_oom();
goto error;
}
if (r < 0) {
goto error;
}
s->active ++;
return 0;
MHD_stop_daemon(d->daemon);
free(d);
return r;
}
static int setup_microhttpd_socket(RemoteServer *s,
const char *address,
bool https) {
int fd;
if (fd < 0)
return fd;
}
int fd,
void *userdata) {
MHDDaemonWrapper *d = userdata;
int r;
assert(d);
if (r == MHD_NO) {
log_error("MHD_run failed!");
// XXX: unregister daemon
return -EINVAL;
}
return 1; /* work to do */
}
/**********************************************************************
**********************************************************************
**********************************************************************/
const struct signalfd_siginfo *si,
void *userdata) {
RemoteServer *s = userdata;
assert(s);
sd_event_exit(s->events, 0);
return 0;
}
static int setup_signals(RemoteServer *s) {
int r;
assert(s);
if (r < 0)
return r;
if (r < 0)
return r;
return 0;
}
int fd, r;
if (r < 0)
return r;
if (fd >= 0)
return -ENOENT;
return -fd;
}
static int remoteserver_init(RemoteServer *s) {
int r, n, fd;
const char *output_name = NULL;
char **file;
assert(s);
sd_event_default(&s->events);
setup_signals(s);
server = s;
n = sd_listen_fds(true);
if (n < 0) {
log_error("Failed to read listening file descriptors from environment: %s",
strerror(-n));
return n;
} else
log_info("Received %d descriptors", n);
log_error("Received fewer sockets than expected");
return -EBADFD;
}
if (fd == http_socket)
r = setup_microhttpd_server(s, fd, false);
else if (fd == https_socket)
r = setup_microhttpd_server(s, fd, true);
else
r = add_raw_socket(s, fd);
} else {
return -EINVAL;
}
if(r < 0) {
log_error("Failed to register socket (fd:%d): %s",
return r;
}
output_name = "socket";
}
if (arg_url) {
if (!urlv)
return log_oom();
if (!url)
return log_oom();
if (arg_getter) {
} else {
}
if (fd < 0)
return fd;
if (r < 0)
return r;
}
if (arg_listen_raw) {
log_info("Listening on a socket...");
r = setup_raw_socket(s, arg_listen_raw);
if (r < 0)
return r;
}
if (arg_listen_http) {
r = setup_microhttpd_socket(s, arg_listen_http, false);
if (r < 0)
return r;
}
if (arg_listen_https) {
r = setup_microhttpd_socket(s, arg_listen_https, true);
if (r < 0)
return r;
}
log_info("Reading standard input...");
fd = STDIN_FILENO;
output_name = "stdin";
} else {
if (fd < 0) {
return -errno;
}
output_name = *file;
}
if (r < 0)
return r;
}
if (s->active == 0) {
log_error("Zarro sources specified");
return -EINVAL;
}
output_name = "multiple";
r = writer_init(&s->writer);
if (r < 0)
return r;
return r;
}
static int server_destroy(RemoteServer *s) {
int r;
size_t i;
MHDDaemonWrapper *d;
r = writer_close(&s->writer);
while ((d = hashmap_steal_first(s->daemons))) {
MHD_stop_daemon(d->daemon);
free(d);
}
hashmap_free(s->daemons);
for (i = 0; i < s->sources_size; i++)
remove_source(s, i);
sd_event_unref(s->events);
/* fds that we're listening on remain open... */
return r;
}
/**********************************************************************
**********************************************************************
**********************************************************************/
int fd,
void *userdata) {
RemoteServer *s = userdata;
int r;
log_info("EOF reached with source fd:%d (%s)",
if (source_non_empty(source))
log_warning("EOF reached with incomplete data");
} else if (r == -E2BIG) {
log_error("Entry too big, skipped");
r = 1;
}
return r;
}
int fd2, r;
if (fd2 < 0) {
return -errno;
}
switch(socket_address_family(addr)) {
case AF_INET:
case AF_INET6: {
char* _cleanup_free_ a = NULL;
r = socket_address_print(addr, &a);
if (r < 0) {
return r;
}
log_info("Accepted %s %s connection from %s",
type,
a);
return fd2;
};
default:
log_error("Rejected %s connection with unsupported family %d",
return -EINVAL;
}
}
int fd,
void *userdata) {
RemoteServer *s = userdata;
int fd2;
SocketAddress addr = {
.size = sizeof(union sockaddr_union),
.type = SOCK_STREAM,
};
if (fd2 < 0)
return fd2;
}
/**********************************************************************
**********************************************************************
**********************************************************************/
static int help(void) {
printf("%s [OPTIONS...] {FILE|-}...\n\n"
"Write external journal events to a journal file.\n\n"
"Options:\n"
" --url=URL Read events from systemd-journal-gatewayd at URL\n"
" --getter=COMMAND Read events from the output of COMMAND\n"
" --listen-raw=ADDR Listen for connections at ADDR\n"
" --listen-http=ADDR Listen for HTTP connections at ADDR\n"
" --listen-https=ADDR Listen for HTTPS connections at ADDR\n"
" --[no-]compress Use XZ-compression in the output journal (default: yes)\n"
" --[no-]seal Use Event sealing in the output journal (default: no)\n"
" --key=FILENAME Specify key in PEM format\n"
" --cert=FILENAME Specify certificate in PEM format\n"
" --trust=FILENAME Specify CA certificate in PEM format\n"
" --gnutls-log=CATEGORY...\n"
" Specify a list of gnutls logging categories\n"
" -h --help Show this help and exit\n"
" --version Print version string and exit\n"
"\n"
"Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
return 0;
}
enum {
ARG_VERSION = 0x100,
};
{}
};
int c, r;
switch(c) {
case 'h':
help();
return 0 /* done */;
case ARG_VERSION:
return 0 /* done */;
case ARG_URL:
if (arg_url) {
log_error("cannot currently set more than one --url");
return -EINVAL;
}
break;
case ARG_GETTER:
if (arg_getter) {
log_error("cannot currently use --getter more than once");
return -EINVAL;
}
arg_getter = optarg;
break;
case ARG_LISTEN_RAW:
if (arg_listen_raw) {
log_error("cannot currently use --listen-raw more than once");
return -EINVAL;
}
break;
case ARG_LISTEN_HTTP:
if (arg_listen_http || http_socket >= 0) {
log_error("cannot currently use --listen-http more than once");
return -EINVAL;
}
if (r >= 0)
http_socket = r;
else if (r == -ENOENT)
else {
return -EINVAL;
}
break;
case ARG_LISTEN_HTTPS:
if (arg_listen_https || https_socket >= 0) {
log_error("cannot currently use --listen-https more than once");
return -EINVAL;
}
if (r >= 0)
https_socket = r;
else if (r == -ENOENT)
else {
return -EINVAL;
}
break;
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 ARG_TRUST:
#ifdef HAVE_GNUTLS
if (trust_pem) {
log_error("CA certificate file specified twice");
return -EINVAL;
}
if (r < 0) {
return r;
}
break;
#else
log_error("Option --trust is not available.");
return -EINVAL;
#endif
case 'o':
if (arg_output) {
log_error("cannot use --output/-o more than once");
return -EINVAL;
}
arg_output = optarg;
break;
case ARG_COMPRESS:
arg_compress = true;
break;
case ARG_NO_COMPRESS:
arg_compress = false;
break;
case ARG_SEAL:
arg_seal = true;
break;
case ARG_NO_SEAL:
arg_seal = false;
break;
case ARG_GNUTLS_LOG: {
#ifdef HAVE_GNUTLS
char *cat;
if (!cat)
return log_oom();
return log_oom();
}
break;
#else
log_error("Option --gnutls-log is not available.");
return -EINVAL;
#endif
}
case '?':
return -EINVAL;
default:
log_error("Unknown option code %c", c);
return -EINVAL;
}
log_error("Options --key and --cert must be used when https sources are specified");
return -EINVAL;
}
return 1 /* work to do */;
}
static int setup_gnutls_logger(char **categories) {
if (!arg_listen_http && !arg_listen_https)
return 0;
#ifdef HAVE_GNUTLS
{
char **cat;
int r;
if (categories)
r = log_enable_gnutls_category(*cat);
if (r < 0)
return r;
}
else
}
#endif
return 0;
}
RemoteServer s = {};
int r, r2;
log_show_color(true);
if (r <= 0)
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (r < 0)
return EXIT_FAILURE;
if (remoteserver_init(&s) < 0)
return EXIT_FAILURE;
sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");
while (s.active) {
r = sd_event_get_state(s.events);
if (r < 0)
break;
if (r == SD_EVENT_FINISHED)
break;
if (r < 0) {
break;
}
}
r2 = server_destroy(&s);
sd_notify(false, "STATUS=Shutting down...");
}