Discussion:
[PATCH] journal: Introduce journal-syslogd
(too old to reply)
Susant Sahani
2015-03-18 05:30:12 UTC
Permalink
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.

V2: Address Zbigniew's comments
1. Rename binary systemd-journal-syslogd
2. Fixed up man and added example
3. Error code check sd_event_add_signal
4. remove +User=systemd-journal-network from service file
5. remove opterr=0
6. assignment into declaration of mh
---
Makefile-man.am | 8 +
Makefile.am | 37 ++
man/systemd-journal-syslogd.service.xml | 84 +++++
man/systemd-journal-syslogd.xml | 146 ++++++++
src/journal-remote/journal-syslog-conf.c | 61 ++++
src/journal-remote/journal-syslog-conf.h | 39 ++
src/journal-remote/journal-syslog-gperf.gperf | 18 +
src/journal-remote/journal-syslog-manager.c | 491 ++++++++++++++++++++++++++
src/journal-remote/journal-syslog-manager.h | 70 ++++
src/journal-remote/journal-syslog-network.c | 218 ++++++++++++
src/journal-remote/journal-syslogd.c | 217 ++++++++++++
src/journal-remote/journal-syslogd.conf.in | 2 +
units/systemd-journal-syslogd.service | 18 +
13 files changed, 1409 insertions(+)
create mode 100644 man/systemd-journal-syslogd.service.xml
create mode 100644 man/systemd-journal-syslogd.xml
create mode 100644 src/journal-remote/journal-syslog-conf.c
create mode 100644 src/journal-remote/journal-syslog-conf.h
create mode 100644 src/journal-remote/journal-syslog-gperf.gperf
create mode 100644 src/journal-remote/journal-syslog-manager.c
create mode 100644 src/journal-remote/journal-syslog-manager.h
create mode 100644 src/journal-remote/journal-syslog-network.c
create mode 100644 src/journal-remote/journal-syslogd.c
create mode 100644 src/journal-remote/journal-syslogd.conf.in
create mode 100644 units/systemd-journal-syslogd.service

diff --git a/Makefile-man.am b/Makefile-man.am
index ab1db33..80584b7 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -1374,6 +1374,14 @@ man/systemd-journal-gatewayd.socket.html: man/systemd-journal-gatewayd.service.h

endif

+MANPAGES += \
+ man/systemd-journal-syslogd.service.8 \
+ man/systemd-journal-syslogd.8
+MANPAGES_ALIAS += \
+ man/systemd-journal-syslogd.8
+man/systemd-journal-syslogd.8: man/systemd-journal-syslogd.service.8
+man/systemd-journal-syslogd.html: man/systemd-journal-syslogd.service.html
+
if HAVE_MYHOSTNAME
MANPAGES += \
man/nss-myhostname.8
diff --git a/Makefile.am b/Makefile.am
index 856accb..e0b985a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4336,6 +4336,43 @@ EXTRA_DIST += \
src/journal-remote/journal-upload.conf.in
endif

+systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-manager.h \
+ src/journal-remote/journal-syslog-manager.c \
+ src/journal-remote/journal-syslog-conf.h \
+ src/journal-remote/journal-syslog-conf.c \
+ src/journal-remote/journal-syslog-network.c \
+ src/journal-remote/journal-syslogd.c
+
+nodist_systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-gperf.c
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslog-gperf.gperf
+
+CLEANFILES += \
+ src/journal-remote/journal-syslog-gperf.c
+
+systemd_journal_syslogd_LDADD = \
+ libsystemd-internal.la \
+ libsystemd-journal-internal.la \
+ libsystemd-shared.la
+
+rootlibexec_PROGRAMS += \
+ systemd-journal-syslogd
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-syslogd.service
+
+EXTRA_DIST += \
+ units/systemd-journal-syslogd.service.in
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-syslogd.conf
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslogd.conf.in
+
# using _CFLAGS = in the conditional below would suppress AM_CFLAGS
journalctl_CFLAGS = \
$(AM_CFLAGS)
diff --git a/man/systemd-journal-syslogd.service.xml b/man/systemd-journal-syslogd.service.xml
new file mode 100644
index 0000000..b540499
--- /dev/null
+++ b/man/systemd-journal-syslogd.service.xml
@@ -0,0 +1,84 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd.service" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-syslogd.service</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ <email>***@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd.service</refname>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Forward journal events using syslog network procotol</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-journal-syslogd.service</filename></para>
+ <cmdsynopsis>
+ <command>/usr/lib/systemd/systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-journal-syslogd</command> serves journal
+ events over the network. It multicasts journal event to Syslog RFC 5424 format.
+ </para>
+
+ <para>The program is started by
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ . Use
+ <command>systemctl start systemd-journal-syslogd.service</command> to start
+ the service, and <command>systemctl enable systemd-journal-syslogd.service</command>
+ to have it started on boot.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd-journal-syslogd.xml b/man/systemd-journal-syslogd.xml
new file mode 100644
index 0000000..e896065
--- /dev/null
+++ b/man/systemd-journal-syslogd.xml
@@ -0,0 +1,146 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>systemd-journal-syslogd</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ <email>***@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Send journal messages over the network in RFC 5424 format</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">--save-state=<replaceable>state file</replaceable></arg>
+ <arg choice="opt" rep="norepeat">--cursor=<replaceable>journal cursor</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>systemd-journal-syslogd</command> will send journal
+ entries to the UDP Multicast address . Unless
+ limited by one of the options specified below, all journal
+ entries accessible to the user the program is running as will be
+ sent, and then the program will wait and send new entries
+ as they become available.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Send entries from the location in the
+ journal specified by the passed cursor. This has the same
+ meaning as <option>--cursor</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--save-state</option><optional>=<replaceable>PATH</replaceable></optional></term>
+
+ <listitem><para>Send entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the cursor saved in file at <replaceable>PATH</replaceable>
+ (<filename>/var/lib/systemd/journal-syslogd/state</filename> by default).
+ After an entry is successfully uploaded, update this file
+ with the cursor of that entry.
+ </para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned; otherwise, a non-zero
+ failure code is returned.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>[Network] Section Options</title>
+
+ <para>The <literal>[Network]</literal> section only applies for
+ UDP multicast address and Port:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>Address=</varname></term>
+ <listitem><para>Controls whether log messages received by the
+ journal daemon shall be forwarded to a multicast UDP network
+ group in syslog RFC 5424 format.</para>
+
+ <para>The the address string format is similar to socket units. See
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=239.0.0.1:6000
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-syslogd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/journal-remote/journal-syslog-conf.c b/src/journal-remote/journal-syslog-conf.c
new file mode 100644
index 0000000..c357189
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.c
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "in-addr-util.h"
+#include "journal-syslog-conf.h"
+
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Manager *m = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = socket_address_parse(&m->address, rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Failed to parse address value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int manager_parse_config_file(Manager *m) {
+ assert(m);
+
+ return config_parse_many("/etc/systemd/journal-syslogd.conf",
+ CONF_DIRS_NULSTR("systemd/journal-syslogd.conf"),
+ "Network\0",
+ config_item_perf_lookup, journal_syslog_gperf_lookup,
+ false, m);
+}
diff --git a/src/journal-remote/journal-syslog-conf.h b/src/journal-remote/journal-syslog-conf.h
new file mode 100644
index 0000000..ca9ef05
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "in-addr-util.h"
+#include "conf-parser.h"
+#include "journal-syslog-manager.h"
+
+const struct ConfigPerfItem* journal_syslog_gperf_lookup(const char *key, unsigned length);
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata);
+int manager_parse_config_file(Manager *m);
diff --git a/src/journal-remote/journal-syslog-gperf.gperf b/src/journal-remote/journal-syslog-gperf.gperf
new file mode 100644
index 0000000..e0f364f
--- /dev/null
+++ b/src/journal-remote/journal-syslog-gperf.gperf
@@ -0,0 +1,18 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journal_syslog_gperf_hash
+%define lookup-function-name journal_syslog_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Network.Address, config_parse_syslog_broadcast_address, 0, 0
diff --git a/src/journal-remote/journal-syslog-manager.c b/src/journal-remote/journal-syslog-manager.c
new file mode 100644
index 0000000..f2c3ced
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.c
@@ -0,0 +1,491 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "util.h"
+#include "socket-util.h"
+#include "conf-parser.h"
+#include "sd-daemon.h"
+#include "network-util.h"
+#include "capability.h"
+#include "mkdir.h"
+#include "fileio.h"
+#include "journal-internal.h"
+#include "journal-syslog-manager.h"
+
+#define JOURNAL_SEND_POLL_TIMEOUT (10 * USEC_PER_SEC)
+
+/* Default severity LOG_NOTICE */
+#define JOURNAL_DEFAULT_SEVERITY "5"
+
+/* Default facility LOG_USER (1<<3) */
+#define JOURNAL_DEFAULT_FACILITY "8"
+
+
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+ size_t fl, nl;
+ void *buf;
+
+ assert(data);
+ assert(field);
+ assert(target);
+ assert(target_size);
+
+ fl = strlen(field);
+ if (length < fl)
+ return 0;
+
+ if (memcmp(data, field, fl))
+ return 0;
+
+ nl = length - fl;
+ buf = malloc(nl+1);
+ if (!buf)
+ return -ENOMEM;
+
+ memcpy(buf, (const char*) data + fl, nl);
+ ((char*)buf)[nl] = 0;
+
+ free(*target);
+ *target = buf;
+ *target_size = nl;
+
+ return 1;
+}
+
+static int manager_read_journal_input(Manager *m) {
+ _cleanup_free_ char *facility = NULL, *identifier = NULL,
+ *priority = NULL, *message = NULL, *pid = NULL,
+ *hostname = NULL;
+ struct timeval tv;
+ usec_t realtime;
+ const void *data;
+ size_t length;
+ size_t n = 0;
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ JOURNAL_FOREACH_DATA_RETVAL(m->journal, data, length, r) {
+
+ r = parse_field(data, length, "PRIORITY=", &priority, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_FACILITY=", &facility, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_HOSTNAME=", &hostname, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_PID=", &pid, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "MESSAGE=", &message, &n);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_journal_get_realtime_usec(m->journal, &realtime);
+ if (r < 0)
+ log_warning_errno(r, "Failed to rerieve realtime from journal: %m");
+ else {
+ tv.tv_sec = realtime / USEC_PER_SEC;
+ tv.tv_usec = realtime % USEC_PER_SEC;
+ }
+
+ /* Set to default facility and priority if missing */
+ if (!facility) {
+ facility = strdup(JOURNAL_DEFAULT_FACILITY);
+ if (!facility)
+ return -ENOMEM;
+ }
+
+ if (!priority) {
+ priority = strdup(JOURNAL_DEFAULT_FACILITY);
+ if (!priority)
+ return -ENOMEM;
+ }
+
+ return manager_push_to_network(m, priority, facility, identifier,
+ message, hostname, pid, r >= 0 ? &tv : NULL);
+}
+
+static int update_cursor_state(Manager *m) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file || !m->last_cursor)
+ return 0;
+
+ f = fopen(m->state_file, "we");
+ if (!f)
+ goto finish;
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ m->last_cursor);
+
+ fflush(f);
+
+ if (ferror(f))
+ r = -errno;
+
+ finish:
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
+ return r;
+}
+
+static int load_cursor_state(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (!m->state_file)
+ return 0;
+
+ r = parse_env_file(m->state_file, NEWLINE, "LAST_CURSOR", &m->last_cursor, NULL);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ log_debug("Last cursor was %s.", m->last_cursor ? m->last_cursor : "Not available");
+
+ return 0;
+}
+
+static int process_journal_input(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ while (true) {
+ r = sd_journal_next(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get next entry: %m");
+ return r;
+ }
+
+ if (r == 0)
+ break;
+
+ r = manager_read_journal_input(m);
+ if (r < 0) {
+ /* Can't send the message. Seek one entry back. */
+ r = sd_journal_previous(m->journal);
+ if (r < 0)
+ log_error_errno(r, "Failed to iterate through journal: %m");
+
+ break;
+ }
+ }
+
+ r = sd_journal_get_cursor(m->journal, &m->current_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ free(m->last_cursor);
+ m->last_cursor = m->current_cursor;
+ m->current_cursor = NULL;
+
+ return update_cursor_state(m);
+}
+
+static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) {
+ Manager *m = userp;
+ int r;
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ if (m->event_journal_input) {
+
+ r = sd_journal_process(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process journal: %m");
+ manager_disconnect(m);
+ return r;
+ }
+
+ if (r == SD_JOURNAL_NOP)
+ return 0;
+ }
+
+ return process_journal_input(m);
+}
+
+static void close_journal_input(Manager *m) {
+ assert(m);
+
+ if (m->journal) {
+ log_debug("Closing journal input.");
+
+ sd_journal_close(m->journal);
+ m->journal = NULL;
+ }
+
+ m->timeout = 0;
+}
+
+static int manager_signal_event_handler(sd_event_source *event, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+
+ assert(m);
+
+ log_received_signal(LOG_INFO, si);
+
+ manager_disconnect(m);
+
+ sd_event_exit(m->event, 0);
+
+ return 0;
+}
+
+static int manager_journal_monitor_listen(Manager *m) {
+ int r, events;
+
+ assert(m);
+
+ r = sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open journal: %m");
+ return r;
+ }
+
+ sd_journal_set_data_threshold(m->journal, 0);
+
+ m->journal_watch_fd = sd_journal_get_fd(m->journal);
+ if (m->journal_watch_fd < 0)
+ return log_error_errno(m->journal_watch_fd, "sd_journal_get_fd failed: %m");
+
+ events = sd_journal_get_events(m->journal);
+
+ r = sd_journal_reliable_fd(m->journal);
+ assert(r >= 0);
+ if (r > 0)
+ m->timeout = -1;
+ else
+ m->timeout = JOURNAL_SEND_POLL_TIMEOUT;
+
+ r = sd_event_add_io(m->event, &m->event_journal_input ,
+ m->journal_watch_fd , events, manager_journal_event_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register input event: %m");
+
+ /* ignore failure */
+ if (!m->last_cursor)
+ (void) load_cursor_state(m);
+
+ if (m->last_cursor) {
+ r = sd_journal_seek_cursor(m->journal, m->last_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to cursor %s: %m",
+ m->last_cursor);
+ }
+
+ return 0;
+}
+
+int manager_connect(Manager *m) {
+ int r;
+
+ assert(m);
+
+ manager_disconnect(m);
+
+ r = manager_open_network_socket(m);
+ if (r < 0)
+ return r;
+
+ r = manager_journal_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_disconnect(Manager *m) {
+ assert(m);
+
+ close_journal_input(m);
+
+ manager_close_network_socket(m);
+
+ m->event_journal_input = sd_event_source_unref(m->event_journal_input);
+
+ sd_notifyf(false, "STATUS=Idle.");
+}
+
+static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *m = userdata;
+ bool connected, online;
+ int r;
+
+ assert(m);
+
+ sd_network_monitor_flush(m->network_monitor);
+
+ /* check if the machine is online */
+ online = network_is_online();
+
+ /* check if the socket is currently open*/
+ connected = m->socket >= 0;
+
+ if (connected && !online) {
+ log_info("No network connectivity, watching for changes.");
+ manager_disconnect(m);
+
+ } else if (!connected && online) {
+ log_info("Network configuration changed, trying to establish connection.");
+
+ r = manager_connect(m);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int manager_network_monitor_listen(Manager *m) {
+ int r, fd, events;
+
+ assert(m);
+
+ r = sd_network_monitor_new(&m->network_monitor, NULL);
+ if (r < 0)
+ return r;
+
+ fd = sd_network_monitor_get_fd(m->network_monitor);
+ if (fd < 0)
+ return fd;
+
+ events = sd_network_monitor_get_events(m->network_monitor);
+ if (events < 0)
+ return events;
+
+ r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_free(Manager *m) {
+ if (!m)
+ return;
+
+ manager_disconnect(m);
+
+ free(m->last_cursor);
+ free(m->current_cursor);
+
+ free(m->state_file);
+
+ sd_event_source_unref(m->network_event_source);
+ sd_network_monitor_unref(m->network_monitor);
+
+ sd_event_source_unref(m->sigterm_event);
+ sd_event_source_unref(m->sigint_event);
+
+ sd_event_unref(m->event);
+
+ free(m);
+}
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ sigset_t mask;
+ int r;
+
+ assert(ret);
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ m->socket = m->journal_watch_fd = -1;
+
+ m->state_file = strdup(state_file);
+ if (!m->state_file)
+ return -ENOMEM;
+
+ if (cursor) {
+ m->last_cursor = strdup(cursor);
+ if (!m->last_cursor)
+ return -ENOMEM;
+ }
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ assert_se(sigemptyset(&mask) == 0);
+ sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+ assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+ r = sd_event_add_signal(m->event, NULL, SIGTERM, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGINT, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ sd_event_set_watchdog(m->event, true);
+
+ r = manager_network_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ *ret = m;
+ m = NULL;
+
+ return 0;
+}
diff --git a/src/journal-remote/journal-syslog-manager.h b/src/journal-remote/journal-syslog-manager.h
new file mode 100644
index 0000000..b947957
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.h
@@ -0,0 +1,70 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "sd-event.h"
+#include "sd-network.h"
+#include "socket-util.h"
+#include "sd-journal.h"
+
+typedef struct Manager Manager;
+
+struct Manager {
+ sd_event *event;
+ sd_event_source *event_journal_input;
+ uint64_t timeout;
+
+ sd_event_source *sigint_event, *sigterm_event;
+
+ /* network */
+ sd_event_source *network_event_source;
+ sd_network_monitor *network_monitor;
+
+ int socket;
+
+ /* Multicast UDP address */
+ SocketAddress address;
+
+ /* journal */
+ int journal_watch_fd;
+ sd_journal *journal;
+
+ char *state_file;
+
+ char *last_cursor, *current_cursor;
+};
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor);
+void manager_free(Manager *m);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+int manager_connect(Manager *m);
+void manager_disconnect(Manager *m);
+
+void manager_close_network_socket(Manager *m);
+int manager_open_network_socket(Manager *m);
+
+int manager_push_to_network(Manager *m, const char *priority, const char *facility,
+ const char *identifier, const char *message,
+ const char *hostname, const char *pid,
+ const struct timeval *tv);
diff --git a/src/journal-remote/journal-syslog-network.c b/src/journal-remote/journal-syslog-network.c
new file mode 100644
index 0000000..1f33ce0
--- /dev/null
+++ b/src/journal-remote/journal-syslog-network.c
@@ -0,0 +1,218 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <unistd.h>
+#include <stddef.h>
+#include <poll.h>
+
+#include "journal-syslog-manager.h"
+
+#define RFC_5424_NILVALUE "-"
+#define RFC_5424_PROTOCOL 1
+
+#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC)
+
+static int sendmsg_loop(Manager *m, struct msghdr *mh) {
+ int r;
+
+ assert(m);
+ assert(mh);
+
+ for (;;) {
+ if (sendmsg(m->socket, mh, MSG_NOSIGNAL) >= 0)
+ return 0;
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno != EAGAIN)
+ return -errno;
+
+ r = fd_wait_for_event(m->socket, POLLOUT, SEND_TIMEOUT_USEC);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
+ mh.msg_namelen = sizeof(m->address.sockaddr.in6);
+ } else
+ return -EAFNOSUPPORT;
+
+ return sendmsg_loop(m, &mh);
+}
+
+/* rfc3339 timestamp format: yyyy-mm-ddthh:mm:ss[.frac]<+/->zz:zz */
+static void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size) {
+ char gm_buf[sizeof("+0530") + 1];
+ struct tm tm;
+ time_t t;
+
+ assert(header_time);
+
+ t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+ localtime_r(&t, &tm);
+
+ strftime(header_time, header_size, "%Y-%m-%dT%T", &tm);
+
+ /* add fractional part */
+ if (tv)
+ snprintf(header_time + strlen(header_time), header_size, ".%06ld", tv->tv_usec);
+
+ /* format the timezone according to RFC */
+ xstrftime(gm_buf, "%z", &tm);
+ snprintf(header_time + strlen(header_time), header_size, "%.3s:%.2s ", gm_buf, gm_buf + 3);
+}
+
+/* The Syslog Protocol RFC5424 format :
+ * <pri>version sp timestamp sp hostname sp app-name sp procid sp msgid sp [sd-id]s sp msg
+ */
+int manager_push_to_network(Manager *m,
+ const char *priority,
+ const char *facility,
+ const char *identifier,
+ const char *message,
+ const char *hostname,
+ const char *pid,
+ const struct timeval *tv) {
+ char header_priority[sizeof("< >1 ") + 1];
+ char header_time[FORMAT_TIMESTAMP_MAX];
+ uint16_t makepri, pri, fac;
+ struct iovec iov[13];
+ int n = 0;
+
+ assert(m);
+ assert(message);
+ assert(priority);
+ assert(facility);
+
+ pri = (uint16_t) strtoul(priority, NULL, 0);
+ fac = (uint16_t) strtoul(facility, NULL, 0);
+ makepri = (fac << 3) + pri;
+
+ /* First: priority field Second: Version '<pri>version' */
+ snprintf(header_priority, sizeof(header_priority), "<%i>%i ", makepri, RFC_5424_PROTOCOL);
+ IOVEC_SET_STRING(iov[n++], header_priority);
+
+ /* Third: timestamp */
+ format_rfc3339_timestamp(tv, header_time, sizeof(header_time));
+ IOVEC_SET_STRING(iov[n++], header_time);
+
+ /* Fourth: hostname */
+ if (hostname)
+ IOVEC_SET_STRING(iov[n++], hostname);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Fifth: identifier */
+ if (identifier)
+ IOVEC_SET_STRING(iov[n++], identifier);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Sixth: procid */
+ if (pid)
+ IOVEC_SET_STRING(iov[n++], pid);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Seventh: msgid */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Eighth: [structured-data] */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Ninth: message */
+ IOVEC_SET_STRING(iov[n++], message);
+
+ return network_send(m, iov, n);
+}
+
+void manager_close_network_socket(Manager *m) {
+ assert(m);
+
+ m->socket = safe_close(m->socket);
+}
+
+int manager_open_network_socket(Manager *m) {
+ const int ttl = 255;
+ const int one = 1;
+ int r;
+
+ assert(m);
+
+ if (!IN_SET(m->address.sockaddr.sa.sa_family, AF_INET, AF_INET6))
+ return -EAFNOSUPPORT;
+
+ m->socket = socket(m->address.sockaddr.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->socket < 0)
+ return -errno;
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return m->socket;
+
+ fail:
+ m->socket = safe_close(m->socket);
+ return r;
+}
diff --git a/src/journal-remote/journal-syslogd.c b/src/journal-remote/journal-syslogd.c
new file mode 100644
index 0000000..fd8e770
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.c
@@ -0,0 +1,217 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+#include "util.h"
+#include "build.h"
+#include "mkdir.h"
+#include "capability.h"
+#include "network-util.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+
+#define STATE_FILE "/var/lib/systemd/journal-syslogd/state"
+
+static const char *arg_cursor = NULL;
+static const char *arg_save_state = STATE_FILE;
+
+static int setup_cursor_state_file(Manager *m, uid_t uid, gid_t gid) {
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert(m);
+
+ r = mkdir_parents(m->state_file, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
+ m->state_file);
+
+ fd = open(m->state_file, O_RDWR|O_CLOEXEC, 0644);
+ if (fd >= 0) {
+
+ /* Try to fix the access mode, so that we can still
+ touch the file after dropping priviliges */
+ fchmod(fd, 0644);
+ fchown(fd, uid, gid);
+ } else
+ /* create stamp file with the compiled-in date */
+ return touch_file(m->state_file, true, USEC_INFINITY, uid, gid, 0644);
+
+ return 0;
+}
+
+static void help(void) {
+ printf("%s ..\n\n"
+ "Send journal events to a UDP multicast group in RFC 5424 syslog format.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --cursor=CURSOR Start at the specified cursor\n"
+ " --save-state[=FILE] Save uploaded cursors (default \n"
+ " " STATE_FILE ")\n"
+ " -h --help Show this help and exit\n"
+ " --version Print version string and exit\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_CURSOR,
+ ARG_SAVE_STATE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "cursor", required_argument, NULL, ARG_CURSOR },
+ { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch(c) {
+ case 'h':
+ help();
+ return 0 /* done */;
+
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0 /* done */;
+ case ARG_CURSOR:
+ if (arg_cursor) {
+ log_error("cannot use more than one --cursor/--after-cursor");
+ return -EINVAL;
+ }
+
+ arg_cursor = optarg;
+ break;
+ case ARG_SAVE_STATE:
+ arg_save_state = optarg ?: STATE_FILE;
+ break;
+
+ case '?':
+ log_error("Unknown option %s.", argv[optind-1]);
+ return -EINVAL;
+
+ case ':':
+ log_error("Missing argument to %s.", argv[optind-1]);
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option code.");
+ }
+
+
+ if (optind < argc) {
+ log_error("Input arguments make no sense with journal input.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ const char *user = "systemd-journal-syslog";
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ log_show_color(true);
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Cannot resolve user name %s: %m", user);
+ goto finish;
+ }
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ r = manager_new(&m, arg_save_state, arg_cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate manager: %m");
+ goto finish;
+ }
+
+ r = manager_parse_config_file(m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse configuration file: %m");
+ goto finish;
+ }
+
+ r = setup_cursor_state_file(m, uid, gid);
+ if (r < 0)
+ goto cleanup;
+
+ r = drop_privileges(uid, gid,
+ (1ULL << CAP_NET_ADMIN) |
+ (1ULL << CAP_NET_BIND_SERVICE) |
+ (1ULL << CAP_NET_BROADCAST));
+ if (r < 0)
+ goto finish;
+
+ log_debug("%s running as pid "PID_FMT,
+ program_invocation_short_name, getpid());
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing input...");
+
+ if (network_is_online()) {
+ r = manager_connect(m);
+ if (r < 0)
+ goto finish;
+ }
+
+ r = sd_event_loop(m->event);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ goto finish;
+ }
+
+ sd_event_get_exit_code(m->event, &r);
+
+ cleanup:
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down...");
+
+ finish:
+ return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/journal-remote/journal-syslogd.conf.in b/src/journal-remote/journal-syslogd.conf.in
new file mode 100644
index 0000000..b567a46
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.conf.in
@@ -0,0 +1,2 @@
+[Network]
+#Address=239.0.0.1:6000
diff --git a/units/systemd-journal-syslogd.service b/units/systemd-journal-syslogd.service
new file mode 100644
index 0000000..079fafa
--- /dev/null
+++ b/units/systemd-journal-syslogd.service
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# 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.
+[Unit]
+Description=Journal Syslog Multicast Daemon
+After=network.target
+
+[Service]
+ExecStart=/usr/lib/systemd/systemd-journal-syslogd
+PrivateTmp=yes
+PrivateDevices=yes
+WatchdogSec=20min
+
+[Install]
+WantedBy=multi-user.target
--
2.3.2
Lennart Poettering
2015-04-09 08:32:58 UTC
Permalink
On Wed, 18.03.15 11:00, Susant Sahani (***@redhat.com) wrote:

Sorry for the late review!
Post by Susant Sahani
+ <para><command>systemd-journal-syslogd</command> serves journal
+ events over the network. It multicasts journal event to Syslog RFC 5424 format.
+ </para>
The tool can also be used to unicast events, no? Maybe clarify that it
can do unicasting as well as multicasting, it's just a matter of
specifying the right target address, no?
Post by Susant Sahani
+static int update_cursor_state(Manager *m) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file || !m->last_cursor)
+ return 0;
+
+ f = fopen(m->state_file, "we");
+ if (!f)
+ goto finish;
I think this really should be written in "atomic" style, i.e. into a
temporary file first, that is then renamed into the actual state file
name. That way the state file is either the old or the new one, but
never half-written for other processes.

Our fopen_temporary() call helps wth this.
Post by Susant Sahani
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ m->last_cursor);
+
+ fflush(f);
+
+ if (ferror(f))
+ r = -errno;
This flusing and check should be done via fflush_and_check().
Post by Susant Sahani
+static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) {
+ Manager *m = userp;
+ int r;
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ if (m->event_journal_input) {
Hmm, why this check? Isn't this set anyway if we entered this event
handler function?
Post by Susant Sahani
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ assert_se(sigemptyset(&mask) == 0);
+ sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+ assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
We have sigprocmask_many() now for this (and shouild probably convert
all invocations like this to it...
Post by Susant Sahani
+
+ pri = (uint16_t) strtoul(priority, NULL, 0);
+ fac = (uint16_t) strtoul(facility, NULL, 0);
Hmm, I'd really like some error checking for this.

Also can we use safe_atou16() for this?
Post by Susant Sahani
+ r = setsockopt(m->socket, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
This is not needed is it?
Post by Susant Sahani
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
And this neither?
Post by Susant Sahani
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
This might be useful.

Looks pretty good already!

Lennart
--
Lennart Poettering, Red Hat
Susant Sahani
2015-04-09 18:14:26 UTC
Permalink
Post by Lennart Poettering
Sorry for the late review!
Thanks for the review .
Post by Lennart Poettering
Post by Susant Sahani
+ <para><command>systemd-journal-syslogd</command> serves journal
+ events over the network. It multicasts journal event to Syslog RFC 5424 format.
+ </para>
The tool can also be used to unicast events, no? Maybe clarify that it
can do unicasting as well as multicasting, it's just a matter of
specifying the right target address, no?
yes infact I tested with the unicast . added now .
Post by Lennart Poettering
Post by Susant Sahani
+static int update_cursor_state(Manager *m) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file || !m->last_cursor)
+ return 0;
+
+ f = fopen(m->state_file, "we");
+ if (!f)
+ goto finish;
I think this really should be written in "atomic" style, i.e. into a
temporary file first, that is then renamed into the actual state file
name. That way the state file is either the old or the new one, but
never half-written for other processes.
Our fopen_temporary() call helps wth this.
Ok
Post by Lennart Poettering
Post by Susant Sahani
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ m->last_cursor);
+
+ fflush(f);
+
+ if (ferror(f))
+ r = -errno;
This flusing and check should be done via fflush_and_check().
Added.
Post by Lennart Poettering
Post by Susant Sahani
+static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) {
+ Manager *m = userp;
+ int r;
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ if (m->event_journal_input) {
Hmm, why this check? Isn't this set anyway if we entered this event
handler function?
OK
Post by Lennart Poettering
Post by Susant Sahani
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ assert_se(sigemptyset(&mask) == 0);
+ sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+ assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
We have sigprocmask_many() now for this (and shouild probably convert
all invocations like this to it...
OK
Post by Lennart Poettering
Post by Susant Sahani
+
+ pri = (uint16_t) strtoul(priority, NULL, 0);
+ fac = (uint16_t) strtoul(facility, NULL, 0);
Hmm, I'd really like some error checking for this.
Also can we use safe_atou16() for this?
Yes doing now .
Post by Lennart Poettering
Post by Susant Sahani
+ r = setsockopt(m->socket, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
This is not needed is it?
Post by Susant Sahani
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
And this neither?
Removed both
Post by Lennart Poettering
Post by Susant Sahani
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
This might be useful.
Looks pretty good already!
Thanks !
Post by Lennart Poettering
Lennart
Susant
Susant Sahani
2015-04-09 18:13:15 UTC
Permalink
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.

V2: Address Zbigniew's comments
1. Rename binary systemd-journal-syslogd
2. Fixed up man and added example
3. Error code check sd_event_add_signal
4. remove +User=systemd-journal-network from service file
5. remove opterr=0
6. assignment into declaration of mh

V3: Address Lennart's comments
1. add unicast events in the man
2. use fopen_temporary and fflush_and_check().
3. remove if (m->event_journal_input) {
4. use sigprocmask_many
5. fix facility and priority
6. remove IP_MULTICAST_TTL and IP_PKTINFO
7. use safe_atoi
---
Makefile-man.am | 8 +
Makefile.am | 37 ++
man/systemd-journal-syslogd.service.xml | 84 +++++
man/systemd-journal-syslogd.xml | 156 ++++++++
src/journal-remote/journal-syslog-conf.c | 61 ++++
src/journal-remote/journal-syslog-conf.h | 39 ++
src/journal-remote/journal-syslog-gperf.gperf | 18 +
src/journal-remote/journal-syslog-manager.c | 501 ++++++++++++++++++++++++++
src/journal-remote/journal-syslog-manager.h | 70 ++++
src/journal-remote/journal-syslog-network.c | 201 +++++++++++
src/journal-remote/journal-syslogd.c | 217 +++++++++++
src/journal-remote/journal-syslogd.conf.in | 2 +
units/systemd-journal-syslogd.service | 18 +
13 files changed, 1412 insertions(+)
create mode 100644 man/systemd-journal-syslogd.service.xml
create mode 100644 man/systemd-journal-syslogd.xml
create mode 100644 src/journal-remote/journal-syslog-conf.c
create mode 100644 src/journal-remote/journal-syslog-conf.h
create mode 100644 src/journal-remote/journal-syslog-gperf.gperf
create mode 100644 src/journal-remote/journal-syslog-manager.c
create mode 100644 src/journal-remote/journal-syslog-manager.h
create mode 100644 src/journal-remote/journal-syslog-network.c
create mode 100644 src/journal-remote/journal-syslogd.c
create mode 100644 src/journal-remote/journal-syslogd.conf.in
create mode 100644 units/systemd-journal-syslogd.service

diff --git a/Makefile-man.am b/Makefile-man.am
index 2f3e5f2..437d488 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -1380,6 +1380,14 @@ man/systemd-journal-gatewayd.socket.html: man/systemd-journal-gatewayd.service.h

endif

+MANPAGES += \
+ man/systemd-journal-syslogd.service.8 \
+ man/systemd-journal-syslogd.8
+MANPAGES_ALIAS += \
+ man/systemd-journal-syslogd.8
+man/systemd-journal-syslogd.8: man/systemd-journal-syslogd.service.8
+man/systemd-journal-syslogd.html: man/systemd-journal-syslogd.service.html
+
if HAVE_MYHOSTNAME
MANPAGES += \
man/nss-myhostname.8
diff --git a/Makefile.am b/Makefile.am
index 0a57389..0b843ac 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4361,6 +4361,43 @@ EXTRA_DIST += \
src/journal-remote/journal-upload.conf.in
endif

+systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-manager.h \
+ src/journal-remote/journal-syslog-manager.c \
+ src/journal-remote/journal-syslog-conf.h \
+ src/journal-remote/journal-syslog-conf.c \
+ src/journal-remote/journal-syslog-network.c \
+ src/journal-remote/journal-syslogd.c
+
+nodist_systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-gperf.c
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslog-gperf.gperf
+
+CLEANFILES += \
+ src/journal-remote/journal-syslog-gperf.c
+
+systemd_journal_syslogd_LDADD = \
+ libsystemd-internal.la \
+ libsystemd-journal-internal.la \
+ libsystemd-shared.la
+
+rootlibexec_PROGRAMS += \
+ systemd-journal-syslogd
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-syslogd.service
+
+EXTRA_DIST += \
+ units/systemd-journal-syslogd.service.in
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-syslogd.conf
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslogd.conf.in
+
# using _CFLAGS = in the conditional below would suppress AM_CFLAGS
journalctl_CFLAGS = \
$(AM_CFLAGS)
diff --git a/man/systemd-journal-syslogd.service.xml b/man/systemd-journal-syslogd.service.xml
new file mode 100644
index 0000000..e837153
--- /dev/null
+++ b/man/systemd-journal-syslogd.service.xml
@@ -0,0 +1,84 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd.service" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-syslogd.service</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ <email>***@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd.service</refname>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Forward journal events using syslog network procotol</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-journal-syslogd.service</filename></para>
+ <cmdsynopsis>
+ <command>/usr/lib/systemd/systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-journal-syslogd</command> serves journal
+ events over the network. It unicasts and multicasts journal event to Syslog RFC 5424 format.
+ </para>
+
+ <para>The program is started by
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ . Use
+ <command>systemctl start systemd-journal-syslogd.service</command> to start
+ the service, and <command>systemctl enable systemd-journal-syslogd.service</command>
+ to have it started on boot.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd-journal-syslogd.xml b/man/systemd-journal-syslogd.xml
new file mode 100644
index 0000000..5497b2e
--- /dev/null
+++ b/man/systemd-journal-syslogd.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>systemd-journal-syslogd</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ <email>***@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Send journal messages over the network in RFC 5424 format</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">--save-state=<replaceable>state file</replaceable></arg>
+ <arg choice="opt" rep="norepeat">--cursor=<replaceable>journal cursor</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>systemd-journal-syslogd</command> will send journal
+ entries to the UDP Unicast and Multicast address . Unless
+ limited by one of the options specified below, all journal
+ entries accessible to the user the program is running as will be
+ sent, and then the program will wait and send new entries
+ as they become available.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Send entries from the location in the
+ journal specified by the passed cursor. This has the same
+ meaning as <option>--cursor</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--save-state</option><optional>=<replaceable>PATH</replaceable></optional></term>
+
+ <listitem><para>Send entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the cursor saved in file at <replaceable>PATH</replaceable>
+ (<filename>/var/lib/systemd/journal-syslogd/state</filename> by default).
+ After an entry is successfully uploaded, update this file
+ with the cursor of that entry.
+ </para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned; otherwise, a non-zero
+ failure code is returned.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>[Network] Section Options</title>
+
+ <para>The <literal>[Network]</literal> section only applies for
+ UDP multicast address and Port:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>Address=</varname></term>
+ <listitem><para>Controls whether log messages received by the
+ journal daemon shall be forwarded to a unicast UDP address or multicast UDP network
+ group in syslog RFC 5424 format.</para>
+
+ <para>The the address string format is similar to socket units. See
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=239.0.0.1:6000
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=192.168.8.101:514
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-syslogd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/journal-remote/journal-syslog-conf.c b/src/journal-remote/journal-syslog-conf.c
new file mode 100644
index 0000000..c357189
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.c
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "in-addr-util.h"
+#include "journal-syslog-conf.h"
+
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Manager *m = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = socket_address_parse(&m->address, rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Failed to parse address value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int manager_parse_config_file(Manager *m) {
+ assert(m);
+
+ return config_parse_many("/etc/systemd/journal-syslogd.conf",
+ CONF_DIRS_NULSTR("systemd/journal-syslogd.conf"),
+ "Network\0",
+ config_item_perf_lookup, journal_syslog_gperf_lookup,
+ false, m);
+}
diff --git a/src/journal-remote/journal-syslog-conf.h b/src/journal-remote/journal-syslog-conf.h
new file mode 100644
index 0000000..ca9ef05
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "in-addr-util.h"
+#include "conf-parser.h"
+#include "journal-syslog-manager.h"
+
+const struct ConfigPerfItem* journal_syslog_gperf_lookup(const char *key, unsigned length);
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata);
+int manager_parse_config_file(Manager *m);
diff --git a/src/journal-remote/journal-syslog-gperf.gperf b/src/journal-remote/journal-syslog-gperf.gperf
new file mode 100644
index 0000000..e0f364f
--- /dev/null
+++ b/src/journal-remote/journal-syslog-gperf.gperf
@@ -0,0 +1,18 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journal_syslog_gperf_hash
+%define lookup-function-name journal_syslog_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Network.Address, config_parse_syslog_broadcast_address, 0, 0
diff --git a/src/journal-remote/journal-syslog-manager.c b/src/journal-remote/journal-syslog-manager.c
new file mode 100644
index 0000000..1051f2d
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.c
@@ -0,0 +1,501 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "util.h"
+#include "socket-util.h"
+#include "conf-parser.h"
+#include "sd-daemon.h"
+#include "network-util.h"
+#include "capability.h"
+#include "mkdir.h"
+#include "fileio.h"
+#include "journal-internal.h"
+#include "journal-syslog-manager.h"
+
+#define JOURNAL_SEND_POLL_TIMEOUT (10 * USEC_PER_SEC)
+
+/* Default severity LOG_NOTICE */
+#define JOURNAL_DEFAULT_SEVERITY LOG_PRI(LOG_NOTICE)
+
+/* Default facility LOG_USER */
+#define JOURNAL_DEFAULT_FACILITY LOG_FAC(LOG_USER)
+
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+ size_t fl, nl;
+ void *buf;
+
+ assert(data);
+ assert(field);
+ assert(target);
+ assert(target_size);
+
+ fl = strlen(field);
+ if (length < fl)
+ return 0;
+
+ if (memcmp(data, field, fl))
+ return 0;
+
+ nl = length - fl;
+ buf = malloc(nl+1);
+ if (!buf)
+ return -ENOMEM;
+
+ memcpy(buf, (const char*) data + fl, nl);
+ ((char*)buf)[nl] = 0;
+
+ free(*target);
+ *target = buf;
+ *target_size = nl;
+
+ return 1;
+}
+
+static int manager_read_journal_input(Manager *m) {
+ _cleanup_free_ char *facility = NULL, *identifier = NULL,
+ *priority = NULL, *message = NULL, *pid = NULL,
+ *hostname = NULL;
+ int sev = JOURNAL_DEFAULT_SEVERITY;
+ int fac = JOURNAL_DEFAULT_FACILITY;
+ struct timeval tv;
+ usec_t realtime;
+ const void *data;
+ size_t length;
+ size_t n = 0;
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ JOURNAL_FOREACH_DATA_RETVAL(m->journal, data, length, r) {
+
+ r = parse_field(data, length, "PRIORITY=", &priority, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_FACILITY=", &facility, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_HOSTNAME=", &hostname, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_PID=", &pid, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "MESSAGE=", &message, &n);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_journal_get_realtime_usec(m->journal, &realtime);
+ if (r < 0)
+ log_warning_errno(r, "Failed to rerieve realtime from journal: %m");
+ else {
+ tv.tv_sec = realtime / USEC_PER_SEC;
+ tv.tv_usec = realtime % USEC_PER_SEC;
+ }
+
+ if (facility) {
+ r = safe_atoi(facility, &fac);
+ if (r < 0)
+ log_debug("Failed to parse syslog facility: %s", facility);
+
+ if (fac < LOG_KERN || fac >= LOG_NFACILITIES)
+ fac = JOURNAL_DEFAULT_FACILITY;
+ }
+
+ if (priority) {
+ r = safe_atoi(priority, &sev);
+ if (r < 0)
+ log_debug("Failed to parse syslog priority: %s", priority);
+
+ if (sev < LOG_EMERG || sev > LOG_DEBUG)
+ sev = JOURNAL_DEFAULT_SEVERITY;
+ }
+
+ return manager_push_to_network(m, sev, fac, identifier,
+ message, hostname, pid, r >= 0 ? &tv : NULL);
+}
+
+static int update_cursor_state(Manager *m) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file || !m->last_cursor)
+ return 0;
+
+ r = fopen_temporary(m->state_file, &f, &temp_path);
+ if (r < 0)
+ goto finish;
+
+ fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ m->last_cursor);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto finish;
+
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(temp_path);
+ temp_path = NULL;
+
+ finish:
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
+ return r;
+}
+
+static int load_cursor_state(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (!m->state_file)
+ return 0;
+
+ r = parse_env_file(m->state_file, NEWLINE, "LAST_CURSOR", &m->last_cursor, NULL);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ log_debug("Last cursor was %s.", m->last_cursor ? m->last_cursor : "Not available");
+
+ return 0;
+}
+
+static int process_journal_input(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ while (true) {
+ r = sd_journal_next(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get next entry: %m");
+ return r;
+ }
+
+ if (r == 0)
+ break;
+
+ r = manager_read_journal_input(m);
+ if (r < 0) {
+ /* Can't send the message. Seek one entry back. */
+ r = sd_journal_previous(m->journal);
+ if (r < 0)
+ log_error_errno(r, "Failed to iterate through journal: %m");
+
+ break;
+ }
+ }
+
+ r = sd_journal_get_cursor(m->journal, &m->current_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ free(m->last_cursor);
+ m->last_cursor = m->current_cursor;
+ m->current_cursor = NULL;
+
+ return update_cursor_state(m);
+}
+
+static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) {
+ Manager *m = userp;
+ int r;
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ r = sd_journal_process(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process journal: %m");
+ manager_disconnect(m);
+ return r;
+ }
+
+ if (r == SD_JOURNAL_NOP)
+ return 0;
+
+ return process_journal_input(m);
+}
+
+static void close_journal_input(Manager *m) {
+ assert(m);
+
+ if (m->journal) {
+ log_debug("Closing journal input.");
+
+ sd_journal_close(m->journal);
+ m->journal = NULL;
+ }
+
+ m->timeout = 0;
+}
+
+static int manager_signal_event_handler(sd_event_source *event, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+
+ assert(m);
+
+ log_received_signal(LOG_INFO, si);
+
+ manager_disconnect(m);
+
+ sd_event_exit(m->event, 0);
+
+ return 0;
+}
+
+static int manager_journal_monitor_listen(Manager *m) {
+ int r, events;
+
+ assert(m);
+
+ r = sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open journal: %m");
+ return r;
+ }
+
+ sd_journal_set_data_threshold(m->journal, 0);
+
+ m->journal_watch_fd = sd_journal_get_fd(m->journal);
+ if (m->journal_watch_fd < 0)
+ return log_error_errno(m->journal_watch_fd, "sd_journal_get_fd failed: %m");
+
+ events = sd_journal_get_events(m->journal);
+
+ r = sd_journal_reliable_fd(m->journal);
+ assert(r >= 0);
+ if (r > 0)
+ m->timeout = -1;
+ else
+ m->timeout = JOURNAL_SEND_POLL_TIMEOUT;
+
+ r = sd_event_add_io(m->event, &m->event_journal_input ,
+ m->journal_watch_fd , events, manager_journal_event_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register input event: %m");
+
+ /* ignore failure */
+ if (!m->last_cursor)
+ (void) load_cursor_state(m);
+
+ if (m->last_cursor) {
+ r = sd_journal_seek_cursor(m->journal, m->last_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to cursor %s: %m",
+ m->last_cursor);
+ }
+
+ return 0;
+}
+
+int manager_connect(Manager *m) {
+ int r;
+
+ assert(m);
+
+ manager_disconnect(m);
+
+ r = manager_open_network_socket(m);
+ if (r < 0)
+ return r;
+
+ r = manager_journal_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_disconnect(Manager *m) {
+ assert(m);
+
+ close_journal_input(m);
+
+ manager_close_network_socket(m);
+
+ m->event_journal_input = sd_event_source_unref(m->event_journal_input);
+
+ sd_notifyf(false, "STATUS=Idle.");
+}
+
+static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *m = userdata;
+ bool connected, online;
+ int r;
+
+ assert(m);
+
+ sd_network_monitor_flush(m->network_monitor);
+
+ /* check if the machine is online */
+ online = network_is_online();
+
+ /* check if the socket is currently open*/
+ connected = m->socket >= 0;
+
+ if (connected && !online) {
+ log_info("No network connectivity, watching for changes.");
+ manager_disconnect(m);
+
+ } else if (!connected && online) {
+ log_info("Network configuration changed, trying to establish connection.");
+
+ r = manager_connect(m);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int manager_network_monitor_listen(Manager *m) {
+ int r, fd, events;
+
+ assert(m);
+
+ r = sd_network_monitor_new(&m->network_monitor, NULL);
+ if (r < 0)
+ return r;
+
+ fd = sd_network_monitor_get_fd(m->network_monitor);
+ if (fd < 0)
+ return fd;
+
+ events = sd_network_monitor_get_events(m->network_monitor);
+ if (events < 0)
+ return events;
+
+ r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_free(Manager *m) {
+ if (!m)
+ return;
+
+ manager_disconnect(m);
+
+ free(m->last_cursor);
+ free(m->current_cursor);
+
+ free(m->state_file);
+
+ sd_event_source_unref(m->network_event_source);
+ sd_network_monitor_unref(m->network_monitor);
+
+ sd_event_source_unref(m->sigterm_event);
+ sd_event_source_unref(m->sigint_event);
+
+ sd_event_unref(m->event);
+
+ free(m);
+}
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ assert(ret);
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ m->socket = m->journal_watch_fd = -1;
+
+ m->state_file = strdup(state_file);
+ if (!m->state_file)
+ return -ENOMEM;
+
+ if (cursor) {
+ m->last_cursor = strdup(cursor);
+ if (!m->last_cursor)
+ return -ENOMEM;
+ }
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
+
+ r = sd_event_add_signal(m->event, NULL, SIGTERM, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGINT, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ sd_event_set_watchdog(m->event, true);
+
+ r = manager_network_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ *ret = m;
+ m = NULL;
+
+ return 0;
+}
diff --git a/src/journal-remote/journal-syslog-manager.h b/src/journal-remote/journal-syslog-manager.h
new file mode 100644
index 0000000..aeb553b
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.h
@@ -0,0 +1,70 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "sd-event.h"
+#include "sd-network.h"
+#include "socket-util.h"
+#include "sd-journal.h"
+
+typedef struct Manager Manager;
+
+struct Manager {
+ sd_event *event;
+ sd_event_source *event_journal_input;
+ uint64_t timeout;
+
+ sd_event_source *sigint_event, *sigterm_event;
+
+ /* network */
+ sd_event_source *network_event_source;
+ sd_network_monitor *network_monitor;
+
+ int socket;
+
+ /* Multicast UDP address */
+ SocketAddress address;
+
+ /* journal */
+ int journal_watch_fd;
+ sd_journal *journal;
+
+ char *state_file;
+
+ char *last_cursor, *current_cursor;
+};
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor);
+void manager_free(Manager *m);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+int manager_connect(Manager *m);
+void manager_disconnect(Manager *m);
+
+void manager_close_network_socket(Manager *m);
+int manager_open_network_socket(Manager *m);
+
+int manager_push_to_network(Manager *m, int severity, int facility,
+ const char *identifier, const char *message,
+ const char *hostname, const char *pid,
+ const struct timeval *tv);
diff --git a/src/journal-remote/journal-syslog-network.c b/src/journal-remote/journal-syslog-network.c
new file mode 100644
index 0000000..26ac362
--- /dev/null
+++ b/src/journal-remote/journal-syslog-network.c
@@ -0,0 +1,201 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <unistd.h>
+#include <stddef.h>
+#include <poll.h>
+
+#include "journal-syslog-manager.h"
+
+#define RFC_5424_NILVALUE "-"
+#define RFC_5424_PROTOCOL 1
+
+#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC)
+
+static int sendmsg_loop(Manager *m, struct msghdr *mh) {
+ int r;
+
+ assert(m);
+ assert(mh);
+
+ for (;;) {
+ if (sendmsg(m->socket, mh, MSG_NOSIGNAL) >= 0)
+ return 0;
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno != EAGAIN)
+ return -errno;
+
+ r = fd_wait_for_event(m->socket, POLLOUT, SEND_TIMEOUT_USEC);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
+ mh.msg_namelen = sizeof(m->address.sockaddr.in6);
+ } else
+ return -EAFNOSUPPORT;
+
+ return sendmsg_loop(m, &mh);
+}
+
+/* rfc3339 timestamp format: yyyy-mm-ddthh:mm:ss[.frac]<+/->zz:zz */
+static void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size) {
+ char gm_buf[sizeof("+0530") + 1];
+ struct tm tm;
+ time_t t;
+
+ assert(header_time);
+
+ t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+ localtime_r(&t, &tm);
+
+ strftime(header_time, header_size, "%Y-%m-%dT%T", &tm);
+
+ /* add fractional part */
+ if (tv)
+ snprintf(header_time + strlen(header_time), header_size, ".%06ld", tv->tv_usec);
+
+ /* format the timezone according to RFC */
+ xstrftime(gm_buf, "%z", &tm);
+ snprintf(header_time + strlen(header_time), header_size, "%.3s:%.2s ", gm_buf, gm_buf + 3);
+}
+
+/* The Syslog Protocol RFC5424 format :
+ * <pri>version sp timestamp sp hostname sp app-name sp procid sp msgid sp [sd-id]s sp msg
+ */
+int manager_push_to_network(Manager *m,
+ int severity,
+ int facility,
+ const char *identifier,
+ const char *message,
+ const char *hostname,
+ const char *pid,
+ const struct timeval *tv) {
+ char header_priority[sizeof("< >1 ") + 1];
+ char header_time[FORMAT_TIMESTAMP_MAX];
+ uint16_t makepri;
+ struct iovec iov[13];
+ int n = 0;
+
+ assert(m);
+ assert(message);
+
+ makepri = (facility << 3) + severity;
+
+ /* First: priority field Second: Version '<pri>version' */
+ snprintf(header_priority, sizeof(header_priority), "<%i>%i ", makepri, RFC_5424_PROTOCOL);
+ IOVEC_SET_STRING(iov[n++], header_priority);
+
+ /* Third: timestamp */
+ format_rfc3339_timestamp(tv, header_time, sizeof(header_time));
+ IOVEC_SET_STRING(iov[n++], header_time);
+
+ /* Fourth: hostname */
+ if (hostname)
+ IOVEC_SET_STRING(iov[n++], hostname);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Fifth: identifier */
+ if (identifier)
+ IOVEC_SET_STRING(iov[n++], identifier);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Sixth: procid */
+ if (pid)
+ IOVEC_SET_STRING(iov[n++], pid);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Seventh: msgid */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Eighth: [structured-data] */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Ninth: message */
+ IOVEC_SET_STRING(iov[n++], message);
+
+ return network_send(m, iov, n);
+}
+
+void manager_close_network_socket(Manager *m) {
+ assert(m);
+
+ m->socket = safe_close(m->socket);
+}
+
+int manager_open_network_socket(Manager *m) {
+ const int one = 1;
+ int r;
+
+ assert(m);
+
+ if (!IN_SET(m->address.sockaddr.sa.sa_family, AF_INET, AF_INET6))
+ return -EAFNOSUPPORT;
+
+ m->socket = socket(m->address.sockaddr.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->socket < 0)
+ return -errno;
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return m->socket;
+
+ fail:
+ m->socket = safe_close(m->socket);
+ return r;
+}
diff --git a/src/journal-remote/journal-syslogd.c b/src/journal-remote/journal-syslogd.c
new file mode 100644
index 0000000..fd8e770
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.c
@@ -0,0 +1,217 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+#include "util.h"
+#include "build.h"
+#include "mkdir.h"
+#include "capability.h"
+#include "network-util.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+
+#define STATE_FILE "/var/lib/systemd/journal-syslogd/state"
+
+static const char *arg_cursor = NULL;
+static const char *arg_save_state = STATE_FILE;
+
+static int setup_cursor_state_file(Manager *m, uid_t uid, gid_t gid) {
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert(m);
+
+ r = mkdir_parents(m->state_file, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
+ m->state_file);
+
+ fd = open(m->state_file, O_RDWR|O_CLOEXEC, 0644);
+ if (fd >= 0) {
+
+ /* Try to fix the access mode, so that we can still
+ touch the file after dropping priviliges */
+ fchmod(fd, 0644);
+ fchown(fd, uid, gid);
+ } else
+ /* create stamp file with the compiled-in date */
+ return touch_file(m->state_file, true, USEC_INFINITY, uid, gid, 0644);
+
+ return 0;
+}
+
+static void help(void) {
+ printf("%s ..\n\n"
+ "Send journal events to a UDP multicast group in RFC 5424 syslog format.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --cursor=CURSOR Start at the specified cursor\n"
+ " --save-state[=FILE] Save uploaded cursors (default \n"
+ " " STATE_FILE ")\n"
+ " -h --help Show this help and exit\n"
+ " --version Print version string and exit\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_CURSOR,
+ ARG_SAVE_STATE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "cursor", required_argument, NULL, ARG_CURSOR },
+ { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch(c) {
+ case 'h':
+ help();
+ return 0 /* done */;
+
+ case ARG_VERSION:
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0 /* done */;
+ case ARG_CURSOR:
+ if (arg_cursor) {
+ log_error("cannot use more than one --cursor/--after-cursor");
+ return -EINVAL;
+ }
+
+ arg_cursor = optarg;
+ break;
+ case ARG_SAVE_STATE:
+ arg_save_state = optarg ?: STATE_FILE;
+ break;
+
+ case '?':
+ log_error("Unknown option %s.", argv[optind-1]);
+ return -EINVAL;
+
+ case ':':
+ log_error("Missing argument to %s.", argv[optind-1]);
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option code.");
+ }
+
+
+ if (optind < argc) {
+ log_error("Input arguments make no sense with journal input.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ const char *user = "systemd-journal-syslog";
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ log_show_color(true);
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Cannot resolve user name %s: %m", user);
+ goto finish;
+ }
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ r = manager_new(&m, arg_save_state, arg_cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate manager: %m");
+ goto finish;
+ }
+
+ r = manager_parse_config_file(m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse configuration file: %m");
+ goto finish;
+ }
+
+ r = setup_cursor_state_file(m, uid, gid);
+ if (r < 0)
+ goto cleanup;
+
+ r = drop_privileges(uid, gid,
+ (1ULL << CAP_NET_ADMIN) |
+ (1ULL << CAP_NET_BIND_SERVICE) |
+ (1ULL << CAP_NET_BROADCAST));
+ if (r < 0)
+ goto finish;
+
+ log_debug("%s running as pid "PID_FMT,
+ program_invocation_short_name, getpid());
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing input...");
+
+ if (network_is_online()) {
+ r = manager_connect(m);
+ if (r < 0)
+ goto finish;
+ }
+
+ r = sd_event_loop(m->event);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ goto finish;
+ }
+
+ sd_event_get_exit_code(m->event, &r);
+
+ cleanup:
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down...");
+
+ finish:
+ return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/journal-remote/journal-syslogd.conf.in b/src/journal-remote/journal-syslogd.conf.in
new file mode 100644
index 0000000..b567a46
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.conf.in
@@ -0,0 +1,2 @@
+[Network]
+#Address=239.0.0.1:6000
diff --git a/units/systemd-journal-syslogd.service b/units/systemd-journal-syslogd.service
new file mode 100644
index 0000000..6e8bfc5
--- /dev/null
+++ b/units/systemd-journal-syslogd.service
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# 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.
+[Unit]
+Description=Journal Syslog Unicast and Multicast Daemon
+After=network.target
+
+[Service]
+ExecStart=/usr/lib/systemd/systemd-journal-syslogd
+PrivateTmp=yes
+PrivateDevices=yes
+WatchdogSec=20min
+
+[Install]
+WantedBy=multi-user.target
--
2.3.5
Lennart Poettering
2015-04-10 10:45:34 UTC
Permalink
Post by Susant Sahani
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.
Hmm, before we merge this, one more thing: I think we need a more
explanatory name for this. If we call it "systemd-journal-syslogd",
then people might assume this might be invovled with or necessary for
systemd *reading* syslog traffic. However, that's not what it does, it
only *sends* syslog traffic. Hence maybe call it
"systemd-journal-syslog-emitd" or so?

Also, how can we tap into the multicast traffic for this? Is there any
better tool for this around, that is commonly available and used?

How did you test this?
Post by Susant Sahani
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(temp_path);
+ temp_path = NULL;
+
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
The failure path should remove the file temp_path here. Add something like:

if (temp_path)
(void) unlink(temp_path);
Post by Susant Sahani
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
This actually looks wrong? This should be
sizeof(m->address.sockaddr.in)?
Post by Susant Sahani
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
And this doesn't look right either, this should be
&m->address.sockaddr.sa, otherwise this should generate a type error,
no?

Lennart
--
Lennart Poettering, Red Hat
Zbigniew Jędrzejewski-Szmek
2015-04-14 04:35:49 UTC
Permalink
Post by Lennart Poettering
Post by Susant Sahani
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.
Hmm, before we merge this, one more thing: I think we need a more
explanatory name for this. If we call it "systemd-journal-syslogd",
then people might assume this might be invovled with or necessary for
systemd *reading* syslog traffic. However, that's not what it does, it
only *sends* syslog traffic. Hence maybe call it
"systemd-journal-syslog-emitd" or so?
Yeah, I have to agree very much here: "journal-syslogd" name is too similar
to "syslogd", the original syslog daemon, and is bound to cause endless confusion.
Maybe we should go for journal-netlogd, reminiscent of netconsole,
which is actually a similar thing. s-j-s-emitd is a bit too long.

I see some typos in the patch... I'll reply separately.

Zbyszek
Post by Lennart Poettering
Also, how can we tap into the multicast traffic for this? Is there any
better tool for this around, that is commonly available and used?
How did you test this?
Post by Susant Sahani
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(temp_path);
+ temp_path = NULL;
+
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
if (temp_path)
(void) unlink(temp_path);
Post by Susant Sahani
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
This actually looks wrong? This should be
sizeof(m->address.sockaddr.in)?
Post by Susant Sahani
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
And this doesn't look right either, this should be
&m->address.sockaddr.sa, otherwise this should generate a type error,
no?
Lennart
--
Lennart Poettering, Red Hat
Susant Sahani
2015-04-16 04:28:31 UTC
Permalink
Post by Lennart Poettering
Post by Susant Sahani
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.
Hmm, before we merge this, one more thing: I think we need a more
explanatory name for this. If we call it "systemd-journal-syslogd",
then people might assume this might be invovled with or necessary for
systemd *reading* syslog traffic. However, that's not what it does, it
only *sends* syslog traffic. Hence maybe call it
"systemd-journal-syslog-emitd" or so?
Sorry for the late reply. Yes this would be a confusion.
systemd-journal-syslog-emitd would be bit long :). As zbignew suggested
journal-netlogd sounds OK .
Post by Lennart Poettering
Also, how can we tap into the multicast traffic for this? Is there any
better tool for this around, that is commonly available and used?
How did you test this?
We need to test whether the formatting of message is correct i.e. can be
parsed by any RFC 5424 parser such as rsyslog. I tested it using rsyslog
imudp . If we capture the traffic of these UDP messages via wireshark ,
these messages are getting parsed .
Post by Lennart Poettering
Post by Susant Sahani
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(temp_path);
+ temp_path = NULL;
+
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
if (temp_path)
(void) unlink(temp_path);
Post by Susant Sahani
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
This actually looks wrong? This should be
sizeof(m->address.sockaddr.in)?
Post by Susant Sahani
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
And this doesn't look right either, this should be
&m->address.sockaddr.sa, otherwise this should generate a type error,
no?
Will fix these in the new version . thanks !
Post by Lennart Poettering
Lennart
Susant
Zbigniew Jędrzejewski-Szmek
2015-04-14 05:16:14 UTC
Permalink
Post by Susant Sahani
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.
V2: Address Zbigniew's comments
1. Rename binary systemd-journal-syslogd
2. Fixed up man and added example
3. Error code check sd_event_add_signal
4. remove +User=systemd-journal-network from service file
5. remove opterr=0
6. assignment into declaration of mh
V3: Address Lennart's comments
1. add unicast events in the man
2. use fopen_temporary and fflush_and_check().
3. remove if (m->event_journal_input) {
4. use sigprocmask_many
5. fix facility and priority
6. remove IP_MULTICAST_TTL and IP_PKTINFO
7. use safe_atoi
---
Makefile-man.am | 8 +
Makefile.am | 37 ++
man/systemd-journal-syslogd.service.xml | 84 +++++
man/systemd-journal-syslogd.xml | 156 ++++++++
src/journal-remote/journal-syslog-conf.c | 61 ++++
src/journal-remote/journal-syslog-conf.h | 39 ++
src/journal-remote/journal-syslog-gperf.gperf | 18 +
src/journal-remote/journal-syslog-manager.c | 501 ++++++++++++++++++++++++++
src/journal-remote/journal-syslog-manager.h | 70 ++++
src/journal-remote/journal-syslog-network.c | 201 +++++++++++
src/journal-remote/journal-syslogd.c | 217 +++++++++++
src/journal-remote/journal-syslogd.conf.in | 2 +
units/systemd-journal-syslogd.service | 18 +
13 files changed, 1412 insertions(+)
create mode 100644 man/systemd-journal-syslogd.service.xml
create mode 100644 man/systemd-journal-syslogd.xml
create mode 100644 src/journal-remote/journal-syslog-conf.c
create mode 100644 src/journal-remote/journal-syslog-conf.h
create mode 100644 src/journal-remote/journal-syslog-gperf.gperf
create mode 100644 src/journal-remote/journal-syslog-manager.c
create mode 100644 src/journal-remote/journal-syslog-manager.h
create mode 100644 src/journal-remote/journal-syslog-network.c
create mode 100644 src/journal-remote/journal-syslogd.c
create mode 100644 src/journal-remote/journal-syslogd.conf.in
create mode 100644 units/systemd-journal-syslogd.service
diff --git a/Makefile-man.am b/Makefile-man.am
index 2f3e5f2..437d488 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -1380,6 +1380,14 @@ man/systemd-journal-gatewayd.socket.html: man/systemd-journal-gatewayd.service.h
endif
+MANPAGES += \
+ man/systemd-journal-syslogd.service.8 \
+ man/systemd-journal-syslogd.8
+MANPAGES_ALIAS += \
+ man/systemd-journal-syslogd.8
+man/systemd-journal-syslogd.8: man/systemd-journal-syslogd.service.8
+man/systemd-journal-syslogd.html: man/systemd-journal-syslogd.service.html
+
if HAVE_MYHOSTNAME
MANPAGES += \
man/nss-myhostname.8
diff --git a/Makefile.am b/Makefile.am
index 0a57389..0b843ac 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4361,6 +4361,43 @@ EXTRA_DIST += \
src/journal-remote/journal-upload.conf.in
endif
+systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-manager.h \
+ src/journal-remote/journal-syslog-manager.c \
+ src/journal-remote/journal-syslog-conf.h \
+ src/journal-remote/journal-syslog-conf.c \
+ src/journal-remote/journal-syslog-network.c \
+ src/journal-remote/journal-syslogd.c
+
+nodist_systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-gperf.c
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslog-gperf.gperf
+
+CLEANFILES += \
+ src/journal-remote/journal-syslog-gperf.c
+
+systemd_journal_syslogd_LDADD = \
+ libsystemd-internal.la \
+ libsystemd-journal-internal.la \
+ libsystemd-shared.la
+
+rootlibexec_PROGRAMS += \
+ systemd-journal-syslogd
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-syslogd.service
+
+EXTRA_DIST += \
+ units/systemd-journal-syslogd.service.in
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-syslogd.conf
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslogd.conf.in
+
# using _CFLAGS = in the conditional below would suppress AM_CFLAGS
journalctl_CFLAGS = \
$(AM_CFLAGS)
diff --git a/man/systemd-journal-syslogd.service.xml b/man/systemd-journal-syslogd.service.xml
new file mode 100644
index 0000000..e837153
--- /dev/null
+++ b/man/systemd-journal-syslogd.service.xml
@@ -0,0 +1,84 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd.service" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-syslogd.service</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd.service</refname>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Forward journal events using syslog network procotol</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-journal-syslogd.service</filename></para>
+ <cmdsynopsis>
+ <command>/usr/lib/systemd/systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-journal-syslogd</command> serves journal
+ events over the network. It unicasts and multicasts journal event to Syslog RFC 5424 format.
"serves" is bad, because it implies clients connecting to it. Maybe something like this:

<command>systemd-journal-syslogd</command> forwards messages from the
journal to other hosts over the network using the traditional syslog
format following <ulink url="">RFC 5425</>. It can be configured to
send messages to both unicast and multicast addresses.
Post by Susant Sahani
+ </para>
+
+ <para>The program is started by
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ . Use
The dot should go right after ">". Otherwise there'll be extra spaces in the
formatted output.

Why is there a separate man page for systemd-journal-syslogd? The one
for systemd-journal-syslogd should be enough, it is not indended to be run
standalone. There's useful stuff in the other one, please merge them!
There should be two man pages: one for the binary and the service,
and the other one for the config file.

systemd-journal-syslogd -h does not work, it fails if the user name
cannot be resolved. But --help should work regardless.
Post by Susant Sahani
+ <command>systemctl start systemd-journal-syslogd.service</command> to start
+ the service, and <command>systemctl enable systemd-journal-syslogd.service</command>
+ to have it started on boot.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
Trailing comma should be a dot.
Post by Susant Sahani
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd-journal-syslogd.xml b/man/systemd-journal-syslogd.xml
new file mode 100644
index 0000000..5497b2e
--- /dev/null
+++ b/man/systemd-journal-syslogd.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>systemd-journal-syslogd</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Send journal messages over the network in RFC 5424 format</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">--save-state=<replaceable>state file</replaceable></arg>
+ <arg choice="opt" rep="norepeat">--cursor=<replaceable>journal cursor</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>systemd-journal-syslogd</command> will send journal
+ entries to the UDP Unicast and Multicast address . Unless
+ limited by one of the options specified below, all journal
+ entries accessible to the user the program is running as will be
+ sent, and then the program will wait and send new entries
+ as they become available.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Send entries from the location in the
+ journal specified by the passed cursor. This has the same
+ meaning as <option>--cursor</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--save-state</option><optional>=<replaceable>PATH</replaceable></optional></term>
+
+ <listitem><para>Send entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the cursor saved in file at <replaceable>PATH</replaceable>
+ (<filename>/var/lib/systemd/journal-syslogd/state</filename> by default).
+ After an entry is successfully uploaded, update this file
+ with the cursor of that entry.
+ </para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned; otherwise, a non-zero
+ failure code is returned.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>[Network] Section Options</title>
+
+ <para>The <literal>[Network]</literal> section only applies for
+ UDP multicast address and Port:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>Address=</varname></term>
+ <listitem><para>Controls whether log messages received by the
+ journal daemon shall be forwarded to a unicast UDP address or multicast UDP network
+ group in syslog RFC 5424 format.</para>
+
+ <para>The the address string format is similar to socket units. See
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=239.0.0.1:6000
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=192.168.8.101:514
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-syslogd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/journal-remote/journal-syslog-conf.c b/src/journal-remote/journal-syslog-conf.c
new file mode 100644
index 0000000..c357189
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.c
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "in-addr-util.h"
+#include "journal-syslog-conf.h"
+
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Manager *m = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = socket_address_parse(&m->address, rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Failed to parse address value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int manager_parse_config_file(Manager *m) {
+ assert(m);
+
+ return config_parse_many("/etc/systemd/journal-syslogd.conf",
+ CONF_DIRS_NULSTR("systemd/journal-syslogd.conf"),
+ "Network\0",
+ config_item_perf_lookup, journal_syslog_gperf_lookup,
+ false, m);
+}
diff --git a/src/journal-remote/journal-syslog-conf.h b/src/journal-remote/journal-syslog-conf.h
new file mode 100644
index 0000000..ca9ef05
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "in-addr-util.h"
+#include "conf-parser.h"
+#include "journal-syslog-manager.h"
+
+const struct ConfigPerfItem* journal_syslog_gperf_lookup(const char *key, unsigned length);
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata);
+int manager_parse_config_file(Manager *m);
diff --git a/src/journal-remote/journal-syslog-gperf.gperf b/src/journal-remote/journal-syslog-gperf.gperf
new file mode 100644
index 0000000..e0f364f
--- /dev/null
+++ b/src/journal-remote/journal-syslog-gperf.gperf
@@ -0,0 +1,18 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journal_syslog_gperf_hash
+%define lookup-function-name journal_syslog_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Network.Address, config_parse_syslog_broadcast_address, 0, 0
diff --git a/src/journal-remote/journal-syslog-manager.c b/src/journal-remote/journal-syslog-manager.c
new file mode 100644
index 0000000..1051f2d
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.c
@@ -0,0 +1,501 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "util.h"
+#include "socket-util.h"
+#include "conf-parser.h"
+#include "sd-daemon.h"
+#include "network-util.h"
+#include "capability.h"
+#include "mkdir.h"
+#include "fileio.h"
+#include "journal-internal.h"
+#include "journal-syslog-manager.h"
+
+#define JOURNAL_SEND_POLL_TIMEOUT (10 * USEC_PER_SEC)
+
+/* Default severity LOG_NOTICE */
+#define JOURNAL_DEFAULT_SEVERITY LOG_PRI(LOG_NOTICE)
+
+/* Default facility LOG_USER */
+#define JOURNAL_DEFAULT_FACILITY LOG_FAC(LOG_USER)
+
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+ size_t fl, nl;
+ void *buf;
+
+ assert(data);
+ assert(field);
+ assert(target);
+ assert(target_size);
+
+ fl = strlen(field);
+ if (length < fl)
+ return 0;
+
+ if (memcmp(data, field, fl))
+ return 0;
+
+ nl = length - fl;
+ buf = malloc(nl+1);
+ if (!buf)
+ return -ENOMEM;
+
+ memcpy(buf, (const char*) data + fl, nl);
+ ((char*)buf)[nl] = 0;
+
+ free(*target);
+ *target = buf;
+ *target_size = nl;
+
+ return 1;
+}
+
+static int manager_read_journal_input(Manager *m) {
+ _cleanup_free_ char *facility = NULL, *identifier = NULL,
+ *priority = NULL, *message = NULL, *pid = NULL,
+ *hostname = NULL;
+ int sev = JOURNAL_DEFAULT_SEVERITY;
+ int fac = JOURNAL_DEFAULT_FACILITY;
+ struct timeval tv;
+ usec_t realtime;
+ const void *data;
+ size_t length;
+ size_t n = 0;
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ JOURNAL_FOREACH_DATA_RETVAL(m->journal, data, length, r) {
+
+ r = parse_field(data, length, "PRIORITY=", &priority, &n);
Is n used for anything?
Post by Susant Sahani
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_FACILITY=", &facility, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_HOSTNAME=", &hostname, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_PID=", &pid, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "MESSAGE=", &message, &n);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_journal_get_realtime_usec(m->journal, &realtime);
+ if (r < 0)
+ log_warning_errno(r, "Failed to rerieve realtime from journal: %m");
+ else {
+ tv.tv_sec = realtime / USEC_PER_SEC;
+ tv.tv_usec = realtime % USEC_PER_SEC;
+ }
+
+ if (facility) {
+ r = safe_atoi(facility, &fac);
safe_atou?
Post by Susant Sahani
+ if (r < 0)
+ log_debug("Failed to parse syslog facility: %s", facility);
+
+ if (fac < LOG_KERN || fac >= LOG_NFACILITIES)
+ fac = JOURNAL_DEFAULT_FACILITY;
+ }
+
+ if (priority) {
+ r = safe_atoi(priority, &sev);
safe_atou?
Post by Susant Sahani
+ if (r < 0)
+ log_debug("Failed to parse syslog priority: %s", priority);
+
+ if (sev < LOG_EMERG || sev > LOG_DEBUG)
+ sev = JOURNAL_DEFAULT_SEVERITY;
+ }
+
+ return manager_push_to_network(m, sev, fac, identifier,
+ message, hostname, pid, r >= 0 ? &tv : NULL);
+}
+
+static int update_cursor_state(Manager *m) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file || !m->last_cursor)
+ return 0;
+
+ r = fopen_temporary(m->state_file, &f, &temp_path);
+ if (r < 0)
+ goto finish;
+
+ fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ m->last_cursor);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto finish;
+
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(temp_path);
+ temp_path = NULL;
Why is this freed here? Shouldn't it always be freed, in the error path too?
Post by Susant Sahani
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
+ return r;
+}
+
+static int load_cursor_state(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (!m->state_file)
+ return 0;
+
+ r = parse_env_file(m->state_file, NEWLINE, "LAST_CURSOR", &m->last_cursor, NULL);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ log_debug("Last cursor was %s.", m->last_cursor ? m->last_cursor : "Not available");
"Not available" → "not available".
Post by Susant Sahani
+ return 0;
+}
+
+static int process_journal_input(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ while (true) {
+ r = sd_journal_next(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get next entry: %m");
+ return r;
+ }
+
+ if (r == 0)
+ break;
+
+ r = manager_read_journal_input(m);
+ if (r < 0) {
+ /* Can't send the message. Seek one entry back. */
+ r = sd_journal_previous(m->journal);
+ if (r < 0)
+ log_error_errno(r, "Failed to iterate through journal: %m");
+
+ break;
+ }
+ }
+
+ r = sd_journal_get_cursor(m->journal, &m->current_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ free(m->last_cursor);
+ m->last_cursor = m->current_cursor;
+ m->current_cursor = NULL;
+
+ return update_cursor_state(m);
+}
+
+static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) {
+ Manager *m = userp;
+ int r;
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ r = sd_journal_process(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process journal: %m");
+ manager_disconnect(m);
+ return r;
+ }
+
+ if (r == SD_JOURNAL_NOP)
+ return 0;
+
+ return process_journal_input(m);
+}
+
+static void close_journal_input(Manager *m) {
+ assert(m);
+
+ if (m->journal) {
+ log_debug("Closing journal input.");
+
+ sd_journal_close(m->journal);
+ m->journal = NULL;
+ }
+
+ m->timeout = 0;
+}
+
+static int manager_signal_event_handler(sd_event_source *event, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+
+ assert(m);
+
+ log_received_signal(LOG_INFO, si);
+
+ manager_disconnect(m);
+
+ sd_event_exit(m->event, 0);
+
+ return 0;
+}
+
+static int manager_journal_monitor_listen(Manager *m) {
+ int r, events;
+
+ assert(m);
+
+ r = sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open journal: %m");
+ return r;
+ }
+
+ sd_journal_set_data_threshold(m->journal, 0);
+
+ m->journal_watch_fd = sd_journal_get_fd(m->journal);
+ if (m->journal_watch_fd < 0)
+ return log_error_errno(m->journal_watch_fd, "sd_journal_get_fd failed: %m");
+
+ events = sd_journal_get_events(m->journal);
+
+ r = sd_journal_reliable_fd(m->journal);
+ assert(r >= 0);
+ if (r > 0)
+ m->timeout = -1;
+ else
+ m->timeout = JOURNAL_SEND_POLL_TIMEOUT;
+
+ r = sd_event_add_io(m->event, &m->event_journal_input ,
In some places there's strange indentation, like this space ^ before the comma,
and also below.
Post by Susant Sahani
+ m->journal_watch_fd , events, manager_journal_event_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register input event: %m");
+
+ /* ignore failure */
+ if (!m->last_cursor)
+ (void) load_cursor_state(m);
+
+ if (m->last_cursor) {
+ r = sd_journal_seek_cursor(m->journal, m->last_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to cursor %s: %m",
+ m->last_cursor);
+ }
+
+ return 0;
+}
+
+int manager_connect(Manager *m) {
+ int r;
+
+ assert(m);
+
+ manager_disconnect(m);
+
+ r = manager_open_network_socket(m);
+ if (r < 0)
+ return r;
+
+ r = manager_journal_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_disconnect(Manager *m) {
+ assert(m);
+
+ close_journal_input(m);
+
+ manager_close_network_socket(m);
+
+ m->event_journal_input = sd_event_source_unref(m->event_journal_input);
+
+ sd_notifyf(false, "STATUS=Idle.");
+}
+
+static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *m = userdata;
+ bool connected, online;
+ int r;
+
+ assert(m);
+
+ sd_network_monitor_flush(m->network_monitor);
+
+ /* check if the machine is online */
+ online = network_is_online();
+
+ /* check if the socket is currently open*/
+ connected = m->socket >= 0;
+
+ if (connected && !online) {
+ log_info("No network connectivity, watching for changes.");
+ manager_disconnect(m);
+
+ } else if (!connected && online) {
+ log_info("Network configuration changed, trying to establish connection.");
+
+ r = manager_connect(m);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int manager_network_monitor_listen(Manager *m) {
+ int r, fd, events;
+
+ assert(m);
+
+ r = sd_network_monitor_new(&m->network_monitor, NULL);
+ if (r < 0)
+ return r;
+
+ fd = sd_network_monitor_get_fd(m->network_monitor);
+ if (fd < 0)
+ return fd;
+
+ events = sd_network_monitor_get_events(m->network_monitor);
+ if (events < 0)
+ return events;
+
+ r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_free(Manager *m) {
+ if (!m)
+ return;
+
+ manager_disconnect(m);
+
+ free(m->last_cursor);
+ free(m->current_cursor);
+
+ free(m->state_file);
+
+ sd_event_source_unref(m->network_event_source);
+ sd_network_monitor_unref(m->network_monitor);
+
+ sd_event_source_unref(m->sigterm_event);
+ sd_event_source_unref(m->sigint_event);
+
+ sd_event_unref(m->event);
+
+ free(m);
+}
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ assert(ret);
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ m->socket = m->journal_watch_fd = -1;
+
+ m->state_file = strdup(state_file);
+ if (!m->state_file)
+ return -ENOMEM;
+
+ if (cursor) {
+ m->last_cursor = strdup(cursor);
+ if (!m->last_cursor)
+ return -ENOMEM;
+ }
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
+
+ r = sd_event_add_signal(m->event, NULL, SIGTERM, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGINT, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ sd_event_set_watchdog(m->event, true);
+
+ r = manager_network_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ *ret = m;
+ m = NULL;
+
+ return 0;
+}
diff --git a/src/journal-remote/journal-syslog-manager.h b/src/journal-remote/journal-syslog-manager.h
new file mode 100644
index 0000000..aeb553b
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.h
@@ -0,0 +1,70 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "sd-event.h"
+#include "sd-network.h"
+#include "socket-util.h"
+#include "sd-journal.h"
+
+typedef struct Manager Manager;
+
+struct Manager {
+ sd_event *event;
+ sd_event_source *event_journal_input;
+ uint64_t timeout;
+
+ sd_event_source *sigint_event, *sigterm_event;
+
+ /* network */
+ sd_event_source *network_event_source;
+ sd_network_monitor *network_monitor;
+
+ int socket;
+
+ /* Multicast UDP address */
+ SocketAddress address;
+
+ /* journal */
+ int journal_watch_fd;
+ sd_journal *journal;
+
+ char *state_file;
+
+ char *last_cursor, *current_cursor;
+};
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor);
+void manager_free(Manager *m);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+int manager_connect(Manager *m);
+void manager_disconnect(Manager *m);
+
+void manager_close_network_socket(Manager *m);
+int manager_open_network_socket(Manager *m);
+
+int manager_push_to_network(Manager *m, int severity, int facility,
+ const char *identifier, const char *message,
+ const char *hostname, const char *pid,
+ const struct timeval *tv);
diff --git a/src/journal-remote/journal-syslog-network.c b/src/journal-remote/journal-syslog-network.c
new file mode 100644
index 0000000..26ac362
--- /dev/null
+++ b/src/journal-remote/journal-syslog-network.c
@@ -0,0 +1,201 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <unistd.h>
+#include <stddef.h>
+#include <poll.h>
+
+#include "journal-syslog-manager.h"
+
+#define RFC_5424_NILVALUE "-"
+#define RFC_5424_PROTOCOL 1
+
+#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC)
+
+static int sendmsg_loop(Manager *m, struct msghdr *mh) {
+ int r;
+
+ assert(m);
+ assert(mh);
+
+ for (;;) {
+ if (sendmsg(m->socket, mh, MSG_NOSIGNAL) >= 0)
+ return 0;
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno != EAGAIN)
+ return -errno;
+
+ r = fd_wait_for_event(m->socket, POLLOUT, SEND_TIMEOUT_USEC);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
+ mh.msg_namelen = sizeof(m->address.sockaddr.in6);
+ } else
+ return -EAFNOSUPPORT;
+
+ return sendmsg_loop(m, &mh);
+}
+
+/* rfc3339 timestamp format: yyyy-mm-ddthh:mm:ss[.frac]<+/->zz:zz */
+static void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size) {
+ char gm_buf[sizeof("+0530") + 1];
+ struct tm tm;
+ time_t t;
+
+ assert(header_time);
+
+ t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+ localtime_r(&t, &tm);
+
+ strftime(header_time, header_size, "%Y-%m-%dT%T", &tm);
+
+ /* add fractional part */
+ if (tv)
+ snprintf(header_time + strlen(header_time), header_size, ".%06ld", tv->tv_usec);
+
+ /* format the timezone according to RFC */
+ xstrftime(gm_buf, "%z", &tm);
+ snprintf(header_time + strlen(header_time), header_size, "%.3s:%.2s ", gm_buf, gm_buf + 3);
+}
+
+ * <pri>version sp timestamp sp hostname sp app-name sp procid sp msgid sp [sd-id]s sp msg
+ */
+int manager_push_to_network(Manager *m,
+ int severity,
+ int facility,
+ const char *identifier,
+ const char *message,
+ const char *hostname,
+ const char *pid,
+ const struct timeval *tv) {
+ char header_priority[sizeof("< >1 ") + 1];
+ char header_time[FORMAT_TIMESTAMP_MAX];
+ uint16_t makepri;
+ struct iovec iov[13];
+ int n = 0;
+
+ assert(m);
+ assert(message);
+
+ makepri = (facility << 3) + severity;
+ /* First: priority field Second: Version '<pri>version' */
+ snprintf(header_priority, sizeof(header_priority), "<%i>%i ", makepri, RFC_5424_PROTOCOL);
+ IOVEC_SET_STRING(iov[n++], header_priority);
+
+ /* Third: timestamp */
+ format_rfc3339_timestamp(tv, header_time, sizeof(header_time));
+ IOVEC_SET_STRING(iov[n++], header_time);
+
+ /* Fourth: hostname */
+ if (hostname)
+ IOVEC_SET_STRING(iov[n++], hostname);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Fifth: identifier */
+ if (identifier)
+ IOVEC_SET_STRING(iov[n++], identifier);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Sixth: procid */
+ if (pid)
+ IOVEC_SET_STRING(iov[n++], pid);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Seventh: msgid */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Eighth: [structured-data] */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Ninth: message */
+ IOVEC_SET_STRING(iov[n++], message);
+
+ return network_send(m, iov, n);
+}
+
+void manager_close_network_socket(Manager *m) {
+ assert(m);
+
+ m->socket = safe_close(m->socket);
+}
+
+int manager_open_network_socket(Manager *m) {
+ const int one = 1;
+ int r;
+
+ assert(m);
+
+ if (!IN_SET(m->address.sockaddr.sa.sa_family, AF_INET, AF_INET6))
+ return -EAFNOSUPPORT;
+
+ m->socket = socket(m->address.sockaddr.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->socket < 0)
+ return -errno;
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return m->socket;
+
+ m->socket = safe_close(m->socket);
+ return r;
+}
diff --git a/src/journal-remote/journal-syslogd.c b/src/journal-remote/journal-syslogd.c
new file mode 100644
index 0000000..fd8e770
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.c
@@ -0,0 +1,217 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+#include "util.h"
+#include "build.h"
+#include "mkdir.h"
+#include "capability.h"
+#include "network-util.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+
+#define STATE_FILE "/var/lib/systemd/journal-syslogd/state"
+
+static const char *arg_cursor = NULL;
+static const char *arg_save_state = STATE_FILE;
+
+static int setup_cursor_state_file(Manager *m, uid_t uid, gid_t gid) {
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert(m);
+
+ r = mkdir_parents(m->state_file, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
+ m->state_file);
+
+ fd = open(m->state_file, O_RDWR|O_CLOEXEC, 0644);
+ if (fd >= 0) {
+
+ /* Try to fix the access mode, so that we can still
+ touch the file after dropping priviliges */
+ fchmod(fd, 0644);
+ fchown(fd, uid, gid);
+ } else
+ /* create stamp file with the compiled-in date */
+ return touch_file(m->state_file, true, USEC_INFINITY, uid, gid, 0644);
+
+ return 0;
+}
+
+static void help(void) {
+ printf("%s ..\n\n"
+ "Send journal events to a UDP multicast group in RFC 5424 syslog format.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --cursor=CURSOR Start at the specified cursor\n"
+ " --save-state[=FILE] Save uploaded cursors (default \n"
+ " " STATE_FILE ")\n"
+ " -h --help Show this help and exit\n"
+ " --version Print version string and exit\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_CURSOR,
+ ARG_SAVE_STATE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "cursor", required_argument, NULL, ARG_CURSOR },
+ { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch(c) {
+ help();
+ return 0 /* done */;
+
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0 /* done */;
+ if (arg_cursor) {
+ log_error("cannot use more than one --cursor/--after-cursor");
+ return -EINVAL;
+ }
+
+ arg_cursor = optarg;
+ break;
+ arg_save_state = optarg ?: STATE_FILE;
+ break;
+
+ log_error("Unknown option %s.", argv[optind-1]);
+ return -EINVAL;
+
+ log_error("Missing argument to %s.", argv[optind-1]);
+ return -EINVAL;
+
+ assert_not_reached("Unhandled option code.");
+ }
+
+
+ if (optind < argc) {
+ log_error("Input arguments make no sense with journal input.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ const char *user = "systemd-journal-syslog";
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ log_show_color(true);
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Cannot resolve user name %s: %m", user);
+ goto finish;
+ }
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
THis shoudl be moved up, so --help works regardless of the user being created.
Post by Susant Sahani
+
+ r = manager_new(&m, arg_save_state, arg_cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate manager: %m");
+ goto finish;
+ }
+
+ r = manager_parse_config_file(m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse configuration file: %m");
+ goto finish;
+ }
+
+ r = setup_cursor_state_file(m, uid, gid);
+ if (r < 0)
+ goto cleanup;
+
+ r = drop_privileges(uid, gid,
+ (1ULL << CAP_NET_ADMIN) |
+ (1ULL << CAP_NET_BIND_SERVICE) |
+ (1ULL << CAP_NET_BROADCAST));
+ if (r < 0)
+ goto finish;
+
+ log_debug("%s running as pid "PID_FMT,
+ program_invocation_short_name, getpid());
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing input...");
+
+ if (network_is_online()) {
+ r = manager_connect(m);
+ if (r < 0)
+ goto finish;
+ }
+
+ r = sd_event_loop(m->event);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ goto finish;
+ }
+
+ sd_event_get_exit_code(m->event, &r);
+
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down...");
+
+ return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/journal-remote/journal-syslogd.conf.in b/src/journal-remote/journal-syslogd.conf.in
new file mode 100644
index 0000000..b567a46
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.conf.in
@@ -0,0 +1,2 @@
+[Network]
+#Address=239.0.0.1:6000
diff --git a/units/systemd-journal-syslogd.service b/units/systemd-journal-syslogd.service
new file mode 100644
index 0000000..6e8bfc5
--- /dev/null
+++ b/units/systemd-journal-syslogd.service
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# 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.
+[Unit]
+Description=Journal Syslog Unicast and Multicast Daemon
+After=network.target
+
+[Service]
+ExecStart=/usr/lib/systemd/systemd-journal-syslogd
+PrivateTmp=yes
+PrivateDevices=yes
+WatchdogSec=20min
+
+[Install]
+WantedBy=multi-user.target
Looks nice, but still some polish is required.

Zbyszek
Susant Sahani
2015-04-20 09:03:23 UTC
Permalink
Sorry for the late reply
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
This tiny daemon enables to pull journal entries and push to a UDP
multicast address in syslog RFC 5424 format. systemd-journal-syslogd
runs with own user systemd-journal-syslog. It starts running after
the network is up.
V2: Address Zbigniew's comments
1. Rename binary systemd-journal-syslogd
2. Fixed up man and added example
3. Error code check sd_event_add_signal
4. remove +User=systemd-journal-network from service file
5. remove opterr=0
6. assignment into declaration of mh
V3: Address Lennart's comments
1. add unicast events in the man
2. use fopen_temporary and fflush_and_check().
3. remove if (m->event_journal_input) {
4. use sigprocmask_many
5. fix facility and priority
6. remove IP_MULTICAST_TTL and IP_PKTINFO
7. use safe_atoi
---
Makefile-man.am | 8 +
Makefile.am | 37 ++
man/systemd-journal-syslogd.service.xml | 84 +++++
man/systemd-journal-syslogd.xml | 156 ++++++++
src/journal-remote/journal-syslog-conf.c | 61 ++++
src/journal-remote/journal-syslog-conf.h | 39 ++
src/journal-remote/journal-syslog-gperf.gperf | 18 +
src/journal-remote/journal-syslog-manager.c | 501 ++++++++++++++++++++++++++
src/journal-remote/journal-syslog-manager.h | 70 ++++
src/journal-remote/journal-syslog-network.c | 201 +++++++++++
src/journal-remote/journal-syslogd.c | 217 +++++++++++
src/journal-remote/journal-syslogd.conf.in | 2 +
units/systemd-journal-syslogd.service | 18 +
13 files changed, 1412 insertions(+)
create mode 100644 man/systemd-journal-syslogd.service.xml
create mode 100644 man/systemd-journal-syslogd.xml
create mode 100644 src/journal-remote/journal-syslog-conf.c
create mode 100644 src/journal-remote/journal-syslog-conf.h
create mode 100644 src/journal-remote/journal-syslog-gperf.gperf
create mode 100644 src/journal-remote/journal-syslog-manager.c
create mode 100644 src/journal-remote/journal-syslog-manager.h
create mode 100644 src/journal-remote/journal-syslog-network.c
create mode 100644 src/journal-remote/journal-syslogd.c
create mode 100644 src/journal-remote/journal-syslogd.conf.in
create mode 100644 units/systemd-journal-syslogd.service
diff --git a/Makefile-man.am b/Makefile-man.am
index 2f3e5f2..437d488 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -1380,6 +1380,14 @@ man/systemd-journal-gatewayd.socket.html: man/systemd-journal-gatewayd.service.h
endif
+MANPAGES += \
+ man/systemd-journal-syslogd.service.8 \
+ man/systemd-journal-syslogd.8
+MANPAGES_ALIAS += \
+ man/systemd-journal-syslogd.8
+man/systemd-journal-syslogd.8: man/systemd-journal-syslogd.service.8
+man/systemd-journal-syslogd.html: man/systemd-journal-syslogd.service.html
+
if HAVE_MYHOSTNAME
MANPAGES += \
man/nss-myhostname.8
diff --git a/Makefile.am b/Makefile.am
index 0a57389..0b843ac 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -4361,6 +4361,43 @@ EXTRA_DIST += \
src/journal-remote/journal-upload.conf.in
endif
+systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-manager.h \
+ src/journal-remote/journal-syslog-manager.c \
+ src/journal-remote/journal-syslog-conf.h \
+ src/journal-remote/journal-syslog-conf.c \
+ src/journal-remote/journal-syslog-network.c \
+ src/journal-remote/journal-syslogd.c
+
+nodist_systemd_journal_syslogd_SOURCES = \
+ src/journal-remote/journal-syslog-gperf.c
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslog-gperf.gperf
+
+CLEANFILES += \
+ src/journal-remote/journal-syslog-gperf.c
+
+systemd_journal_syslogd_LDADD = \
+ libsystemd-internal.la \
+ libsystemd-journal-internal.la \
+ libsystemd-shared.la
+
+rootlibexec_PROGRAMS += \
+ systemd-journal-syslogd
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-syslogd.service
+
+EXTRA_DIST += \
+ units/systemd-journal-syslogd.service.in
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-syslogd.conf
+
+EXTRA_DIST += \
+ src/journal-remote/journal-syslogd.conf.in
+
# using _CFLAGS = in the conditional below would suppress AM_CFLAGS
journalctl_CFLAGS = \
$(AM_CFLAGS)
diff --git a/man/systemd-journal-syslogd.service.xml b/man/systemd-journal-syslogd.service.xml
new file mode 100644
index 0000000..e837153
--- /dev/null
+++ b/man/systemd-journal-syslogd.service.xml
@@ -0,0 +1,84 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd.service" xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-syslogd.service</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd.service</refname>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Forward journal events using syslog network procotol</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-journal-syslogd.service</filename></para>
+ <cmdsynopsis>
+ <command>/usr/lib/systemd/systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-journal-syslogd</command> serves journal
+ events over the network. It unicasts and multicasts journal event to Syslog RFC 5424 format.
<command>systemd-journal-syslogd</command> forwards messages from the
journal to other hosts over the network using the traditional syslog
format following <ulink url="">RFC 5425</>. It can be configured to
send messages to both unicast and multicast addresses.
yes modified
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
+ </para>
+
+ <para>The program is started by
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ . Use
The dot should go right after ">". Otherwise there'll be extra spaces in the
formatted output.
Why is there a separate man page for systemd-journal-syslogd? The one
for systemd-journal-syslogd should be enough, it is not indended to be run
standalone. There's useful stuff in the other one, please merge them!
There should be two man pages: one for the binary and the service,
and the other one for the config file.
systemd-journal-syslogd -h does not work, it fails if the user name
cannot be resolved. But --help should work regardless.
fixed
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
+ <command>systemctl start systemd-journal-syslogd.service</command> to start
+ the service, and <command>systemctl enable systemd-journal-syslogd.service</command>
+ to have it started on boot.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
Trailing comma should be a dot.
ok
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/man/systemd-journal-syslogd.xml b/man/systemd-journal-syslogd.xml
new file mode 100644
index 0000000..5497b2e
--- /dev/null
+++ b/man/systemd-journal-syslogd.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+-->
+
+<refentry id="systemd-journal-syslogd" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>systemd-journal-syslogd</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Susant</firstname>
+ <surname>Sahani</surname>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-syslogd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-syslogd</refname>
+ <refpurpose>Send journal messages over the network in RFC 5424 format</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-syslogd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">--save-state=<replaceable>state file</replaceable></arg>
+ <arg choice="opt" rep="norepeat">--cursor=<replaceable>journal cursor</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>systemd-journal-syslogd</command> will send journal
+ entries to the UDP Unicast and Multicast address . Unless
+ limited by one of the options specified below, all journal
+ entries accessible to the user the program is running as will be
+ sent, and then the program will wait and send new entries
+ as they become available.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Send entries from the location in the
+ journal specified by the passed cursor. This has the same
+ meaning as <option>--cursor</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--save-state</option><optional>=<replaceable>PATH</replaceable></optional></term>
+
+ <listitem><para>Send entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the cursor saved in file at <replaceable>PATH</replaceable>
+ (<filename>/var/lib/systemd/journal-syslogd/state</filename> by default).
+ After an entry is successfully uploaded, update this file
+ with the cursor of that entry.
+ </para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned; otherwise, a non-zero
+ failure code is returned.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>[Network] Section Options</title>
+
+ <para>The <literal>[Network]</literal> section only applies for
+ UDP multicast address and Port:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>Address=</varname></term>
+ <listitem><para>Controls whether log messages received by the
+ journal daemon shall be forwarded to a unicast UDP address or multicast UDP network
+ group in syslog RFC 5424 format.</para>
+
+ <para>The the address string format is similar to socket units. See
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=239.0.0.1:6000
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>Example</title>
+ <example>
+ <title>/etc/systemd/journal-syslogd.conf</title>
+ <programlisting>[Network]
+Address=192.168.8.101:514
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-syslogd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/journal-remote/journal-syslog-conf.c b/src/journal-remote/journal-syslog-conf.c
new file mode 100644
index 0000000..c357189
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.c
@@ -0,0 +1,61 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "in-addr-util.h"
+#include "journal-syslog-conf.h"
+
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Manager *m = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = socket_address_parse(&m->address, rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -r,
+ "Failed to parse address value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ return 0;
+}
+
+int manager_parse_config_file(Manager *m) {
+ assert(m);
+
+ return config_parse_many("/etc/systemd/journal-syslogd.conf",
+ CONF_DIRS_NULSTR("systemd/journal-syslogd.conf"),
+ "Network\0",
+ config_item_perf_lookup, journal_syslog_gperf_lookup,
+ false, m);
+}
diff --git a/src/journal-remote/journal-syslog-conf.h b/src/journal-remote/journal-syslog-conf.h
new file mode 100644
index 0000000..ca9ef05
--- /dev/null
+++ b/src/journal-remote/journal-syslog-conf.h
@@ -0,0 +1,39 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "in-addr-util.h"
+#include "conf-parser.h"
+#include "journal-syslog-manager.h"
+
+const struct ConfigPerfItem* journal_syslog_gperf_lookup(const char *key, unsigned length);
+int config_parse_syslog_broadcast_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata);
+int manager_parse_config_file(Manager *m);
diff --git a/src/journal-remote/journal-syslog-gperf.gperf b/src/journal-remote/journal-syslog-gperf.gperf
new file mode 100644
index 0000000..e0f364f
--- /dev/null
+++ b/src/journal-remote/journal-syslog-gperf.gperf
@@ -0,0 +1,18 @@
+%{
+#include <stddef.h>
+#include "conf-parser.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journal_syslog_gperf_hash
+%define lookup-function-name journal_syslog_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Network.Address, config_parse_syslog_broadcast_address, 0, 0
diff --git a/src/journal-remote/journal-syslog-manager.c b/src/journal-remote/journal-syslog-manager.c
new file mode 100644
index 0000000..1051f2d
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.c
@@ -0,0 +1,501 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 "util.h"
+#include "socket-util.h"
+#include "conf-parser.h"
+#include "sd-daemon.h"
+#include "network-util.h"
+#include "capability.h"
+#include "mkdir.h"
+#include "fileio.h"
+#include "journal-internal.h"
+#include "journal-syslog-manager.h"
+
+#define JOURNAL_SEND_POLL_TIMEOUT (10 * USEC_PER_SEC)
+
+/* Default severity LOG_NOTICE */
+#define JOURNAL_DEFAULT_SEVERITY LOG_PRI(LOG_NOTICE)
+
+/* Default facility LOG_USER */
+#define JOURNAL_DEFAULT_FACILITY LOG_FAC(LOG_USER)
+
+static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) {
+ size_t fl, nl;
+ void *buf;
+
+ assert(data);
+ assert(field);
+ assert(target);
+ assert(target_size);
+
+ fl = strlen(field);
+ if (length < fl)
+ return 0;
+
+ if (memcmp(data, field, fl))
+ return 0;
+
+ nl = length - fl;
+ buf = malloc(nl+1);
+ if (!buf)
+ return -ENOMEM;
+
+ memcpy(buf, (const char*) data + fl, nl);
+ ((char*)buf)[nl] = 0;
+
+ free(*target);
+ *target = buf;
+ *target_size = nl;
+
+ return 1;
+}
+
+static int manager_read_journal_input(Manager *m) {
+ _cleanup_free_ char *facility = NULL, *identifier = NULL,
+ *priority = NULL, *message = NULL, *pid = NULL,
+ *hostname = NULL;
+ int sev = JOURNAL_DEFAULT_SEVERITY;
+ int fac = JOURNAL_DEFAULT_FACILITY;
+ struct timeval tv;
+ usec_t realtime;
+ const void *data;
+ size_t length;
+ size_t n = 0;
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ JOURNAL_FOREACH_DATA_RETVAL(m->journal, data, length, r) {
+
+ r = parse_field(data, length, "PRIORITY=", &priority, &n);
Is n used for anything?
No removed .
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_FACILITY=", &facility, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_HOSTNAME=", &hostname, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "_PID=", &pid, &n);
+ if (r < 0)
+ return r;
+ else if (r > 0)
+ continue;
+
+ r = parse_field(data, length, "MESSAGE=", &message, &n);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_journal_get_realtime_usec(m->journal, &realtime);
+ if (r < 0)
+ log_warning_errno(r, "Failed to rerieve realtime from journal: %m");
+ else {
+ tv.tv_sec = realtime / USEC_PER_SEC;
+ tv.tv_usec = realtime % USEC_PER_SEC;
+ }
+
+ if (facility) {
+ r = safe_atoi(facility, &fac);
safe_atou?
yes
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
+ if (r < 0)
+ log_debug("Failed to parse syslog facility: %s", facility);
+
+ if (fac < LOG_KERN || fac >= LOG_NFACILITIES)
+ fac = JOURNAL_DEFAULT_FACILITY;
+ }
+
+ if (priority) {
+ r = safe_atoi(priority, &sev);
safe_atou?
Post by Susant Sahani
+ if (r < 0)
+ log_debug("Failed to parse syslog priority: %s", priority);
+
+ if (sev < LOG_EMERG || sev > LOG_DEBUG)
+ sev = JOURNAL_DEFAULT_SEVERITY;
+ }
+
+ return manager_push_to_network(m, sev, fac, identifier,
+ message, hostname, pid, r >= 0 ? &tv : NULL);
+}
+
+static int update_cursor_state(Manager *m) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(m);
+
+ if (!m->state_file || !m->last_cursor)
+ return 0;
+
+ r = fopen_temporary(m->state_file, &f, &temp_path);
+ if (r < 0)
+ goto finish;
+
+ fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ m->last_cursor);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto finish;
+
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ free(temp_path);
+ temp_path = NULL;
Why is this freed here? Shouldn't it always be freed, in the error path too?
Post by Susant Sahani
+ if (r < 0)
+ log_error_errno(r, "Failed to save state %s: %m", m->state_file);
+
+ return r;
+}
+
+static int load_cursor_state(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (!m->state_file)
+ return 0;
+
+ r = parse_env_file(m->state_file, NEWLINE, "LAST_CURSOR", &m->last_cursor, NULL);
+ if (r < 0 && r != -ENOENT)
+ return r;
+
+ log_debug("Last cursor was %s.", m->last_cursor ? m->last_cursor : "Not available");
"Not available" → "not available".
Post by Susant Sahani
+ return 0;
+}
+
+static int process_journal_input(Manager *m) {
+ int r;
+
+ assert(m);
+ assert(m->journal);
+
+ while (true) {
+ r = sd_journal_next(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get next entry: %m");
+ return r;
+ }
+
+ if (r == 0)
+ break;
+
+ r = manager_read_journal_input(m);
+ if (r < 0) {
+ /* Can't send the message. Seek one entry back. */
+ r = sd_journal_previous(m->journal);
+ if (r < 0)
+ log_error_errno(r, "Failed to iterate through journal: %m");
+
+ break;
+ }
+ }
+
+ r = sd_journal_get_cursor(m->journal, &m->current_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ free(m->last_cursor);
+ m->last_cursor = m->current_cursor;
+ m->current_cursor = NULL;
+
+ return update_cursor_state(m);
+}
+
+static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) {
+ Manager *m = userp;
+ int r;
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ r = sd_journal_process(m->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process journal: %m");
+ manager_disconnect(m);
+ return r;
+ }
+
+ if (r == SD_JOURNAL_NOP)
+ return 0;
+
+ return process_journal_input(m);
+}
+
+static void close_journal_input(Manager *m) {
+ assert(m);
+
+ if (m->journal) {
+ log_debug("Closing journal input.");
+
+ sd_journal_close(m->journal);
+ m->journal = NULL;
+ }
+
+ m->timeout = 0;
+}
+
+static int manager_signal_event_handler(sd_event_source *event, const struct signalfd_siginfo *si, void *userdata) {
+ Manager *m = userdata;
+
+ assert(m);
+
+ log_received_signal(LOG_INFO, si);
+
+ manager_disconnect(m);
+
+ sd_event_exit(m->event, 0);
+
+ return 0;
+}
+
+static int manager_journal_monitor_listen(Manager *m) {
+ int r, events;
+
+ assert(m);
+
+ r = sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open journal: %m");
+ return r;
+ }
+
+ sd_journal_set_data_threshold(m->journal, 0);
+
+ m->journal_watch_fd = sd_journal_get_fd(m->journal);
+ if (m->journal_watch_fd < 0)
+ return log_error_errno(m->journal_watch_fd, "sd_journal_get_fd failed: %m");
+
+ events = sd_journal_get_events(m->journal);
+
+ r = sd_journal_reliable_fd(m->journal);
+ assert(r >= 0);
+ if (r > 0)
+ m->timeout = -1;
+ else
+ m->timeout = JOURNAL_SEND_POLL_TIMEOUT;
+
+ r = sd_event_add_io(m->event, &m->event_journal_input ,
In some places there's strange indentation, like this space ^ before the comma,
and also below.
Post by Susant Sahani
+ m->journal_watch_fd , events, manager_journal_event_handler, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register input event: %m");
+
+ /* ignore failure */
+ if (!m->last_cursor)
+ (void) load_cursor_state(m);
+
+ if (m->last_cursor) {
+ r = sd_journal_seek_cursor(m->journal, m->last_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to cursor %s: %m",
+ m->last_cursor);
+ }
+
+ return 0;
+}
+
+int manager_connect(Manager *m) {
+ int r;
+
+ assert(m);
+
+ manager_disconnect(m);
+
+ r = manager_open_network_socket(m);
+ if (r < 0)
+ return r;
+
+ r = manager_journal_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_disconnect(Manager *m) {
+ assert(m);
+
+ close_journal_input(m);
+
+ manager_close_network_socket(m);
+
+ m->event_journal_input = sd_event_source_unref(m->event_journal_input);
+
+ sd_notifyf(false, "STATUS=Idle.");
+}
+
+static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *m = userdata;
+ bool connected, online;
+ int r;
+
+ assert(m);
+
+ sd_network_monitor_flush(m->network_monitor);
+
+ /* check if the machine is online */
+ online = network_is_online();
+
+ /* check if the socket is currently open*/
+ connected = m->socket >= 0;
+
+ if (connected && !online) {
+ log_info("No network connectivity, watching for changes.");
+ manager_disconnect(m);
+
+ } else if (!connected && online) {
+ log_info("Network configuration changed, trying to establish connection.");
+
+ r = manager_connect(m);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int manager_network_monitor_listen(Manager *m) {
+ int r, fd, events;
+
+ assert(m);
+
+ r = sd_network_monitor_new(&m->network_monitor, NULL);
+ if (r < 0)
+ return r;
+
+ fd = sd_network_monitor_get_fd(m->network_monitor);
+ if (fd < 0)
+ return fd;
+
+ events = sd_network_monitor_get_events(m->network_monitor);
+ if (events < 0)
+ return events;
+
+ r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void manager_free(Manager *m) {
+ if (!m)
+ return;
+
+ manager_disconnect(m);
+
+ free(m->last_cursor);
+ free(m->current_cursor);
+
+ free(m->state_file);
+
+ sd_event_source_unref(m->network_event_source);
+ sd_network_monitor_unref(m->network_monitor);
+
+ sd_event_source_unref(m->sigterm_event);
+ sd_event_source_unref(m->sigint_event);
+
+ sd_event_unref(m->event);
+
+ free(m);
+}
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ assert(ret);
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ m->socket = m->journal_watch_fd = -1;
+
+ m->state_file = strdup(state_file);
+ if (!m->state_file)
+ return -ENOMEM;
+
+ if (cursor) {
+ m->last_cursor = strdup(cursor);
+ if (!m->last_cursor)
+ return -ENOMEM;
+ }
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0);
+
+ r = sd_event_add_signal(m->event, NULL, SIGTERM, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(m->event, NULL, SIGINT, manager_signal_event_handler, m);
+ if (r < 0)
+ return r;
+
+ sd_event_set_watchdog(m->event, true);
+
+ r = manager_network_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ *ret = m;
+ m = NULL;
+
+ return 0;
+}
diff --git a/src/journal-remote/journal-syslog-manager.h b/src/journal-remote/journal-syslog-manager.h
new file mode 100644
index 0000000..aeb553b
--- /dev/null
+++ b/src/journal-remote/journal-syslog-manager.h
@@ -0,0 +1,70 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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/>.
+***/
+
+#pragma once
+
+#include "sd-event.h"
+#include "sd-network.h"
+#include "socket-util.h"
+#include "sd-journal.h"
+
+typedef struct Manager Manager;
+
+struct Manager {
+ sd_event *event;
+ sd_event_source *event_journal_input;
+ uint64_t timeout;
+
+ sd_event_source *sigint_event, *sigterm_event;
+
+ /* network */
+ sd_event_source *network_event_source;
+ sd_network_monitor *network_monitor;
+
+ int socket;
+
+ /* Multicast UDP address */
+ SocketAddress address;
+
+ /* journal */
+ int journal_watch_fd;
+ sd_journal *journal;
+
+ char *state_file;
+
+ char *last_cursor, *current_cursor;
+};
+
+int manager_new(Manager **ret, const char *state_file, const char *cursor);
+void manager_free(Manager *m);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+int manager_connect(Manager *m);
+void manager_disconnect(Manager *m);
+
+void manager_close_network_socket(Manager *m);
+int manager_open_network_socket(Manager *m);
+
+int manager_push_to_network(Manager *m, int severity, int facility,
+ const char *identifier, const char *message,
+ const char *hostname, const char *pid,
+ const struct timeval *tv);
diff --git a/src/journal-remote/journal-syslog-network.c b/src/journal-remote/journal-syslog-network.c
new file mode 100644
index 0000000..26ac362
--- /dev/null
+++ b/src/journal-remote/journal-syslog-network.c
@@ -0,0 +1,201 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <unistd.h>
+#include <stddef.h>
+#include <poll.h>
+
+#include "journal-syslog-manager.h"
+
+#define RFC_5424_NILVALUE "-"
+#define RFC_5424_PROTOCOL 1
+
+#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC)
+
+static int sendmsg_loop(Manager *m, struct msghdr *mh) {
+ int r;
+
+ assert(m);
+ assert(mh);
+
+ for (;;) {
+ if (sendmsg(m->socket, mh, MSG_NOSIGNAL) >= 0)
+ return 0;
+
+ if (errno == EINTR)
+ continue;
+
+ if (errno != EAGAIN)
+ return -errno;
+
+ r = fd_wait_for_event(m->socket, POLLOUT, SEND_TIMEOUT_USEC);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) {
+ struct msghdr mh = {
+ .msg_iov = iovec,
+ .msg_iovlen = n_iovec,
+ };
+
+ assert(m);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (m->address.sockaddr.sa.sa_family == AF_INET) {
+ mh.msg_name = &m->address.sockaddr.sa;
+ mh.msg_namelen = sizeof(m->address.sockaddr.sa);
+ } else if (m->address.sockaddr.sa.sa_family == AF_INET6) {
+ mh.msg_name = &m->address.sockaddr.in6;
+ mh.msg_namelen = sizeof(m->address.sockaddr.in6);
+ } else
+ return -EAFNOSUPPORT;
+
+ return sendmsg_loop(m, &mh);
+}
+
+/* rfc3339 timestamp format: yyyy-mm-ddthh:mm:ss[.frac]<+/->zz:zz */
+static void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size) {
+ char gm_buf[sizeof("+0530") + 1];
+ struct tm tm;
+ time_t t;
+
+ assert(header_time);
+
+ t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+ localtime_r(&t, &tm);
+
+ strftime(header_time, header_size, "%Y-%m-%dT%T", &tm);
+
+ /* add fractional part */
+ if (tv)
+ snprintf(header_time + strlen(header_time), header_size, ".%06ld", tv->tv_usec);
+
+ /* format the timezone according to RFC */
+ xstrftime(gm_buf, "%z", &tm);
+ snprintf(header_time + strlen(header_time), header_size, "%.3s:%.2s ", gm_buf, gm_buf + 3);
+}
+
+ * <pri>version sp timestamp sp hostname sp app-name sp procid sp msgid sp [sd-id]s sp msg
+ */
+int manager_push_to_network(Manager *m,
+ int severity,
+ int facility,
+ const char *identifier,
+ const char *message,
+ const char *hostname,
+ const char *pid,
+ const struct timeval *tv) {
+ char header_priority[sizeof("< >1 ") + 1];
+ char header_time[FORMAT_TIMESTAMP_MAX];
+ uint16_t makepri;
+ struct iovec iov[13];
+ int n = 0;
+
+ assert(m);
+ assert(message);
+
+ makepri = (facility << 3) + severity;
+ /* First: priority field Second: Version '<pri>version' */
+ snprintf(header_priority, sizeof(header_priority), "<%i>%i ", makepri, RFC_5424_PROTOCOL);
+ IOVEC_SET_STRING(iov[n++], header_priority);
+
+ /* Third: timestamp */
+ format_rfc3339_timestamp(tv, header_time, sizeof(header_time));
+ IOVEC_SET_STRING(iov[n++], header_time);
+
+ /* Fourth: hostname */
+ if (hostname)
+ IOVEC_SET_STRING(iov[n++], hostname);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Fifth: identifier */
+ if (identifier)
+ IOVEC_SET_STRING(iov[n++], identifier);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Sixth: procid */
+ if (pid)
+ IOVEC_SET_STRING(iov[n++], pid);
+ else
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Seventh: msgid */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Eighth: [structured-data] */
+ IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE);
+ IOVEC_SET_STRING(iov[n++], " ");
+
+ /* Ninth: message */
+ IOVEC_SET_STRING(iov[n++], message);
+
+ return network_send(m, iov, n);
+}
+
+void manager_close_network_socket(Manager *m) {
+ assert(m);
+
+ m->socket = safe_close(m->socket);
+}
+
+int manager_open_network_socket(Manager *m) {
+ const int one = 1;
+ int r;
+
+ assert(m);
+
+ if (!IN_SET(m->address.sockaddr.sa.sa_family, AF_INET, AF_INET6))
+ return -EAFNOSUPPORT;
+
+ m->socket = socket(m->address.sockaddr.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->socket < 0)
+ return -errno;
+
+ r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return m->socket;
+
+ m->socket = safe_close(m->socket);
+ return r;
+}
diff --git a/src/journal-remote/journal-syslogd.c b/src/journal-remote/journal-syslogd.c
new file mode 100644
index 0000000..fd8e770
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.c
@@ -0,0 +1,217 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ 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 <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+#include "util.h"
+#include "build.h"
+#include "mkdir.h"
+#include "capability.h"
+#include "network-util.h"
+#include "journal-syslog-conf.h"
+#include "journal-syslog-manager.h"
+
+#define STATE_FILE "/var/lib/systemd/journal-syslogd/state"
+
+static const char *arg_cursor = NULL;
+static const char *arg_save_state = STATE_FILE;
+
+static int setup_cursor_state_file(Manager *m, uid_t uid, gid_t gid) {
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ assert(m);
+
+ r = mkdir_parents(m->state_file, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
+ m->state_file);
+
+ fd = open(m->state_file, O_RDWR|O_CLOEXEC, 0644);
+ if (fd >= 0) {
+
+ /* Try to fix the access mode, so that we can still
+ touch the file after dropping priviliges */
+ fchmod(fd, 0644);
+ fchown(fd, uid, gid);
+ } else
+ /* create stamp file with the compiled-in date */
+ return touch_file(m->state_file, true, USEC_INFINITY, uid, gid, 0644);
+
+ return 0;
+}
+
+static void help(void) {
+ printf("%s ..\n\n"
+ "Send journal events to a UDP multicast group in RFC 5424 syslog format.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --cursor=CURSOR Start at the specified cursor\n"
+ " --save-state[=FILE] Save uploaded cursors (default \n"
+ " " STATE_FILE ")\n"
+ " -h --help Show this help and exit\n"
+ " --version Print version string and exit\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_CURSOR,
+ ARG_SAVE_STATE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "cursor", required_argument, NULL, ARG_CURSOR },
+ { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch(c) {
+ help();
+ return 0 /* done */;
+
+ puts(PACKAGE_STRING);
+ puts(SYSTEMD_FEATURES);
+ return 0 /* done */;
+ if (arg_cursor) {
+ log_error("cannot use more than one --cursor/--after-cursor");
+ return -EINVAL;
+ }
+
+ arg_cursor = optarg;
+ break;
+ arg_save_state = optarg ?: STATE_FILE;
+ break;
+
+ log_error("Unknown option %s.", argv[optind-1]);
+ return -EINVAL;
+
+ log_error("Missing argument to %s.", argv[optind-1]);
+ return -EINVAL;
+
+ assert_not_reached("Unhandled option code.");
+ }
+
+
+ if (optind < argc) {
+ log_error("Input arguments make no sense with journal input.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ const char *user = "systemd-journal-syslog";
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ log_show_color(true);
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Cannot resolve user name %s: %m", user);
+ goto finish;
+ }
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
THis shoudl be moved up, so --help works regardless of the user being created.
moved up. thanks
Post by Zbigniew Jędrzejewski-Szmek
Post by Susant Sahani
+
+ r = manager_new(&m, arg_save_state, arg_cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to allocate manager: %m");
+ goto finish;
+ }
+
+ r = manager_parse_config_file(m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse configuration file: %m");
+ goto finish;
+ }
+
+ r = setup_cursor_state_file(m, uid, gid);
+ if (r < 0)
+ goto cleanup;
+
+ r = drop_privileges(uid, gid,
+ (1ULL << CAP_NET_ADMIN) |
+ (1ULL << CAP_NET_BIND_SERVICE) |
+ (1ULL << CAP_NET_BROADCAST));
+ if (r < 0)
+ goto finish;
+
+ log_debug("%s running as pid "PID_FMT,
+ program_invocation_short_name, getpid());
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing input...");
+
+ if (network_is_online()) {
+ r = manager_connect(m);
+ if (r < 0)
+ goto finish;
+ }
+
+ r = sd_event_loop(m->event);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ goto finish;
+ }
+
+ sd_event_get_exit_code(m->event, &r);
+
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down...");
+
+ return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/journal-remote/journal-syslogd.conf.in b/src/journal-remote/journal-syslogd.conf.in
new file mode 100644
index 0000000..b567a46
--- /dev/null
+++ b/src/journal-remote/journal-syslogd.conf.in
@@ -0,0 +1,2 @@
+[Network]
+#Address=239.0.0.1:6000
diff --git a/units/systemd-journal-syslogd.service b/units/systemd-journal-syslogd.service
new file mode 100644
index 0000000..6e8bfc5
--- /dev/null
+++ b/units/systemd-journal-syslogd.service
@@ -0,0 +1,18 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# 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.
+[Unit]
+Description=Journal Syslog Unicast and Multicast Daemon
+After=network.target
+
+[Service]
+ExecStart=/usr/lib/systemd/systemd-journal-syslogd
+PrivateTmp=yes
+PrivateDevices=yes
+WatchdogSec=20min
+
+[Install]
+WantedBy=multi-user.target
Looks nice, but still some polish is required.
Zbyszek
_______________________________________________
systemd-devel mailing list
http://lists.freedesktop.org/mailman/listinfo/systemd-devel
Continue reading on narkive:
Loading...