summaryrefslogtreecommitdiff
path: root/src/grp-journal
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-09-13 18:09:19 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-09-13 18:09:19 -0400
commita3af4daf91a34f58d4e724aa806b161954e9269f (patch)
treefc78fbba4b7623b5020220c0d28bffce1abfe143 /src/grp-journal
parentbbd6ee5c5916da5f54678a7500b0a420352079a2 (diff)
./tools/notsd-move
Diffstat (limited to 'src/grp-journal')
-rw-r--r--src/grp-journal/.gitignore1
-rw-r--r--src/grp-journal/90-journald.preset10
-rw-r--r--src/grp-journal/Makefile191
-rw-r--r--src/grp-journal/README.in26
-rw-r--r--src/grp-journal/catalog/systemd.be.catalog260
-rw-r--r--src/grp-journal/catalog/systemd.be@latin.catalog260
-rw-r--r--src/grp-journal/catalog/systemd.bg.catalog324
-rw-r--r--src/grp-journal/catalog/systemd.catalog334
-rw-r--r--src/grp-journal/catalog/systemd.da.catalog261
-rw-r--r--src/grp-journal/catalog/systemd.fr.catalog320
-rw-r--r--src/grp-journal/catalog/systemd.hr.catalog314
-rw-r--r--src/grp-journal/catalog/systemd.hu.catalog262
-rw-r--r--src/grp-journal/catalog/systemd.it.catalog254
-rw-r--r--src/grp-journal/catalog/systemd.ko.catalog264
-rw-r--r--src/grp-journal/catalog/systemd.pl.catalog315
-rw-r--r--src/grp-journal/catalog/systemd.pt_BR.catalog264
-rw-r--r--src/grp-journal/catalog/systemd.ru.catalog354
-rw-r--r--src/grp-journal/catalog/systemd.sr.catalog262
-rw-r--r--src/grp-journal/catalog/systemd.zh_CN.catalog253
-rw-r--r--src/grp-journal/catalog/systemd.zh_TW.catalog263
-rw-r--r--src/grp-journal/grp-remote/.gitignore2
-rw-r--r--src/grp-journal/grp-remote/Makefile30
-rw-r--r--src/grp-journal/grp-remote/browse.html544
-rwxr-xr-xsrc/grp-journal/grp-remote/log-generator.py76
-rw-r--r--src/grp-journal/grp-remote/microhttpd-util.c328
-rw-r--r--src/grp-journal/grp-remote/microhttpd-util.h60
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile68
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c1077
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.in29
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.xml302
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.socket16
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.sysusers8
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/Makefile85
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c507
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h69
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c169
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h70
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c1602
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in6
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml121
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h53
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in25
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket15
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers8
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml325
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/Makefile55
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c424
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c881
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in5
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h72
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.service.in27
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.sysusers8
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.xml263
-rw-r--r--src/grp-journal/grp-remote/systemd-remote.tmpfiles13
-rw-r--r--src/grp-journal/journal-nocow.tmpfiles27
-rw-r--r--src/grp-journal/journalctl/Makefile62
-rw-r--r--src/grp-journal/journalctl/journal-qrcode.c135
-rw-r--r--src/grp-journal/journalctl/journal-qrcode.h27
-rw-r--r--src/grp-journal/journalctl/journalctl.c2602
-rw-r--r--src/grp-journal/journalctl/journalctl.completion.bash133
-rw-r--r--src/grp-journal/journalctl/journalctl.completion.zsh98
-rw-r--r--src/grp-journal/journalctl/journalctl.xml914
-rw-r--r--src/grp-journal/journalctl/systemd-journal-catalog-update.service.in21
-rw-r--r--src/grp-journal/journalctl/systemd-journal-flush.service.in22
-rw-r--r--src/grp-journal/libjournal-core/.gitignore4
-rw-r--r--src/grp-journal/libjournal-core/Makefile56
-rw-r--r--src/grp-journal/libjournal-core/journald-audit.c565
-rw-r--r--src/grp-journal/libjournal-core/journald-audit.h28
-rw-r--r--src/grp-journal/libjournal-core/journald-console.c116
-rw-r--r--src/grp-journal/libjournal-core/journald-console.h24
-rw-r--r--src/grp-journal/libjournal-core/journald-gperf.gperf48
-rw-r--r--src/grp-journal/libjournal-core/journald-kmsg.c474
-rw-r--r--src/grp-journal/libjournal-core/journald-kmsg.h29
-rw-r--r--src/grp-journal/libjournal-core/journald-native.c502
-rw-r--r--src/grp-journal/libjournal-core/journald-native.h35
-rw-r--r--src/grp-journal/libjournal-core/journald-rate-limit.c272
-rw-r--r--src/grp-journal/libjournal-core/journald-rate-limit.h28
-rw-r--r--src/grp-journal/libjournal-core/journald-server.c2009
-rw-r--r--src/grp-journal/libjournal-core/journald-server.h187
-rw-r--r--src/grp-journal/libjournal-core/journald-stream.c786
-rw-r--r--src/grp-journal/libjournal-core/journald-stream.h32
-rw-r--r--src/grp-journal/libjournal-core/journald-syslog.c455
-rw-r--r--src/grp-journal/libjournal-core/journald-syslog.h33
-rw-r--r--src/grp-journal/libjournal-core/journald-wall.c72
-rw-r--r--src/grp-journal/libjournal-core/journald-wall.h24
-rw-r--r--src/grp-journal/libjournal-core/test-audit-type.c43
-rw-r--r--src/grp-journal/libjournal-core/test-catalog.c264
-rw-r--r--src/grp-journal/libjournal-core/test-compress-benchmark.c180
-rw-r--r--src/grp-journal/libjournal-core/test-compress.c308
-rw-r--r--src/grp-journal/libjournal-core/test-journal-enum.c53
-rw-r--r--src/grp-journal/libjournal-core/test-journal-flush.c75
-rw-r--r--src/grp-journal/libjournal-core/test-journal-init.c64
-rw-r--r--src/grp-journal/libjournal-core/test-journal-interleaving.c307
-rw-r--r--src/grp-journal/libjournal-core/test-journal-match.c76
-rw-r--r--src/grp-journal/libjournal-core/test-journal-send.c102
-rw-r--r--src/grp-journal/libjournal-core/test-journal-stream.c196
-rw-r--r--src/grp-journal/libjournal-core/test-journal-syslog.c45
-rw-r--r--src/grp-journal/libjournal-core/test-journal-verify.c150
-rw-r--r--src/grp-journal/libjournal-core/test-journal.c178
-rw-r--r--src/grp-journal/libjournal-core/test-mmap-cache.c79
-rw-r--r--src/grp-journal/systemd-cat/Makefile35
-rw-r--r--src/grp-journal/systemd-cat/cat.c161
-rw-r--r--src/grp-journal/systemd-cat/systemd-cat.completion.bash57
-rw-r--r--src/grp-journal/systemd-cat/systemd-cat.completion.zsh12
-rw-r--r--src/grp-journal/systemd-cat/systemd-cat.xml178
-rw-r--r--src/grp-journal/systemd-journald/Makefile75
-rw-r--r--src/grp-journal/systemd-journald/journald.c120
-rw-r--r--src/grp-journal/systemd-journald/journald.conf41
-rw-r--r--src/grp-journal/systemd-journald/journald.conf.xml410
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald-audit.socket20
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald-dev-log.socket32
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald.service.in32
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald.service.xml276
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald.socket26
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald.sysusers8
-rw-r--r--src/grp-journal/systemd-journald/systemd-journald.tmpfiles.m455
116 files changed, 25613 insertions, 0 deletions
diff --git a/src/grp-journal/.gitignore b/src/grp-journal/.gitignore
new file mode 100644
index 0000000000..c3fea7424f
--- /dev/null
+++ b/src/grp-journal/.gitignore
@@ -0,0 +1 @@
+/README
diff --git a/src/grp-journal/90-journald.preset b/src/grp-journal/90-journald.preset
new file mode 100644
index 0000000000..6a8c17b1fa
--- /dev/null
+++ b/src/grp-journal/90-journald.preset
@@ -0,0 +1,10 @@
+# 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.
+
+disable syslog.socket
+
+disable systemd-journal-gatewayd.*
diff --git a/src/grp-journal/Makefile b/src/grp-journal/Makefile
new file mode 100644
index 0000000000..91293d8dfa
--- /dev/null
+++ b/src/grp-journal/Makefile
@@ -0,0 +1,191 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+test_journal_SOURCES = \
+ src/journal/test-journal.c
+
+test_journal_LDADD = \
+ libjournal-core.la
+
+test_journal_send_SOURCES = \
+ src/journal/test-journal-send.c
+
+test_journal_send_LDADD = \
+ libjournal-core.la
+
+test_journal_syslog_SOURCES = \
+ src/journal/test-journal-syslog.c
+
+test_journal_syslog_LDADD = \
+ libjournal-core.la
+
+test_journal_match_SOURCES = \
+ src/journal/test-journal-match.c
+
+test_journal_match_LDADD = \
+ libjournal-core.la
+
+test_journal_enum_SOURCES = \
+ src/journal/test-journal-enum.c
+
+test_journal_enum_LDADD = \
+ libjournal-core.la
+
+test_journal_stream_SOURCES = \
+ src/journal/test-journal-stream.c
+
+test_journal_stream_LDADD = \
+ libjournal-core.la
+
+test_journal_flush_SOURCES = \
+ src/journal/test-journal-flush.c
+
+test_journal_flush_LDADD = \
+ libjournal-core.la
+
+test_journal_init_SOURCES = \
+ src/journal/test-journal-init.c
+
+test_journal_init_LDADD = \
+ libjournal-core.la
+
+test_journal_verify_SOURCES = \
+ src/journal/test-journal-verify.c
+
+test_journal_verify_LDADD = \
+ libjournal-core.la
+
+test_journal_interleaving_SOURCES = \
+ src/journal/test-journal-interleaving.c
+
+test_journal_interleaving_LDADD = \
+ libjournal-core.la
+
+test_mmap_cache_SOURCES = \
+ src/journal/test-mmap-cache.c
+
+test_mmap_cache_LDADD = \
+ libjournal-core.la
+
+test_catalog_SOURCES = \
+ src/journal/test-catalog.c
+
+test_catalog_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DCATALOG_DIR=\"$(abs_top_srcdir)/catalog\"
+
+test_catalog_LDADD = \
+ libjournal-core.la
+
+test_compress_SOURCES = \
+ src/journal/test-compress.c
+
+test_compress_LDADD = \
+ libshared.la
+
+test_compress_benchmark_SOURCES = \
+ src/journal/test-compress-benchmark.c
+
+test_compress_benchmark_LDADD = \
+ libshared.la
+
+test_audit_type_SOURCES = \
+ src/journal/test-audit-type.c
+
+test_audit_type_LDADD = \
+ libjournal-core.la
+
+journal-install-hook:
+ -$(MKDIR_P) $(DESTDIR)/var/log/journal
+ -chown 0:0 $(DESTDIR)/var/log/journal
+ -chmod 755 $(DESTDIR)/var/log/journal
+ -setfacl -nm g:adm:rx,d:g:adm:rx $(DESTDIR)/var/log/journal/
+ -setfacl -nm g:wheel:rx,d:g:wheel:rx $(DESTDIR)/var/log/journal/
+
+journal-uninstall-hook:
+ -rmdir $(DESTDIR)/var/log/journal/remote
+ -rmdir $(DESTDIR)/var/log/journal/
+
+INSTALL_EXEC_HOOKS += journal-install-hook
+UNINSTALL_EXEC_HOOKS += journal-uninstall-hook
+
+# ------------------------------------------------------------------------------
+# Update catalog on installation. Do not bother if installing
+# in DESTDIR, since this is likely for packaging purposes.
+catalog-update-hook:
+ -test -n "$(DESTDIR)" || $(rootbindir)/journalctl --update-catalog
+
+INSTALL_DATA_HOOKS += \
+ catalog-update-hook
+
+catalog-remove-hook:
+ -test -n "$(DESTDIR)" || rm -f $(catalogstatedir)/database
+
+UNINSTALL_DATA_HOOKS += \
+ catalog-remove-hook
+
+tests += \
+ test-journal \
+ test-journal-enum \
+ test-journal-send \
+ test-journal-syslog \
+ test-journal-match \
+ test-journal-stream \
+ test-journal-init \
+ test-journal-verify \
+ test-journal-interleaving \
+ test-journal-flush \
+ test-mmap-cache \
+ test-catalog \
+ test-audit-type
+
+ifneq ($(HAVE_COMPRESSION),)
+tests += \
+ test-compress \
+ test-compress-benchmark
+endif # HAVE_COMPRESSION
+
+ifneq ($(HAVE_SYSV_COMPAT),)
+
+varlog_DATA = \
+ docs/var-log/README
+
+$(outdir)/README: docs/var-log/README.in
+ $(SED_PROCESS)
+
+CLEANFILES += \
+ docs/var-log/README
+endif # HAVE_SYSV_COMPAT
+
+EXTRA_DIST += \
+ docs/var-log/README.in
+
+nested.subdirs += grp-remote
+nested.subdirs += journalctl
+nested.subdirs += libjournal-core
+nested.subdirs += systemd-cat
+nested.subdirs += systemd-journald
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/README.in b/src/grp-journal/README.in
new file mode 100644
index 0000000000..2e64fb196a
--- /dev/null
+++ b/src/grp-journal/README.in
@@ -0,0 +1,26 @@
+You are looking for the traditional text log files in @VARLOGDIR@, and
+they are gone?
+
+Here's an explanation on what's going on:
+
+You are running a systemd-based OS where traditional syslog has been
+replaced with the Journal. The journal stores the same (and more)
+information as classic syslog. To make use of the journal and access
+the collected log data simply invoke "journalctl", which will output
+the logs in the identical text-based format the syslog files in
+@VARLOGDIR@ used to be. For further details, please refer to
+journalctl(1).
+
+Alternatively, consider installing one of the traditional syslog
+implementations available for your distribution, which will generate
+the classic log files for you. Syslog implementations such as
+syslog-ng or rsyslog may be installed side-by-side with the journal
+and will continue to function the way they always did.
+
+Thank you!
+
+Further reading:
+ man:journalctl(1)
+ man:systemd-journald.service(8)
+ man:journald.conf(5)
+ http://0pointer.de/blog/projects/the-journal.html
diff --git a/src/grp-journal/catalog/systemd.be.catalog b/src/grp-journal/catalog/systemd.be.catalog
new file mode 100644
index 0000000000..051f49492f
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.be.catalog
@@ -0,0 +1,260 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2015 Viktar Vaŭčkievič
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Belarusian translation
+
+# The catalog format is documented on
+# Фармат каталога апісаны на старонцы
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Сэрвіс журналявання запусціўся
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Працэс сістэмнага журналявання запусціўся, адкрыў файлы для
+запісу і гатовы апрацоўваць запыты.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Сэрвіс журналявання спыніўся
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Працэс сістэмнага журналявання спыніўся і закрыў усе файлы.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Паведамленні з сэрвісу адкінуты
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Сэрвіс адправіў занадта штат паведамленняў за кароткі прамежак часу.
+Частка паведамленняў была адкінута.
+
+Майце на ўвазе, што былі адкінуты паведамлення толькі гэтага сэрвісу.
+Паведамленні іншых сэрвісаў засталіся.
+
+Мяжа, пасля якой паведамленні будуць адкінуты, наладжваецца з
+дапамогай RateLimitIntervalSec= і RateLimitBurst= у файле
+/etc/systemd/journald.conf. Глядзіце journald.conf(5) для дэталей.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Паведамленні страчаны
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Паведамленні ядра былі страчаны, так як сістэма журналявання не паспела
+іх апрацаваць.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) скінуў дамп памяці
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) разбіўся і скінуў дамп памяці.
+
+Звычайна гэта сведчыць аб памылцы ў праграмным кодзе.
+Рэкамендуецца паведаміць аб гэтым распрацоўнікам.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Новая сесія № @SESSION_ID@ створана для карыстальніка @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Новая сесія з № @SESSION_ID@ створана для карыстальніка @USER_ID@.
+
+Лідар гэтай сесіі пад № @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Сесія № @SESSION_ID@ спынена
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Сесія № @SESSION_ID@ спынена.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Даступна новае працоўнае месца № @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Новае працоўнае месца № @SEAT_ID@ наладжана і даступна для выкарыстання.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Працоўнае месца № @SEAT_ID@ выдалена
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Працоўнае месца № @SEAT_ID@ выдалена і больш не даступна.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Час зменены
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Сістэмны гадзіннік зменены на @REALTIME@ мікрасекунд ад 1 студзеня 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Часавы пояс зменены на @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Сістэмны часавы пояс зменены на @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Запуск сістэмы завяршыўся
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Усе сістэмныя сэрвісы, неабходныя для загрузкі сістэмы, паспяхова
+запусціліся. Майце на ўвазе, што гэта не значыць, што машына нічога не
+робіць. Магчыма, некаторыя сэрвісы яшчэ ініцыялізіруюцца.
+
+На запуск ядра спатрэбілася @KERNEL_USEC@ мікрасекунд.
+
+На запуск пачатковага RAM-дыска спатрэбілася @INITRD_USEC@ мікрасекунд.
+
+На запуск сістэмных сэрвісаў спатрэбілася @USERSPACE_USEC@ мікрасекунд.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Сістэма перайшла ў стан сну @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Цяпер сістэма перайшла у стан сну @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Сістэма выйшла са стана сну @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Цяпер сістэма выйшла са стана сну @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Сістэма завяршае работу
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Пачаўся працэс выключэння сістэмы.
+Спыняюцца ўсе сістэмныя сэрвісы і дэмантуюцца файлавыя сістэмы.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Юніт @UNIT@ запускаецца
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Пачаўся працэс запуску юніта @UNIT@.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Юніт @UNIT@ запусціўся
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Працэс запуску юніта @UNIT@ завершаны.
+
+Вынік: @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Юніт @UNIT@ спыняецца
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Пачаўся працэс спынення юніта @UNIT@.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Юніт @UNIT@ спынены
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Працэс спынення юніта @UNIT@ завершаны.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Збой юніта @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Збой юніта @UNIT@.
+
+Вынік: @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Юніт @UNIT@ перачытвае сваю канфігурацыю
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Юніт @UNIT@ пачаў перачытваць сваю канфігурацыю.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Юніт @UNIT@ перачытаў сваю канфігурацыю
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Юніт @UNIT@ перачытаў сваю канфігурацыю.
+
+Вынік: @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Працэс @EXECUTABLE@ не можа быць выкананы
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Працэс @EXECUTABLE@ не можа быць выкананы ў выніку збою.
+
+Ён вярнуў памылку нумар @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Sibject: Адно ці больш паведамленняў не былі накіраваны ў syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Адно ці больш паведамленняў не былі накіраваны ў syslog сэрвіс, які
+выконваецца паралельна з journald. Звычайна гэта значыць, што
+рэалізацыя syslog не паспявае апрацаваць паведамленні з неабходнай
+хуткасцю.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Кропка мантавання не пустая
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Каталог @WHERE@ указаны як кропка мантавання (другое поле ў /etc/fstab
+ці Where= поле ў файле юніта systemd) і не пусты. Гэта не перашкаджае
+мантаванню, але існуючыя ў ім файлы будуць недаступны. Для доступу да
+іх, калі ласка, змантуйце гэтую файлавую сістэму ў іншае месца.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Віртуальная машына або кантэйнер запусціўся
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Віртуальная машына @NAME@ з лідарам № @LEADER@ запусцілася і
+гатова для выкарыстання.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Віртуальная машына або кантэйнер спынены
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Віртуальная машына @NAME@ з лідарам № @LEADER@ спынена.
diff --git a/src/grp-journal/catalog/systemd.be@latin.catalog b/src/grp-journal/catalog/systemd.be@latin.catalog
new file mode 100644
index 0000000000..6ab361aafb
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.be@latin.catalog
@@ -0,0 +1,260 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2015 Viktar Vaŭčkievič
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Belarusian Latin translation
+
+# The catalog format is documented on
+# Farmat kataloha apisany na staroncy
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Servis žurnaliavannia zapusciŭsia
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Praces sistemnaha žurnaliavannia zapusciŭsia, adkryŭ fajly dlia
+zapisu i hatovy apracoŭvać zapyty.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Servis žurnaliavannia spyniŭsia
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Praces sistemnaha žurnaliavannia spyniŭsia i zakryŭ usie fajly.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Paviedamlienni z servisu adkinuty
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Servis adpraviŭ zanadta štat paviedamlienniaŭ za karotki pramiežak času.
+Častka paviedamlienniaŭ byla adkinuta.
+
+Majcie na ŭvazie, što byli adkinuty paviedamliennia toĺki hetaha servisu.
+Paviedamlienni inšych servisaŭ zastalisia.
+
+Miaža, paslia jakoj paviedamlienni buduć adkinuty, naladžvajecca z
+dapamohaj RateLimitIntervalSec= i RateLimitBurst= u fajlie
+/etc/systemd/journald.conf. Hliadzicie journald.conf(5) dlia detaliej.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Paviedamlienni stračany
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Paviedamlienni jadra byli stračany, tak jak sistema žurnaliavannia nie paspiela
+ich apracavać.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Praces @COREDUMP_PID@ (@COREDUMP_COMM@) skinuŭ damp pamiaci
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Praces @COREDUMP_PID@ (@COREDUMP_COMM@) razbiŭsia i skinuŭ damp pamiaci.
+
+Zvyčajna heta sviedčyć ab pamylcy ŭ prahramnym kodzie.
+Rekamiendujecca paviedamić ab hetym raspracoŭnikam.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Novaja siesija № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Novaja siesija z № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@.
+
+Lidar hetaj siesii pad № @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Siesija № @SESSION_ID@ spyniena
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Siesija № @SESSION_ID@ spyniena.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Dastupna novaje pracoŭnaje miesca № @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Novaje pracoŭnaje miesca № @SEAT_ID@ naladžana i dastupna dlia vykarystannia.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Pracoŭnaje miesca № @SEAT_ID@ vydaliena
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Pracoŭnaje miesca № @SEAT_ID@ vydaliena i boĺš nie dastupna.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Čas zmienieny
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Sistemny hadzinnik zmienieny na @REALTIME@ mikrasiekund ad 1 studzienia 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Časavy pojas zmienieny na @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Sistemny časavy pojas zmienieny na @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Zapusk sistemy zaviaršyŭsia
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Usie sistemnyja servisy, nieabchodnyja dlia zahruzki sistemy, paspiachova
+zapuscilisia. Majcie na ŭvazie, što heta nie značyć, što mašyna ničoha nie
+robić. Mahčyma, niekatoryja servisy jašče inicyjalizirujucca.
+
+Na zapusk jadra spatrebilasia @KERNEL_USEC@ mikrasiekund.
+
+Na zapusk pačatkovaha RAM-dyska spatrebilasia @INITRD_USEC@ mikrasiekund.
+
+Na zapusk sistemnych servisaŭ spatrebilasia @USERSPACE_USEC@ mikrasiekund.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Sistema pierajšla ŭ stan snu @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Ciapier sistema pierajšla u stan snu @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Sistema vyjšla sa stana snu @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Ciapier sistema vyjšla sa stana snu @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Sistema zaviaršaje rabotu
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Pačaŭsia praces vykliučennia sistemy.
+Spyniajucca ŭsie sistemnyja servisy i demantujucca fajlavyja sistemy.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Junit @UNIT@ zapuskajecca
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Pačaŭsia praces zapusku junita @UNIT@.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Junit @UNIT@ zapusciŭsia
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Praces zapusku junita @UNIT@ zavieršany.
+
+Vynik: @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Junit @UNIT@ spyniajecca
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Pačaŭsia praces spyniennia junita @UNIT@.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Junit @UNIT@ spynieny
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Praces spyniennia junita @UNIT@ zavieršany.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Zboj junita @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Zboj junita @UNIT@.
+
+Vynik: @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Junit @UNIT@ pieračytvaje svaju kanfihuracyju
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Junit @UNIT@ pačaŭ pieračytvać svaju kanfihuracyju.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Junit @UNIT@ pieračytaŭ svaju kanfihuracyju
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Junit @UNIT@ pieračytaŭ svaju kanfihuracyju.
+
+Vynik: @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Praces @EXECUTABLE@ nie moža być vykanany
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Praces @EXECUTABLE@ nie moža być vykanany ŭ vyniku zboju.
+
+Jon viarnuŭ pamylku numar @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Sibject: Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog servis, jaki
+vykonvajecca paralieĺna z journald. Zvyčajna heta značyć, što
+realizacyja syslog nie paspiavaje apracavać paviedamlienni z nieabchodnaj
+chutkasciu.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Kropka mantavannia nie pustaja
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Kataloh @WHERE@ ukazany jak kropka mantavannia (druhoje polie ŭ /etc/fstab
+ci Where= polie ŭ fajlie junita systemd) i nie pusty. Heta nie pieraškadžaje
+mantavanniu, alie isnujučyja ŭ im fajly buduć niedastupny. Dlia dostupu da
+ich, kali laska, zmantujcie hetuju fajlavuju sistemu ŭ inšaje miesca.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Virtuaĺnaja mašyna abo kantejnier zapusciŭsia
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ zapuscilasia i
+hatova dlia vykarystannia.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Virtuaĺnaja mašyna abo kantejnier spynieny
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ spyniena.
diff --git a/src/grp-journal/catalog/systemd.bg.catalog b/src/grp-journal/catalog/systemd.bg.catalog
new file mode 100644
index 0000000000..30246c0bbe
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.bg.catalog
@@ -0,0 +1,324 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2016 Alexander Shopov <ash@kambanaria.org>
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+
+# The catalog format is documented on
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Журналният процес е пуснат
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Журналният процес на системата е стартирал, отворил е журналните файлове
+за запис и може да приема заявки.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Журналният процес е спрян
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Журналният процес на системата е спрян, затворени са всички отворени
+журнални файлове.
+
+-- ec387f577b844b8fa948f33cad9a75e6
+Subject: Пространството върху диска заето от журналните файлове
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@JOURNAL_NAME@ (@JOURNAL_PATH@) в момента заема @CURRENT_USE_PRETTY@.
+Максималният зададен размер е @MAX_USE_PRETTY@.
+Свободни се оставят поне @DISK_KEEP_FREE_PRETTY@ (от текущо наличните @DISK_AVAILABLE_PRETTY@).
+Максималният наложен размер е @LIMIT_PRETTY@, от който @AVAILABLE_PRETTY@ са свободни.
+
+Настройките за максималния размер на журнала върху диска се
+управляват чрез директивите „SystemMaxUse=“, „SystemKeepFree=“,
+„SystemMaxFileSize=“, „RuntimeMaxUse=“, „RuntimeKeepFree=“ и
+„RuntimeMaxFileSize=“ във файла „/etc/systemd/journald.conf“.
+За повече информация прегледайте „journald.conf(5)“ от ръководството.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Съобщенията от някоя услуга не са допуснати
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Някоя услуга генерира прекалено много съобщения за кратък период.
+Част само от нейните съобщения са отхвърляни.
+
+Съобщенията от другите услуги не са засегнати.
+
+Настройките за максималния брой съобщения, които ще се обработят, се
+управляват чрез директивите „RateLimitInterval=“ и „RateLimitBurst=“ във
+файла „/etc/systemd/journald.conf“. За повече информация прегледайте
+„journald.conf(5)“ от ръководството.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Пропуснати журнални съобщения
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Някои от съобщенията на ядрото може и да са пропуснати, защото системата не
+смогваше да ги обработи достатъчно бързо.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Процес № @COREDUMP_PID@ (@COREDUMP_COMM@) запази освободената памет
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Процес № @COREDUMP_PID@ (@COREDUMP_COMM@) заби, представянето му в паметта
+бе запазено.
+
+Най-често това се дължи на грешка в забилата програма и следва да я
+докладвате на създателите на програмата.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Създадена е нова сесия № @SESSION_ID@ за потребителя „@USER_ID@“
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+За потребителя „@USER_ID@“ е създадена нова сесия № @SESSION_ID@.
+
+Водещият процес на сесията е: @LEADER@
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Сесия № @SESSION_ID@ приключи
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Сесия № @SESSION_ID@ приключи работа.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Налично е ново работно място № @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Новото работно място № @SEAT_ID@ е настроено и готово за работа.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Работното място № @SEAT_ID@ е премахнато
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Работното място № @SEAT_ID@ вече не е налично.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Смяна на системното време
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Часовникът на системата е сверен да сочи @REALTIME@ микросекунди след
+1 януари 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Смяна на часовия пояс да е „@TIMEZONE@“
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Часовият пояс на системата е сменен на „@TIMEZONE@“.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Стартирането на системата завърши
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Успешно са стартирали всички услуги, които са посочени за задействане при
+стартиране на системата. Това не означава, че системата бездейства, защото
+някои от услугите може да извършват специфични действия при стартиране.
+
+Стартирането на ядрото отне @KERNEL_USEC@ микросекунди.
+
+Стартирането на RAM диска за първоначално зареждане отне @INITRD_USEC@
+микросекунди.
+
+Стартирането на потребителските програми отне @USERSPACE_USEC@ микросекунди.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Системата е приспана на ниво „@SLEEP@“
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системата премина в състояние на приспиване „@SLEEP@“.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Системата се събуди след приспиване на ниво„@SLEEP@“
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системата се събуди от състояние на приспиване „@SLEEP@“.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Започна процедура на спиране на системата
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Започна процедурата на Systemd за спиране на системата. Всички процеси и
+услуги се спират, всички файлови системи се демонтират.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Модул „@UNIT@“ се стартира
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Модулът „@UNIT@“ се стартира в момента
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Модул „@UNIT@“ вече е стартиран
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Стартирането на модул „@UNIT@“ завърши.
+
+Резултатът е: @RESULT@
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Модул „@UNIT@“ се спира
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Модулът „@UNIT@“ се спира в момента.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Модул „@UNIT@“ вече е спрян
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Спирането на модул „@UNIT@“ завърши.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Модулът „@UNIT@“ не успя да стартира
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Модулът „@UNIT@“ не успя да стартира.
+
+Резултатът е: @RESULT@
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Модулът „@UNIT@“ започна презареждане на настройките си
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Модулът „@UNIT@“ започна презареждане на настройките си.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Модулът „@UNIT@“ завърши презареждането на настройките си
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Модулът „@UNIT@“ завърши презареждането на настройките си.
+
+Резултатът e: @RESULT@
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Програмата „@EXECUTABLE@“ не успя да се стартира
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Програмата „@EXECUTABLE@“ не успя да се стартира.
+
+Върнатият номер на грешка е: @ERRNO@
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Поне едно съобщение не бе препратено към syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Поне едно съобщение не бе препратено към журналната услуга syslog, която
+работи успоредно с journald.
+
+Най-често това указва, че тази реализация на syslog не може да поеме текущия
+обем съобщения.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Точката за монтиране не е празна
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Директорията „@WHERE@“ не е празна.
+
+Тя е указана като точка за монтиране — или като второ поле във файла
+„/etc/fstab“, или чрез директивата „Where=“ в някой от файловете за
+модул на Systemd.
+
+Това не пречи на самото монтиране, но вече съществуващите там файлове и
+директории няма да се виждат повече, освен ако ръчно не монтирате тази
+непразна директория някъде другаде.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Стартирана е виртуална машина или контейнер
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Виртуалната машина „@NAME@“ с идентификатор на водещия процес @LEADER@
+е стартирана и готова за работа.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Спряна е виртуална машина или контейнер
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Виртуалната машина „@NAME@“ с идентификатор на водещия процес @LEADER@
+е спряна.
+
+-- 36db2dfa5a9045e1bd4af5f93e1cf057
+Subject: Режимът DNSSEC е изключен, защото сървърът не го поддържа
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8) resolved.conf(5)
+
+Локалната услуга за имена (systemd-resolved.service) установи, че
+настроения сървър за DNS не поддържа DNSSEC, затова този режим е изключен.
+
+Това се случва, когато директивата „DNSSEC=allow-downgrade“ е включена във
+файла „resolved.conf“ и зададеният сървър за DNS не е съвместим с DNSSEC.
+
+Внимавайте, защото това може да позволи атака, при която трета страна ви
+връща отговори, които да предизвикат понижаването на сигурността от DNSSEC
+до DNS.
+
+Такова събитие означава, че или сървърът за DNS не е съвместим с DNSSEC,
+или някой успешно ви е атакувал за понижаване на сигурността на имената.
+
+-- 1675d7f172174098b1108bf8c7dc8f5d
+Subject: Неуспешна проверка на DNSSEC
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Заявка или запис в DNS не издържа проверка с DNSSEC.
+
+Това обикновено показва вмешателство на трета страна в канала ви за връзка.
+
+-- 4d4408cfd0d144859184d1e65d7c8a65
+Subject: Анулирана доверена котва в DNSSEC
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Анулирана е доверена котва за DNSSEC и трябва да настроите нова.
+
+Понякога новата идва с обновяване на системата.
diff --git a/src/grp-journal/catalog/systemd.catalog b/src/grp-journal/catalog/systemd.catalog
new file mode 100644
index 0000000000..90929bca6d
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.catalog
@@ -0,0 +1,334 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+
+# The catalog format is documented on
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: The journal has been started
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The system journal process has started up, opened the journal
+files for writing and is now ready to process requests.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: The journal has been stopped
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The system journal process has shut down and closed all currently
+active journal files.
+
+-- ec387f577b844b8fa948f33cad9a75e6
+Subject: Disk space used by the journal
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@JOURNAL_NAME@ (@JOURNAL_PATH@) is currently using @CURRENT_USE_PRETTY@.
+Maximum allowed usage is set to @MAX_USE_PRETTY@.
+Leaving at least @DISK_KEEP_FREE_PRETTY@ free (of currently available @DISK_AVAILABLE_PRETTY@ of disk space).
+Enforced usage limit is thus @LIMIT_PRETTY@, of which @AVAILABLE_PRETTY@ are still available.
+
+The limits controlling how much disk space is used by the journal may
+be configured with SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=,
+RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings in
+/etc/systemd/journald.conf. See journald.conf(5) for details.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Messages from a service have been suppressed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+A service has logged too many messages within a time period. Messages
+from the service have been dropped.
+
+Note that only messages from the service in question have been
+dropped, other services' messages are unaffected.
+
+The limits controlling when messages are dropped may be configured
+with RateLimitIntervalSec= and RateLimitBurst= in
+/etc/systemd/journald.conf. See journald.conf(5) for details.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Journal messages have been missed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Kernel messages have been lost as the journal system has been unable
+to process them quickly enough.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Process @COREDUMP_PID@ (@COREDUMP_COMM@) crashed and dumped core.
+
+This usually indicates a programming error in the crashing program and
+should be reported to its vendor as a bug.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1 de
+Subject: Speicherabbild für Prozess @COREDUMP_PID@ (@COREDUMP_COMM) generiert
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Prozess @COREDUMP_PID@ (@COREDUMP_COMM@) ist abgebrochen worden und
+ein Speicherabbild wurde generiert.
+
+Üblicherweise ist dies ein Hinweis auf einen Programmfehler und sollte
+als Fehler dem jeweiligen Hersteller gemeldet werden.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: A new session @SESSION_ID@ has been created for user @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+A new session with the ID @SESSION_ID@ has been created for the user @USER_ID@.
+
+The leading process of the session is @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Session @SESSION_ID@ has been terminated
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+A session with the ID @SESSION_ID@ has been terminated.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: A new seat @SEAT_ID@ is now available
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+A new seat @SEAT_ID@ has been configured and is now available.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Seat @SEAT_ID@ has now been removed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+A seat @SEAT_ID@ has been removed and is no longer available.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Time change
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The system clock has been changed to @REALTIME@ microseconds after January 1st, 1970.
+
+-- c7a787079b354eaaa9e77b371893cd27 de
+Subject: Zeitänderung
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Die System-Zeit wurde geändert auf @REALTIME@ Mikrosekunden nach dem 1. Januar 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Time zone change to @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The system timezone has been changed to @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: System start-up is now complete
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+All system services necessary queued for starting at boot have been
+successfully started. Note that this does not mean that the machine is
+now idle as services might still be busy with completing start-up.
+
+Kernel start-up required @KERNEL_USEC@ microseconds.
+
+Initial RAM disk start-up required @INITRD_USEC@ microseconds.
+
+Userspace start-up required @USERSPACE_USEC@ microseconds.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: System sleep state @SLEEP@ entered
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The system has now entered the @SLEEP@ sleep state.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: System sleep state @SLEEP@ left
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The system has now left the @SLEEP@ sleep state.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: System shutdown initiated
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemd shutdown has been initiated. The shutdown has now begun and
+all system services are terminated and all file systems unmounted.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Unit @UNIT@ has begun start-up
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has begun starting up.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Unit @UNIT@ has finished start-up
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has finished starting up.
+
+The start-up result is @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Unit @UNIT@ has begun shutting down
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has begun shutting down.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Unit @UNIT@ has finished shutting down
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has finished shutting down.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Unit @UNIT@ has failed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has failed.
+
+The result is @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Unit @UNIT@ has begun reloading its configuration
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has begun reloading its configuration
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Unit @UNIT@ has finished reloading its configuration
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Unit @UNIT@ has finished reloading its configuration
+
+The result is @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Process @EXECUTABLE@ could not be executed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The process @EXECUTABLE@ could not be executed and failed.
+
+The error number returned by this process is @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: One or more messages could not be forwarded to syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+One or more messages could not be forwarded to the syslog service
+running side-by-side with journald. This usually indicates that the
+syslog implementation has not been able to keep up with the speed of
+messages queued.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Mount point is not empty
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The directory @WHERE@ is specified as the mount point (second field in
+/etc/fstab or Where= field in systemd unit file) and is not empty.
+This does not interfere with mounting, but the pre-exisiting files in
+this directory become inaccessible. To see those over-mounted files,
+please manually mount the underlying file system to a secondary
+location.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: A virtual machine or container has been started
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The virtual machine @NAME@ with its leader PID @LEADER@ has been
+started is now ready to use.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: A virtual machine or container has been terminated
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+The virtual machine @NAME@ with its leader PID @LEADER@ has been
+shut down.
+
+-- 36db2dfa5a9045e1bd4af5f93e1cf057
+Subject: DNSSEC mode has been turned off, as server doesn't support it
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8) resolved.conf(5)
+
+The resolver service (systemd-resolved.service) has detected that the
+configured DNS server does not support DNSSEC, and DNSSEC validation has been
+turned off as result.
+
+This event will take place if DNSSEC=allow-downgrade is configured in
+resolved.conf and the configured DNS server is incompatible with DNSSEC. Note
+that using this mode permits DNSSEC downgrade attacks, as an attacker might be
+able turn off DNSSEC validation on the system by inserting DNS replies in the
+communication channel that result in a downgrade like this.
+
+This event might be indication that the DNS server is indeed incompatible with
+DNSSEC or that an attacker has successfully managed to stage such a downgrade
+attack.
+
+-- 1675d7f172174098b1108bf8c7dc8f5d
+Subject: DNSSEC validation failed
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+A DNS query or resource record set failed DNSSEC validation. This is usually
+indication that the communication channel used was tampered with.
+
+-- 4d4408cfd0d144859184d1e65d7c8a65
+Subject: A DNSSEC trust anchor has been revoked
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+A DNSSEC trust anchor has been revoked. A new trust anchor has to be
+configured, or the operating system needs to be updated, to provide an updated
+DNSSEC trust anchor.
diff --git a/src/grp-journal/catalog/systemd.da.catalog b/src/grp-journal/catalog/systemd.da.catalog
new file mode 100644
index 0000000000..093e8139da
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.da.catalog
@@ -0,0 +1,261 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Danish translation
+
+# The catalog format is documented on
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Journalen er blevet startet
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+System-journal processen har startet op, åbnet journal filerne for
+tilskrivning og er nu klar til at modtage anmodninger.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Journalen er blevet stoppet
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+System-journal processen er stoppet og har lukket alle aktive journal
+filer.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Beskeder fra en service er blevet undertrykt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+En service har logget for mange beskeder inden for en given tidsperiode.
+Beskeder fra omtalte service er blevet smidt væk.
+
+Kun beskeder fra omtalte service er smidt væk. Beskeder fra andre
+services er ikke påvirket.
+
+Grænsen for hvornår beskeder bliver smidt væk kan konfigureres
+med RateLimitIntervalSec= og RateLimitBurst= i
+/etc/systemd/journald.conf. Se journald.conf(5) for detaljer herom.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Journal beskeder er gået tabt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Kernel beskeder er gået tabt da journal systemet ikke har været i stand
+til at håndtere dem hurtigt nok.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Fejl-fil genereret for process @COREDUMP_PID@ (@COREDUMP_COMM@)
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Process @COREDUMP_PID@ (@COREDUMP_COMM@) har lukket ned og genereret en
+fejl-fil.
+
+Dette indikerer som regel en programmeringsfejl i det nedlukkede program
+og burde blive reporteret som en bug til folkene bag
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: En ny session @SESSION_ID@ er blevet lavet for bruger @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+En ny session med ID @SESSION_ID@ er blevet lavet for brugeren @USER_ID@.
+
+Den ledende process for sessionen er @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Session @SESSION_ID@ er blevet lukket ned
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+En session med ID @SESSION_ID@ er blevet lukket ned.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: En ny arbejdsstation $SEAT_ID@ er nu tilgængelig
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+En ny arbejdsstation @SEAT_ID@ er blevet konfigureret og er nu tilgængelig.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Arbejdsstation @SEAT_ID@ er nu blevet fjernet
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+En arbejdsstation @SEAT_ID@ er blevet fjernet og er ikke længere tilgængelig.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Tidsændring
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemtiden er blevet ændret til @REALTIME@ mikrosekunder efter d. 1. Januar 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Tidszoneændring til @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Tidszonen for systemet er blevet ændret til @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Opstart af systemet er nu fuldført
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Alle system services i kø til at køre ved opstart, er blevet startet
+med success. Bemærk at dette ikke betyder at maskinen er i dvale, da
+services stadig kan være i gang med at færdiggøre deres opstart.
+
+Opstart af kernel tog @KERNEL_USEC@ mikrosekunder.
+
+Opstart af initrd tog @INITRD_USEC@ mikrosekunder.
+
+Opstart af userspace tog @USERSPACE_USEC@ mikrosekunder.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: System slumretilstand @SLEEP@ trådt i kraft
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+System er nu gået i @SLEEP@ slumretilstand.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: System slumretilstand @SLEEP@ forladt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemet har nu forladt @SLEEP@ slumretilstand.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Systemnedlukning påbegyndt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemnedlukning er blevet påbegyndt. Nedlukningen er nu begyndt og
+alle system services er blevet afbrudt og alle filsystemer afmonteret.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Enhed @UNIT@ har påbegyndt opstart
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ er begyndt at starte op.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Enhed @UNIT har færdiggjort opstart
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ er færdig med at starte op.
+
+Resultat for opstart er @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Enhed @UNIT@ har påbegyndt nedlukning
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ har påbegyndt nedlukning.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Enhed @UNIT@ har færdiggjort nedlukning
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ har færdiggjort nedlukning.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Enhed @UNIT@ har fejlet
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ har fejlet.
+
+Resultatet er @RESULT@
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Enhed @UNIT@ har påbegyndt genindlæsning af sin konfiguration
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ er begyndt at genindlæse sin konfiguration
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Enhed @UNIT@ har færdiggjort genindlæsning af sin konfiguration
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Enhed @UNIT@ er færdig med at genindlæse sin konfiguration
+
+Resultatet er: @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Process @EXECUTABLE@ kunne ikke eksekveres
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Processen @EXECUTABLE@ kunne ikke eksekveres og fejlede.
+
+Processens returnerede fejlkode er @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Èn eller flere beskeder kunne ikke videresendes til syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Èn eller flere beskeder kunne ikke videresendes til syslog servicen
+der kører side-om-side med journald. Dette indikerer typisk at syslog
+implementationen ikke har kunnet følge med mængden af ventende beskeder.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Monteringspunkt er ikke tomt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Folderen @WHERE@ er specificeret som monteringspunkt (andet felt i
+/etc/fstab eller Where= feltet i systemd enhedsfil) men er ikke tom.
+Dette forstyrrer ikke monteringen, men de pre-eksisterende filer i folderen
+bliver utilgængelige. For at se de over-monterede filer; montér det
+underlæggende filsystem til en anden lokation.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: En virtuel maskine eller container er blevet startet
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet
+startet og er klar til brug.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: En virtuel maskine eller container er blevet afbrudt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet
+nedlukket.
diff --git a/src/grp-journal/catalog/systemd.fr.catalog b/src/grp-journal/catalog/systemd.fr.catalog
new file mode 100644
index 0000000000..0cea629c31
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.fr.catalog
@@ -0,0 +1,320 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2013-2015 Sylvain Plantefève
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# French translation
+
+# Le format du catalogue de messages est décrit (en anglais) içi :
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Le journal a été démarré
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le processus du journal système a démarré, ouvert ses fichiers en écriture
+et est prêt à traiter les requêtes.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Le journal a été arrêté
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le processus du journal système a été arrêté et tous ses fichiers actifs
+ont été fermés.
+
+-- ec387f577b844b8fa948f33cad9a75e6
+Subject: Espace disque utilisé par le journal
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+@JOURNAL_NAME@ (@JOURNAL_PATH@) utilise actuellement @CURRENT_USE_PRETTY@.
+Le maximum autorisé est défini à @MAX_USE_PRETTY@.
+Au moins @DISK_KEEP_FREE_PRETTY@ doivent être laissés libres
+(sur @DISK_AVAILABLE_PRETTY@ d'espace disque actuellement libre).
+La limite appliquée est donc @LIMIT_PRETTY@, dont @AVAILABLE_PRETTY@
+sont toujours disponibles.
+
+Les limites définissant la quantité d'espace disque que peut utiliser le
+journal peuvent être configurées avec les paramètres SystemMaxUse=,
+SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=,
+RuntimeMaxFileSize= dans le fichier /etc/systemd/journald.conf.
+Voir journald.conf(5) pour plus de détails.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Des messages d'un service ont été supprimés
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Un service a essayé d'enregistrer un trop grand nombre de messages sur un
+intervalle de temps donné. Des messages de ce service ont été évincés.
+
+Notez que seuls des messages de ce service ont été évincés, les messages des
+autres services ne sont pas affectés.
+
+Les limites définissant ce comportement peuvent être configurées avec les
+paramètres RateLimitIntervalSec= et RateLimitBurst= dans le fichier
+/etc/systemd/journald.conf. Voir journald.conf(5) pour plus de détails.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Des messages du journal ont été manqués
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Des messages du noyau ont été manqués car le journal système n'a pas été
+capable de les traiter suffisamment vite.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a généré un fichier « core »
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a planté et généré un fichier « core ».
+
+Cela indique généralement une erreur de programmation dans le programme
+incriminé, et cela devrait être notifié à son concepteur comme un défaut (bug).
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Une nouvelle session @SESSION_ID@ a été créée pour l'utilisateur @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Une nouvelle session a été créée pour l'utilisateur @USER_ID@ avec
+l'identifiant (ID) @SESSION_ID@.
+
+Le processus maître de la session est @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: La session @SESSION_ID@ s'est terminée
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+La session d'identifiant (ID) @SESSION_ID@ s'est terminée.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Un nouveau poste (seat) @SEAT_ID@ est disponible
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Un nouveau poste (seat) @SEAT_ID@ a été configuré et est maintenant
+disponible.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Le poste (seat) @SEAT_ID@ a été retiré
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Le poste (seat) @SEAT_ID@ a été retiré et n'est plus disponible.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Changement d'heure
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'horloge système a été modifiée et positionnée à @REALTIME@ microsecondes
+après le 1er janvier 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Fuseau horaire modifié en @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le fuseau horaire du système a été modifié et positionné à @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Le démarrage du système est terminé
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Tous les services nécessaires au démarrage du système ont été lancés avec
+succès. Notez que cela ne signifie pas que le système est maintenant au
+repos, car des services peuvent encore être en train de terminer leur
+démarrage.
+
+Le chargement du noyau a nécessité @KERNEL_USEC@ microsecondes.
+
+Le chargement du « RAM disk » initial a nécessité @INITRD_USEC@ microsecondes.
+
+Le chargement de l'espace utilisateur a nécessité @USERSPACE_USEC@ microsecondes.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Le système entre dans l'état de repos (sleep state) @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le système est maintenant à l'état de repos (sleep state) @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Le système sorti de l'état de repos (sleep state) @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le système est maintenant sorti de l'état de repos (sleep state) @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Arrêt du système amorcé
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'arrêt du système a été amorcé. L'arrêt a maintenant commencé, tous les
+services du système sont terminés et tous les systèmes de fichiers sont
+démontés.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: L'unité (unit) @UNIT@ a commencé à démarrer
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a commencé à démarrer.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: L'unité (unit) @UNIT@ a terminé son démarrage
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a terminé son démarrage, avec le résultat @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: L'unité (unit) @UNIT@ a commencé à s'arrêter
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a commencé à s'arrêter.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: L'unité (unit) @UNIT@ a terminé son arrêt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a terminé son arrêt.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: L'unité (unit) @UNIT@ a échoué
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a échoué, avec le résultat @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: L'unité (unit) @UNIT@ a commencé à recharger sa configuration
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a commencé à recharger sa configuration.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: L'unité (unit) @UNIT@ a terminé de recharger configuration
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unité (unit) @UNIT@ a terminé de recharger configuration,
+avec le résultat @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Le processus @EXECUTABLE@ n'a pas pu être exécuté
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le processus @EXECUTABLE@ n'a pas pu être exécuté, et a donc échoué.
+
+Le code d'erreur renvoyé est @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Un ou plusieurs messages n'ont pas pu être transmis à syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Un ou plusieurs messages n'ont pas pu être transmis au service syslog
+s'exécutant conjointement avec journald. Cela indique généralement que
+l'implémentation de syslog utilisée n'a pas été capable de suivre
+la cadence du flux de messages.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Le point de montage n'est pas vide
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Le répertoire @WHERE@ est spécifié comme point de montage (second champ du
+fichier /etc/fstab, ou champ Where= dans une unité (unit) systemd) et n'est
+pas vide.
+Cela ne perturbe pas le montage du système de fichiers, mais les fichiers
+préalablement présents dans ce répertoire sont devenus inaccessibles.
+Pour atteindre ces fichiers, veuillez monter manuellement le système de
+fichiers sous-jacent à un autre emplacement.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Une machine virtuelle ou un conteneur (container) a été démarré
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+La machine virtuelle @NAME@ a été démarrée avec le PID maître @LEADER@,
+et est maintenant prête à l'emploi.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Une machine virtuelle ou un conteneur (container) a été arrêté
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+La machine virtuelle @NAME@ avec le PID maître @LEADER@ a été arrêtée.
+
+-- 36db2dfa5a9045e1bd4af5f93e1cf057
+Subject: Le mode DNSSEC a été désactivé, car il n'est pas supporté par le serveur
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8) resolved.conf(5)
+
+Le service de résolution (systemd-resolved.service) a détecté que le serveur
+DNS configuré ne supporte pas DNSSEC, et la validation DNSSEC a donc été
+désactivée.
+
+Cet évènement se produit si DNSSEC=allow-downgrade est configuré dans
+resolved.conf et que le serveur DNS configuré n'est pas compatible avec
+DNSSEC.
+Veuillez noter que ce mode permet des attaques de rétrogradation DNSSEC,
+car un attaquant peut être capable de désactiver la validation DNSSEC sur
+le système en injectant des réponses DNS dans le canal de communication.
+
+Cet évènement indique que le serveur DNS est effectivement incompatible avec
+DNSSEC, ou qu'un attaquant a peut-être conduit une telle attaque avec succès.
+
+-- 1675d7f172174098b1108bf8c7dc8f5d
+Subject: La validation DNSSEC a échoué
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Une requête ou une ressource DNS n'a pas passé la validation DNSSEC.
+Ceci est généralement une indication que le canal de communication a été
+altéré.
+
+-- 4d4408cfd0d144859184d1e65d7c8a65
+Subject: Une ancre de confiance DNSSEC a été révoquée
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Une ancre de confiance DNSSEC a été révoquée. Une nouvelle ancre de
+confiance doit être configurée, ou le système d'exploitation a besoin
+d'être mis à jour, pour fournir une version à jour de l'ancre de confiance.
diff --git a/src/grp-journal/catalog/systemd.hr.catalog b/src/grp-journal/catalog/systemd.hr.catalog
new file mode 100644
index 0000000000..350988dd87
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.hr.catalog
@@ -0,0 +1,314 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Croatian translation
+
+# Format kataloga je dokumentiran na
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# Za pojašnjenje zašto ovo radimo, posjetite https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: journal je pokrenut
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Journal proces sustava se pokrenuo, otvorio je journal
+ datoteke za upis i spreman je za obradu zahtjeva.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: journal je zaustavljen
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Journal proces sustava je isključio i zatvorio sve trenutno
+aktivne journal datoteke.
+
+-- ec387f577b844b8fa948f33cad9a75e6
+Subject: Diskovni prostor koji koristi journal
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@JOURNAL_NAME@ (@JOURNAL_PATH@) trenutno koristi @CURRENT_USE_PRETTY@.
+Najveća dopuštena upotreba je postavljena na @MAX_USE_PRETTY@.
+Ostavljam najmanje @DISK_KEEP_FREE_PRETTY@ slobodno (trenutno dostupno @DISK_AVAILABLE_PRETTY@ diskovnog prostora).
+Prisilno ograničenje upotrebe je @LIMIT_PRETTY@, od kojeg je @AVAILABLE_PRETTY@ još dostupno.
+
+Ograničenja kontroliraju koliko diskovnog prostora koristi journal mogu
+se podesiti sa SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=,
+RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings u
+/etc/systemd/journald.conf. Pogledajte journald.conf(5) za više pojedinosti.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Poruka iz usluge je potisnuta
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Usluga je prijavila previše poruka u određenom vremenskom razdoblju. Poruke
+iz usluge su odbačene.
+
+Zapamtite da samo poruke iz usluge u upitu su
+odbačene, ostale poruke usluga nisu zahvaćene.
+
+Ograničenja koja kontroliraju kada je poruka odbačena mogu se podesiti
+sa RateLimitIntervalSec= i RateLimitBurst= u
+/etc/systemd/journald.conf. Pogledajte journald.conf(5) za više pojedinosti.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Journal poruka je propuštena
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Kernel poruka je izgubljena zato jer ih journal sustav nije mogao
+dovoljno brzo obraditi.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) je izbacio jezgru
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Proces @COREDUMP_PID@ (@COREDUMP_COMM@) se srušio i izbacio jezgru.
+
+Rušenje programa je uobičajeno uzrokovano greškom u programiranju i
+trebalo bi se prijaviti razvijatelju kao greška.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Nova sesija @SESSION_ID@ je stvorena za korisnika @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Nova sesija sa ID @SESSION_ID@ je stvorena za korisnika @USER_ID@.
+
+Glavni proces sesije je @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Sesija @SESSION_ID@ je prekinuta
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Sesija sa ID @SESSION_ID@ je prekinuta.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Novo sjedište @SEAT_ID@ je sada dostupno
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Novo sjedište @SEAT_ID@ je podešeno i sada je dostupno.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Sjedište @SEAT_ID@ je sada uklonjeno
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Sjedište @SEAT_ID@ je uklonjeno i više nije dostupno.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Vrijeme promjene
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Sat sustava je promijenjen na @REALTIME@ microsekundi nakon 1. Siječnja, 1970 godine.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Vremenska zona je promijenjena u @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Vremenska zona je promijenjena u @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Pokretanje sustava je sada završeno
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Sve usluge sustava koje su zadane za pokretanje pri pokretanju sustava
+su uspješno pokrenute. Zapamtite da ovo ne znači da sada računalo
+miruje zato jer se neke usluge još uvijek mogu pokretati.
+
+Pokretanje kernela zahtijeva @KERNEL_USEC@ mikrosekundi.
+
+Pokretanje početnog RAM diska zahtijeva @INITRD_USEC@ mikrosekundi.
+
+Pokretanje prostora korisnika zahtijeva @USERSPACE_USEC@ mikrosekundi.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Pokrenuto je stanje spavanja @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Sustav je sada pokrenuo stanje spavanja @SLEEP@
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Završeno je stanje spavanja @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Sustav je sada završio stanje spavanja @SLEEP@
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Pokrenuto je isključivanje sustava
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Pokrenuto je isključivanje sustava. Isključivanje je sada pokrenuto,
+sve usluge sustava su prekinute i svi datotečni sustavi su odmontirani.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Jedinica @UNIT@ je započela pokretanje
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ je započela pokretanje.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Jedinica @UNIT@ je završila pokretanje
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ je završila pokretanje.
+
+Rezultat pokretanja je @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Jedinica @UNIT@ je započela isključivanje
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ je započela isključivanje.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Jedinica @UNIT@ je završila isključivanje
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ je završila isključivanje.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Jedinica @UNIT@ nije uspjela
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ nije uspjela.
+
+Rezultat je @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Jedinica @UNIT@ je započela ponovno učitavati podešavanja
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ je započela ponovno učitavati podešavanja
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Jedinica @UNIT@ je završila ponovno učitavati podešavanja
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedinica @UNIT@ je završila ponovno učitavati podešavanja
+
+Rezultat je @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Proces @EXECUTABLE@ se ne može pokrenuti
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Proces @EXECUTABLE@ se ne može pokrenuti i nije uspio.
+
+Broj greške vraćen ovim procesom je @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Jedna ili više poruka se ne mogu proslijediti u dnevnik sustava
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jedna ili više poruka se ne mogu proslijediti u dnevnik sustava, usluge
+su pokrenute istovremeno s journalom. Ovo uobičajeno označava da
+implementacija dnevnika sustava ne može slijediti brzinu
+zahtjeva poruka.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Točka montiranja nije prazna
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Direktorij @WHERE@ je određen za točku montiranja (drugi redak u
+/etc/fstab ili Where= redak u datoteci systemd jedinice) i nije prazan.
+To ne utječe na montiranje, ali postojeće datoteke u ovom direktoriju
+postaju nedostupne. Kako bi vidjeli datoteke preko kojih je montirano,
+ručno montirajte osnovni datotečni sustav na drugu lokaciju.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Virtualni stroj ili spremnik su pokrenuti
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Virtualni stroj @NAME@ sa vodećim @LEADER@ PID-om je
+pokrenut i spreman je za korištenje.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Virtualni stroj ili spremnik su isključeni
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Virtualni stroj @NAME@ sa vodećim PID-om @LEADER@ je
+isključen.
+
+-- 36db2dfa5a9045e1bd4af5f93e1cf057
+Subject: DNSSEC način je isključen, jer ga poslužitelj ne podržava
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8) resolved.conf(5)
+
+Usluga razrješavanja (systemd-resolved.service) je otkrila da
+podešeni DNS poslužitelj ne podržava DNSSEC, i DNSSEC, kao rezultat
+provjera je isključena.
+
+Ovaj događaj će zauzeti mjesto ako je DNSSEC=allow-downgrade podešen u
+resolved.conf i podešeni DNS poslužitelj je nekompatibilan s DNSSEC. Zapamtite
+da korištenje ovog načina dopušta povećanje DNSSEC napada, napadač bi mogao
+isključiti DNSSEC provjeru na sustavu umetanjem DNS odgovora u
+komunikacijski kanal što rezultira povećanjem napada poput ovog.
+
+Ovaj događaj bi mogao označavati da je DNS poslužitelj uistinu nekompatibilan s
+DNSSEC ili da je napadač uspješno izvršio takav napad.
+
+-- 1675d7f172174098b1108bf8c7dc8f5d
+Subject: DNSSEC provjera neuspješna
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+DNS zahtjev ili snimak resursa nije prošao DNSSEC provjeru. To uobičajeno
+označava da je komunikacijski kanal mijenjan.
+
+-- 4d4408cfd0d144859184d1e65d7c8a65
+Subject: DNSSEC pouzdano sidro je opozvano
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+A DNSSEC trust anchor has been revoked. A new trust anchor has to be
+configured, or the operating system needs to be updated, to provide an updated
+DNSSEC trust anchor.
diff --git a/src/grp-journal/catalog/systemd.hu.catalog b/src/grp-journal/catalog/systemd.hu.catalog
new file mode 100644
index 0000000000..68e8c2572e
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.hu.catalog
@@ -0,0 +1,262 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2016 Gabor Kelemen
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+
+# The catalog format is documented on
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: A napló elindult
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszernapló folyamat elindult, megnyitotta írásra a naplófájlokat,
+és most készen áll kérések feldolgozására.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: A napló leállt
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszernapló folyamat leállt, és bezárt minden jelenleg aktív naplófájlt.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Egy szolgáltatás üzenetei elnémítva
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Egy szolgáltatás túl sok üzenetet naplózott adott idő alatt. A
+szolgáltatástól származó üzenetek eldobásra kerültek.
+
+Ne feledje, hogy csak a kérdéses szolgáltatás üzenetei kerültek eldobásra,
+ más szolgáltatások üzeneteit ez nem befolyásolja.
+
+Az üzenetek eldobását vezérlő korlátok az /etc/systemd/journald.conf
+RateLimitIntervalSec= és RateLimitBurst= beállításaival adhatók meg.
+Részletekért lásd a journald.conf(5) man oldalt.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Naplóüzenetek vesztek el
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Kernelüzenetek vesztek el, mert a naplózó rendszer nem tudta elég gyorsan
+feldolgozni azokat.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Egy folyamat összeomlott: @COREDUMP_PID@ (@COREDUMP_COMM@)
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Ez a folyamat: @COREDUMP_PID@ (@COREDUMP_COMM@) összeomlott, és core fájlt
+ írt ki.
+
+Ez általában programozási hibát jelez az összeomló programban, és
+a szállítója felé kell bejelenteni.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Új munkamenet (@SESSION_ID@) létrehozva, felhasználója: @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Létrejött egy új munkamenet @SESSION_ID@ azonosítóval ezen felhasználóhoz:
+@USER_ID@.
+
+A munkamenet vezető folyamata: @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Munkamenet (@SESSION_ID@) befejezve
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+A következő azonosítójú munkamenet befejeződött: @SESSION_ID@.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Elérhető egy új munkaállomás: @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Beállításra kerül és használható egy új munkaállomás: @SEAT_ID@.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: A munkaállomás eltávolítva: @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+A munkaállomás el lett távolítva, és már nem érhető el: @SEAT_ID@
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Időmódosítás
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszeróra beállítva @REALTIME@ ezredmásodpercre 1970. január 1. után.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Időzóna-módosítás erre: @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszer időzónája módosítva lett erre: @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: A rendszer indítása kész
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszerindításkor szükséges indításhoz sorba állított összes
+rendszerszolgáltatás elindult. Ne feledje, hogy ez nem jelenti, hogy a
+gép üresjáratban van, mivel egyes szolgáltatások még az indítás
+befejezésével lehetnek elfoglalva.
+
+A kernel indítása @KERNEL_USEC@ ezredmásodpercet igényelt.
+
+A kiinduló RAM lemez indítása @INITRD_USEC@ ezredmásodpercet igényelt.
+
+A felhasználói programok indítása @USERSPACE_USEC@ ezredmásodpercet igényelt.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: A rendszer „@SLEEP@” alvási állapotba lépett
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszer belépett ebbe az alvási állapotba: @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: A rendszer „@SLEEP@” alvási állapotból kilépett
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A rendszer kilépett ebből az alvási állapotból: @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Rendszer leállítása kezdeményezve
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A systemd leállítása kezdeményezve. A leállítás megkezdődött, minden
+rendszerszolgáltatás befejeződik, minden fájlrendszer leválasztásra kerül.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: A(z) @UNIT@ egység indítása megkezdődött
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység megkezdte az indulást.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: A(z) @UNIT@ egység befejezte az indulást
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység befejezte az indulást
+
+Az indítás eredménye: @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: A(z) @UNIT@ egység megkezdte a leállást
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység megkezdte a leállást.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: A(z) @UNIT@ egység befejezte a leállást
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység befejezte a leállást.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: A(z) @UNIT@ egység hibát jelzett
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység hibát jelzett.
+
+Az eredmény: @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: A(z) @UNIT@ egység megkezdte a beállításainak újratöltését
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység megkezdte a beállításainak újratöltését.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: A(z) @UNIT@ egység befejezte a beállításainak újratöltését
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @UNIT@ egység befejezte a beállításainak újratöltését.
+
+Az eredmény: @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: A folyamat végrehajtása sikertelen: @EXECUTABLE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A folyamat végrehajtása sikertelen volt, és hibát jelzett: @EXECUTABLE@.
+
+A folyamat által visszaadott hibaszám: @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Legalább egy üzenet nem továbbítható a rendszernaplónak
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Legalább egy üzenet nem volt továbbítható a journald-vel párhuzamosan futó
+syslog szolgáltatásnak. Ez általában azt jelenti, hogy a syslog
+megvalósítás nem volt képes lépést tartani a sorba állított
+üzenetek sebességével.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: A csatolási pont nem üres
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A csatolási pontként megadott @WHERE@ könyvtár (második mező az /etc/fstab
+fájlban, vagy a Where= sor a systemd egységfájlban) nem üres. Ez nem
+akadályozza meg a csatolást, de a könyvtárban már meglévő fájlok
+elérhetetlenné válnak. A fájlok láthatóvá tételéhez csatolja
+az azokat tartalmazó fájlrendszert egy másodlagos helyre.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Egy virtuális gép vagy konténer elindult
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) elindult, és
+használatra kész.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Egy virtuális gép vagy konténer befejeződött
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) leállt.
diff --git a/src/grp-journal/catalog/systemd.it.catalog b/src/grp-journal/catalog/systemd.it.catalog
new file mode 100644
index 0000000000..b6fca48221
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.it.catalog
@@ -0,0 +1,254 @@
+# This file is part of systemd.
+#
+# Copyright 2013 Daniele Medri
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Il registro è stato avviato
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Il processo relativo al registro di sistema è stato avviato, ha aperto i
+file in scrittura ed è ora pronto a gestire richieste.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Il registro è stato terminato
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Il processo relativo al registro di sistema è stato terminato e ha chiuso
+tutti i file attivi.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: I messaggi di un servizio sono stati soppressi
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Un servizio ha registrato troppi messaggi in un dato periodo di tempo.
+I messaggi del servizio sono stati eliminati.
+
+Solo i messaggi del servizio indicato sono stati
+eliminati, i messaggi degli altri servizi rimangono invariati.
+
+I limiti oltre i quali i messaggi si eliminano si configurano
+con RateLimitIntervalSec= e RateLimitBurst= in
+/etc/systemd/journald.conf. Vedi journald.conf(5) per maggiori informazioni.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: I messaggi di un servizio sono stati perduti
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+I messaggi del kernel sono stati perduti perché, il registro di sistema
+non è stato in grado di gestirli abbastanza velocemente.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) ha generato un dump.
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) si è bloccato generando un dump.
+
+Questo di solito capita per un errore di programmazione nell'applicazione e
+dovrebbe essere segnalato al vendor come un bug.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: La nuova sessione @SESSION_ID@ è stata creata per l'utente @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Una nuova sessione con ID @SESSION_ID@ è stata creata per l'utente @USER_ID@.
+
+Il processo primario della sessione è @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: La sessione @SESSION_ID@ è terminata
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+La sessione con ID @SESSION_ID@ è terminata.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: La nuova postazione @SEAT_ID@ è ora disponibile
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+La nuova postazione @SEAT_ID@ è stata configurata ed è ora disponibile.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: La postazione @SEAT_ID@ è stata rimossa
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+La postazione @SEAT_ID@ è stata rimossa e non è più disponibile.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Cambio d'orario
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'orologio di sistema è cambiato in @REALTIME@ microsecondi dal 1 gennaio, 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Il fuso orario è cambiato in @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Il fuso orario di sistema è cambiato in @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Avvio del sistema completato.
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Tutti i servizi di sistema richiesti per la fase di avvio sono stati eseguiti
+con successo. Nota che la macchina potrebbe non essere ancora pronta in quanto
+i servizi attivati sono in fase di completamento.
+
+L'avvio del kernel ha richiesto @KERNEL_USEC@ microsecondi.
+
+L'avvio del disco RAM ha richiesto @INITRD_USEC@ microsecondi.
+
+L'avvio dello userspace ha richiesto @USERSPACE_USEC@ microsecondi.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Il sistema è entrato in fase di pausa @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Il sistema è entrato nello stato di pausa @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Il sistema è uscito dalla fase di pausa @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Il sistema è uscito dallo stato di pausa @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Il sistema è in fase di spegnimento
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemd è in fase di spegnimento. Tutti i servizi di sistema
+saranno terminati e tutti i file systems smontati.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: L'unità @UNIT@ inizia la fase di avvio
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ ha iniziato la fase di avvio.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: L'unità @UNIT@ termina la fase di avvio
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ ha terminato la fase di avvio.
+
+La fase di avvio è @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: L'unità @UNIT@ inizia la fase di spegnimento
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ ha iniziato la fase di spegnimento.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: L'unità @UNIT@ termina la fase di spegnimento
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ ha terminato la fase di spegnimento.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: L'unità @UNIT@ è fallita
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ è fallita.
+
+Il risultato è @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: L'unità @UNIT@ inizia a caricare la propria configurazione
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ è iniziata ricaricando la propria configurazione
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: L'unità @UNIT@ termina il caricamento della propria configurazione
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+L'unità @UNIT@ è terminata ricaricando la propria configurazione
+
+Il risultato è @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Il processo @EXECUTABLE@ non può essere eseguito
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Il processo @EXECUTABLE@ non può essere eseguito e termina.
+
+Il numero di errore restituito durante l'esecuzione del processo è @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Uno o più messaggi non possono essere inoltrati a syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Uno o più messaggi non possono essere inviati al servizio syslog
+eseguito in parallelo a journald. Questo di solito capita perché,
+l'implementazione di syslog non sta al passo con la
+velocità dei messaggi accodati.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Il punto di montaggio non è vuoto
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+La directory @WHERE@ è specificata come punto di montaggio (secondo campo
+in /etc/fstab o nel campo Where= del file unità di systemd) e non è vuoto.
+Questo non interferisce con il montaggio, ma i file pre-esistenti in questa
+directory diventano inaccessibili. Per visualizzare i file, si suggerisce
+di montare manualmente il file system indicato in una posizione secondaria.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Avviata macchina virtuale o container
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+La macchina virtuale @NAME@ con PID primario @LEADER@ è stata
+avviata ed è pronta all'uso.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Terminata macchina virtuale o container
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+La macchina virtuale @NAME@ con PID primario @LEADER@ è stata spenta.
diff --git a/src/grp-journal/catalog/systemd.ko.catalog b/src/grp-journal/catalog/systemd.ko.catalog
new file mode 100644
index 0000000000..2fc6b60b1b
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.ko.catalog
@@ -0,0 +1,264 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Korean translation
+
+# The catalog format is documented on
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+#
+# Translator :
+# Seong-ho Cho <darkcircle.0426@gmail.com>, 2015.
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: 저널 시작
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+시스템 저널 프로세스를 시작했고 기록목적으로 저널 파일을 열었으며,
+프로세스 요청을 기다리고 있습니다.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: 저널 멈춤
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+시스템 저널 프로세스를 껐고 현재 활성화 중인 저널 파일을 모두
+닫았습니다.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: 서비스의 메시지를 거절함
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+일정 시간동안 서비스에서 너무 많은 메시지를 기록했습니다.
+서비스에서 오는 메시지를 거절했습니다.
+
+의문점이 있는 서비스로부터 오는 메시지만 거절했음을 참고하십시오
+다른 서비스의 메시지에는 영향을 주지 않습니다.
+
+메시지 거절 제어 제한 값은 /etc/systemd/journald.conf 의
+RateLimitIntervalSec= 변수와 RateLimitBurst= 변수로 설정합니다.
+자세한 내용은 ournald.conf(5)를 살펴보십시오.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: 저널 메시지 놓침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+저널 시스템에서 커널 메시지를 충분히 빠르게 처리할 수 없어 커널
+ 메시지를 잃었습니다.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: 프로세스 @COREDUMP_PID@번 코어 덤프(@COREDUMP_COMM@) 생성함
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+프로세스 @COREDUMP_PID@번 (@COREDUMP_COMM@)이 비정상적으로 끝나
+코어 덤프를 생성했습니다.
+
+보통 비정상 종료 관리 프로그램에서 프로그래밍 오류를 나타내며,
+제작자에게 버그로 보고해야합니다.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: @USER_ID@ 사용자의 새 @SESSION_ID@ 세션 만듦
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+@USER_ID@ 사용자의 새 @SESSION_ID@ 세션을 만들었습니다.
+
+이 세션의 관리 프로세스는 @LEADER@ 입니다.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: @SESSION_ID@ 세션 마침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+@SESSION_ID@ 세션을 끝냈습니다.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: 새 @SEAT_ID@ 시트 사용할 수 있음
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+새 @SEAT_ID@ 시트를 설정했고 사용할 수 있습니다.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: @SEAT_ID@ 시트 제거함
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+@SEAT_ID@ 시트를 제거했으며 더이상 사용할 수 없습니다.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: 시간 바꿈
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+시스템 시계를 1970년 1월 1일 이후로 @REALTIME@ 마이크로초 지난 값으로
+설정했습니다.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: @TIMEZONE@ 시간대로 시간대 바꿈
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+시스템 시간대를 @TIMEZONE@ 시간대로 바꾸었습니다.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: 시스템 시동 마침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+부팅 과정에 시작하려고 준비한 모든 시스템 서비스를 성공적으로
+ 시작했습니다. 머신이 서비스처럼 대기중이라는 의미는 아니며
+지동을 완전히 마칠 때까지 사용중일 수도 있는 점 참고하십시오.
+
+커널 시동에 @KERNEL_USEC@ 마이크로초가 걸립니다.
+
+초기 램 디스크 시동에 @INITRD_USEC@ 마이크로초가 걸립니다.
+
+사용자 영역 시동에 @USERSPACE_USEC@ 마이크로초가 걸립니다.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: @SLEEP@ 대기 상태 진입
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@SLEEP@ 대기 상태로 진입했습니다.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: @SLEEP@ 대기 상태 마침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@SLEEP@ 대기 상태를 마쳤습니다.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: 컴퓨터 끄기 시작
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+컴퓨터 끄기 동작을 시작했습니다. 모든 시스템 동작을 멈추고
+모든 파일 시스템의 마운트를 해제합니다.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: @UNIT@ 유닛 시작
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛을 시작했습니다.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: @UNIT@ 유닛 시동 마침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛 시동을 마쳤습니다.
+
+시동 결과는 @RESULT@ 입니다.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: @UNIT@ 유닛 끝내기 동작 시작
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛 끝내기 동작을 시작했습니다.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: @UNIT@ 유닛 끝내기 동작 마침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛 끝내기 동작을 마쳤습니다.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: @UNIT@ 유닛 동작 실패
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛 동작에 실패했습니다.
+
+결과는 @RESULT@ 입니다.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: @UNIT@ 유닛 설정 다시 읽기 시작
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛의 설정 다시 읽기를 시작했습니다
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: @UNIT@ 유닛 설정 다시 읽기 완료
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 유닛의 설정 다시 읽기 동작을 끝냈습니다.
+
+결과는 @RESULT@ 입니다.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: @EXECUTABLE@ 프로세스 시작할 수 없음
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@EXECUTABLE@ 프로세스를 시작할 수 없어 실행에 실패했습니다.
+
+이 프로세스에서 반환한 오류 번호는 @ERRNO@번 입니다.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: 하나 이상의 메시지를 syslog에 전달할 수 없음
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+journald 서비스와 동시에 실행중인 syslog 서비스에 하나 이상의 메시지를
+전달할 수 없습니다. 보통 순차적으로 오는 메시지의 속도를 syslog 구현체가
+따라가지 못함을 의미합니다.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: 마운트 지점 비어있지 않음
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@WHERE@ 디렉터리를 마운트 지점으로 지정했으며 (/etc/fstab 파일의
+ 두번째 필드 또는 systemd 유닛 파일의 Where= 필드) 비어있지 않습니다.
+마운트 과정에 방해가 되진 않지만 이전에 이 디렉터리에 존재하는 파일에
+ 접근할 수 없게 됩니다. 중복으로 마운트한 파일을 보려면, 근본 파일
+시스템의 다음 위치에 직접 마운트하십시오.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: 가상 머신 또는 컨테이너 시작
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 시작했으며,
+이제부터 사용할 수 있습니다.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: 가상 머신 또는 컨테이너 마침
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 껐습니다.
diff --git a/src/grp-journal/catalog/systemd.pl.catalog b/src/grp-journal/catalog/systemd.pl.catalog
new file mode 100644
index 0000000000..d8059e93cd
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.pl.catalog
@@ -0,0 +1,315 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2014, 2015, 2016 Piotr Drąg
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Polish translation
+
+# The catalog format is documented on
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Uruchomiono dziennik
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemowy proces dziennika został uruchomiony, otworzył pliki dziennika do
+zapisu i jest gotowy do przetwarzania żądań.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Zatrzymano dziennik
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemowy proces dziennika został wyłączony i zamknął wszystkie obecnie
+aktywne pliki dziennika.
+
+-- ec387f577b844b8fa948f33cad9a75e6
+Subject: Miejsce na dysku używane przez dziennik
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@JOURNAL_NAME@ (@JOURNAL_PATH@) obecnie używa @CURRENT_USE_PRETTY@.
+Maksymalnie może używać @MAX_USE_PRETTY@.
+Zostawianie co najmniej @DISK_KEEP_FREE_PRETTY@ wolnego (z obecnie dostępnego @DISK_AVAILABLE_PRETTY@ miejsca na dysku).
+Wymuszone ograniczenie użycia wynosi więc @LIMIT_PRETTY@, z czego @AVAILABLE_PRETTY@ jest nadal dostępne.
+
+Ograniczenia kontrolujące ilość miejsca na dysku używanego przez dziennik
+można konfigurować za pomocą ustawień SystemMaxUse=, SystemKeepFree=,
+SystemMaxFileSize=, RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize=
+w pliku /etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej
+informacji.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Ograniczono komunikaty z usługi
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Usługa zapisała za dużo komunikatów w określonym czasie. Komunikaty z usługi
+zostały pominięte.
+
+Proszę zauważyć, że tylko komunikaty z danej usługi zostały pominięte. Nie ma
+to wpływu na komunikaty innych usług.
+
+Ograniczenia kontrolujące pomijanie komunikatów mogą być konfigurowane
+za pomocą opcji RateLimitIntervalSec= i RateLimitBurst= w pliku
+/etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej informacji.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Utracono komunikaty dziennika
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Komunikaty jądra zostały utracone, ponieważ system dziennika nie mógł
+przetworzyć ich odpowiednio szybko.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) zrzucił plik core
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Proces @COREDUMP_PID@ (@COREDUMP_COMM@) uległ awarii i zrzucił plik core.
+
+Zwykle wskazuje to na błąd programistyczny w danym programie i powinno zostać
+zgłoszone jego producentowi jako błąd.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Utworzono nową sesję @SESSION_ID@ dla użytkownika @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Nowa sesja o identyfikatorze @SESSION_ID@ została utworzona dla użytkownika
+@USER_ID@.
+
+Proces prowadzący sesji: @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Zakończono sesję @SESSION_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Sesja o identyfikatorze @SESSION_ID@ została zakończona.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Dostępne jest nowe stanowisko @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Nowe stanowisko @SEAT_ID@ zostało skonfigurowane i jest teraz dostępne.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Usunięto stanowisko @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Stanowisko @SEAT_ID@ zostało usunięte i nie jest już dostępne.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Zmiana czasu
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Zegar systemowy został zmieniony na @REALTIME@ μs po 1 stycznia 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Zmiana strefy czasowej na @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemowa strefa czasowa została zmieniona na @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Ukończono uruchamianie systemu
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Wszystkie usługi systemowe obowiązkowo zakolejkowane do włączenia podczas
+uruchamiania systemu zostały pomyślnie uruchomione. Proszę zauważyć, że nie
+oznacza to, że komputer jest bezczynny, jako że usługi mogą wciąż kończyć
+proces uruchamiania.
+
+Uruchamianie jądra zajęło @KERNEL_USEC@ μs.
+
+Uruchamianie początkowego dysku RAM zajęło @INITRD_USEC@ μs.
+
+Uruchamianie przestrzeni użytkownika zajęło @USERSPACE_USEC@ μs.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Przejście do stanu uśpienia @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+System przeszedł do stanu uśpienia @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Wyjście ze stanu uśpienia @SLEEP@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+System wyszedł ze stanu uśpienia @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Zainicjowano wyłączenie systemu
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Zainicjowano wyłączenie systemd. Wyłączenie zostało rozpoczęte i wszystkie
+usługi systemowe zostały zakończone, a wszystkie systemy plików odmontowane.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Rozpoczęto uruchamianie jednostki @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ rozpoczęła uruchamianie.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Ukończono uruchamianie jednostki @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ ukończyła uruchamianie.
+
+Wynik uruchamiania: @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Rozpoczęto wyłączanie jednostki @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ rozpoczęła wyłączanie.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Ukończono wyłączanie jednostki @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ ukończyła wyłączanie.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Jednostka @UNIT@ się nie powiodła
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ się nie powiodła.
+
+Wynik: @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Rozpoczęto ponowne wczytywanie konfiguracji jednostki @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ rozpoczęła ponowne wczytywanie swojej konfiguracji.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Ukończono ponowne wczytywanie konfiguracji jednostki @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jednostka @UNIT@ ukończyła ponowne wczytywanie swojej konfiguracji.
+
+Wynik: @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Nie można wykonać procesu @EXECUTABLE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Proces @EXECUTABLE@ nie mógł zostać wykonany i się nie powiódł.
+
+Numer błędu zwrócony przez ten proces: @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Nie można przekazać jednego lub więcej komunikatów do syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Jeden lub więcej komunikatów nie może zostać przekazanych do usługi syslog
+uruchomionej obok journald. Zwykle oznacza to, że implementacja syslog nie
+jest w stanie nadążyć za prędkością kolejki komunikatów.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Punkt montowania nie jest pusty
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Katalog @WHERE@ został podany jako punkt montowania (drugie pole w pliku
+/etc/fstab lub pole Where= w pliku jednostki systemd) i nie jest pusty. Nie
+wpływa to na montowanie, ale wcześniej istniejące pliki w tym katalogu stają
+się niedostępne. Aby zobaczyć te pliki, proszę ręcznie zamontować system
+plików w innym położeniu.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Uruchomiono maszynę wirtualną lub kontener
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została uruchomiona i jest
+gotowa do użycia.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Zakończono maszynę wirtualną lub kontener
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została wyłączona.
+
+-- 36db2dfa5a9045e1bd4af5f93e1cf057
+Subject: Wyłączono tryb DNSSEC, ponieważ serwer go nie obsługuje
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8) resolved.conf(5)
+
+Usługa resolver (systemd-resolved.service) wykryła, że skonfigurowany serwer
+DNS nie obsługuje DNSSEC, w wyniku czego walidacja DNSSEC została wyłączona.
+
+To zdarzenie będzie miało miejsce, jeśli skonfigurowano DNSSEC=allow-downgrade
+w pliku resolved.conf, a skonfigurowany serwer DNS jest niezgodny z DNSSEC.
+Proszę zauważyć, że używanie tego trybu umożliwia ataki wyłączające DNSSEC,
+ponieważ atakujący będzie mógł wyłączyć walidację DNSSEC na komputerze przez
+umieszczenie odpowiednich odpowiedzi DNS w kanale komunikacji.
+
+To zdarzenie może wskazywać, że serwer DNS jest faktycznie niezgodny z DNSSEC,
+albo że atakującemu udało się upozorować atak tego typu.
+
+-- 1675d7f172174098b1108bf8c7dc8f5d
+Subject: Walidacja DNSSEC się nie powiodła
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Zapytanie DNS lub ustawiony wpis zasobu nie przeszedł walidacji DNSSEC.
+Zwykle wskazuje to, że ktoś manipulował używanym kanałem komunikacji.
+
+-- 4d4408cfd0d144859184d1e65d7c8a65
+Subject: Unieważniono kotwicę zaufania DNSSEC
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Kotwica zaufania DNSSEC została unieważniona. Należy skonfigurować nową, albo
+system operacyjny musi zostać zaktualizowany, aby dostarczyć zaktualizowaną
+kotwicę zaufania DNSSEC.
diff --git a/src/grp-journal/catalog/systemd.pt_BR.catalog b/src/grp-journal/catalog/systemd.pt_BR.catalog
new file mode 100644
index 0000000000..8b856e8355
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.pt_BR.catalog
@@ -0,0 +1,264 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2015 Rafael Ferreira (translation)
+#
+# 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/>.
+
+# Catálogo de mensagens para as mensagens do próprio systemd
+
+# O formato do catálogo está documentado em
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# Para uma explicação do porquê de fazermos tudo isso, veja
+# https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: O jornal foi inciado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O processo jornal do sistema foi iniciado, arquivos foram abertos e está
+pronto para processar requisições.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: O jornal foi interrompido
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O processo do jornal do sistema foi desligado e todos os arquivos de jornal
+do sistema foram fechados.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Mensagens de um serviço foram suprimidas
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Um serviço registrou no log um número excessivo de mensagens dentro de um
+período de tempo. Mensagens do serviço foram descartadas.
+
+Note que apenas mensagens de um serviço em questão foram descartadas; outras
+mensagens dos serviços não foram afetadas.
+
+Os controles de limites de quando as mensagens são descartadas pode ser
+configurado com RateLimitIntervalSec= e RateLimitBurst= no
+/etc/systemd/journald.conf. Veja journald.conf(5) para detalhes.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Mensagens do jornal foram perdidas
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Mensagens do kernel foram perdidas pois o sistema do jornal não pôde
+processá-las em velocidade suficiente para a demanda.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Processo @COREDUMP_PID@ (@COREDUMP_COMM@) despejou núcleo
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Processo @COREDUMP_PID@ (@COREDUMP_COMM@) travou e despejou o núcleo.
+
+Isso normalmente indica um erro de programação no programa que travou e
+deveria ser relatado para seu fabricante como um erro.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: A nova sessão @SESSION_ID@ foi criada para usuário o @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Uma nova sessão com o ID @SESSION_ID@ foi criada para o usuário @USER_ID@.
+
+O processo originador da sessão é @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Sessão @SESSION_ID@ foi terminada
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Um sessão com o ID @SESSION_ID@ foi terminada.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Um novo seat @SEAT_ID@ está disponível
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Um novo seat @SEAT_ID@ foi configurado e está disponível.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Seat @SEAT_ID@ foi removido agora
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Um seat @SEAT_ID@ foi removido e não está mais disponível.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Time change
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O relógio do sistema foi alterado para @REALTIME@ microssegundos após 1º de
+janeiro de 1970.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Fuso horário alterado para @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O fuso horário do sistema foi alterado para @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Inicialização do sistema foi concluída
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Todos os serviços do sistema necessários que estão enfileirados para
+executar na inicialização do sistema, foram iniciados com sucesso. Note
+que isso não significa que a máquina está ociosa, pois os serviços podem
+ainda estar ocupados com a inicialização completa.
+
+Inicialização do kernel precisou @KERNEL_USEC@ microssegundos.
+
+Disco de RAM inicial precisou de @INITRD_USEC@ microssegundos.
+
+Inicialização do espaço do usuário precisou de @USERSPACE_USEC@ microssegundos.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Estado de suspensão do sistema @SLEEP@ iniciado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O sistema entrou agora no estado de suspensão @SLEEP@.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Estado de suspensão do sistema @SLEEP@ finalizado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O sistema saiu agora do estado de suspensão @SLEEP@.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Desligamento do sistema iniciado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Desligamento do sistema foi inicializado. O desligamento se iniciou e todos
+os serviços do sistema foram terminados e todos os sistemas desmontados.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Unidade @UNIT@ sendo iniciado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ está sendo iniciada.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Unidade @UNIT@ concluiu a inicialização
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ concluiu a inicialização.
+
+The start-up result is @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Unidade @UNIT@ sendo desligado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ está sendo desligada.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: A unidade @UNIT@ concluiu o desligamento
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ concluiu o desligamento.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: A unidade @UNIT@ falhou
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ falhou.
+
+O resultado é @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Unidade @UNIT@ iniciou recarregamento de sua configuração
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ iniciou o recarregamento de sua configuração.
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Unidade @UNIT@ concluiu recarregamento de sua configuração
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A unidade @UNIT@ concluiu o recarregamento de sua configuração.
+
+O resultado é @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Processo @EXECUTABLE@ não pôde ser executado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O processo @EXECUTABLE@ não pôde ser executado e falhou.
+
+O número de erro retornado por este processo é @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Uma ou mais mensagens não puderam ser encaminhadas para o syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Uma ou mais mensagens não puderam ser encaminhadas para o serviço do syslog
+em execução paralela ao journald. Isso normalmente indica que a implementação
+do syslog não foi capaz de se manter com a velocidade das mensagens
+enfileiradas.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Ponto de montagem não está vazio
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+O diretório @WHERE@ está especificado como ponto de montagem (o segundo campo
+no /etc/fstab ou campo Where= no arquivo de unidade do systemd) e não está
+vazio. Isso não interfere com a montagem, mas os arquivos pré-existentes
+neste diretório se tornaram inacessívels. Para ver aqueles arquivos, sobre os
+quais foi realizada a montagem, por favor monte manualmente o sistema de
+arquivos subjacente para uma localização secundária.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Uma máquina virtual ou contêiner foi iniciado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A máquina virtual @NAME@ com seu PID @LEADER@ incial foi iniciada e está
+pronto para ser usad.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Uma máquina virtual ou contêiner foi terminado
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+A máquina virtual @NAME@ com seu PID @LEADER@ incial foi desligada.
diff --git a/src/grp-journal/catalog/systemd.ru.catalog b/src/grp-journal/catalog/systemd.ru.catalog
new file mode 100644
index 0000000000..e56dbe3acc
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.ru.catalog
@@ -0,0 +1,354 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2013-2016 Sergey Ptashnick
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Russian translation
+
+# Формат каталога сообщений описан по ссылке
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# Перед каждым элементом в комментарии указан Subject исходного
+# сообщения (на английском).
+
+# Subject: The Journal has been started
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Запущена служба журналирования
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Процесс, отвечающий за журналирование системных событий, успешно запустился,
+открыл для записи файлы журнала, и готов обрабатывать запросы.
+
+# Subject: The Journal has been stopped
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Служба журналирования остановлена
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Процесс, отвечающий за журналирование системных событий, завершил работу и
+закрыл все свои файлы.
+
+# Subject: Disk space used by the journal
+-- ec387f577b844b8fa948f33cad9a75e6
+Subject: Место на диске, занятое журналом
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@JOURNAL_NAME@ (@JOURNAL_PATH@) сейчас занимает @CURRENT_USE_PRETTY@.
+Максимальный разрешенный размер составляет @MAX_USE_PRETTY@.
+Оставляем свободными как минимум @DISK_KEEP_FREE_PRETTY@ (сейчас на диске
+свободно @DISK_AVAILABLE_PRETTY@).
+Таким образом, предел использования составляет @LIMIT_PRETTY@, из которых
+@AVAILABLE_PRETTY@ пока свободно.
+
+Ограничения на размер журнала настраиваются при помощи параметров
+SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=,
+RuntimeKeepFree=, RuntimeMaxFileSize= в файле /etc/systemd/journald.conf.
+Более подробные сведения вы можете получить на справочной странице
+journald.conf(5).
+
+# Subject: Messages from a service have been suppressed
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Часть сообщений от службы пропущена
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Служба отправила слишком много сообщений за короткий промежуток времени.
+Часть сообщений была пропущена.
+
+Обратите внимание, что были пропущены сообщения только от этой службы,
+сообщения других служб не затронуты.
+
+Предел, после которого служба журнала начинает игнорировать сообщения,
+настраивается параметрами RateLimitIntervalSec= и RateLimitBurst= в файле
+/etc/systemd/journald.conf. Подробности смотрите на странице руководства
+journald.conf(5).
+
+# Subject: Journal messages have been missed
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Часть сообщений ядра пропущена
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Часть сообщений, поступивших от ядра, была потеряна, так как служба
+журналирования не успела их обработать.
+
+# Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) сбросил дамп памяти
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) завершился из-за критической ошибки.
+Записан дамп памяти.
+
+Вероятно, это произошло из-за ошибки, допущенной в коде программы.
+Рекомендуется сообщить её разработчикам о возникшей проблеме.
+
+# Subject: A new session @SESSION_ID@ has been created for user @USER_ID@
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Для пользователя @USER_ID@ создан новый сеанс @SESSION_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Для пользователя @USER_ID@ создан новый сеанс с идентификатором @SESSION_ID@.
+
+Главный процесс нового сеанса имеет индентификатор @LEADER@.
+
+# Subject: A session @SESSION_ID@ has been terminated
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Сеанс @SESSION_ID@ завершен
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Сеанс с идентификатором @SESSION_ID@ завершился.
+
+# Subject: A new seat @SEAT_ID@ is now available
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Добавлено новое рабочее место @SEAT_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Новое рабочее место (seat) @SEAT_ID@ полностью настроено и готово к
+использованию.
+
+# Subject: A seat @SEAT_ID@ has now been removed
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Рабочее место @SEAT_ID@ отключено
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Рабочее место (seat) @SEAT_ID@ было отключено.
+
+# Subject: Time change
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Переведены системные часы
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системные часы были переведены. Сейчас они показывают @REALTIME@ микросекунд
+с момента 00:00:00 1 января 1970 года.
+
+# Subject: Time zone change to @TIMEZONE@
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Часовой пояс изменен на @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системный часовой пояс был изменен. Новое значение: @TIMEZONE@.
+
+# Subject: System start-up is now complete
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Запуск системы завершен
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Все системные службы, запуск которых предписан настройками, были запущены.
+Впрочем, это ещё не означает, что система в данный момент ничем не занята,
+так как некоторые службы могут продолжать инициализацию даже после того, как
+отчитались о своем запуске.
+
+Запуск ядра занял @KERNEL_USEC@ микросекунд.
+
+Процессы начального RAM-диска (initrd) отработали за @INITRD_USEC@ микросекунд.
+
+Запуск системных служб занял @USERSPACE_USEC@ микросекунд.
+
+# Subject: System sleep state @SLEEP@ entered
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Система перешла в состояние сна (@SLEEP@)
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Система была переведена в состояние сна (@SLEEP@).
+
+# Subject: System sleep state @SLEEP@ left
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Система вышла из состояния сна (@SLEEP@)
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Система была выведена из состояния сна (@SLEEP@).
+
+# Subject: System shutdown initiated
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Подготовка системы к выключению
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Начат процесс подготовки к выключению компьютера. Останавливаются все системные
+службы, отмонтируются все файловые системы.
+
+# Subject: Unit @UNIT@ has begun with start-up
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Начинается запуск юнита @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Начат процесс запуска юнита @UNIT@.
+
+# Subject: Unit @UNIT@ has finished start-up
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Запуск юнита @UNIT@ завершен
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Процесс запуска юнита @UNIT@ был завершен.
+
+Результат: @RESULT@.
+
+# Subject: Unit @UNIT@ has begun shutting down
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Начинается остановка юнита @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Начат процесс остановки юнита @UNIT@.
+
+# Subject: Unit @UNIT@ has finished shutting down
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Завершена остановка юнита @UNIT@.
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Процесс остановки юнита @UNIT@ был завершен.
+
+# Subject: Unit @UNIT@ has failed
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Ошибка юнита @UNIT@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Произошел сбой юнита @UNIT@.
+
+Результат: @RESULT@.
+
+# Subject: Unit @UNIT@ has begun with reloading its configuration
+-- d34d037fff1847e6ae669a370e694725
+Subject: Юнит @UNIT@ начал перечитывать свои настройки
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Юнит @UNIT@ начал процесс перечитывания своей конфигурации.
+
+# Subject: Unit @UNIT@ has finished reloading its configuration
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Юнит @UNIT@ завершил перечитывание своих настроек
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Юнит @UNIT@ завершил процесс перечитывания своей конфигурации.
+
+Результат: @RESULT@.
+
+# Subject: Process @EXECUTABLE@ could not be executed
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Не удалось запустить процесс @EXECUTABLE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Сбой: не удалось запустить процесс @EXECUTABLE@.
+
+Код ошибки: @ERRNO@.
+
+# Subject: One or more messages could not be forwarded to syslog
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Часть сообщений не удалось передать процессу syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Не удалось передать некоторые сообщения демону системного лога (syslog),
+дублирующему работу службы системного журнала. Скорее всего, причина в том, что
+используемая реализация syslog не успевает обрабатывать сообщения с достаточной
+скоростью.
+
+# Subject: Mount point is not empty
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Каталог, являющийся точкой монтирования, не пуст
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Каталог @WHERE@, который был указан в качестве точки монтирования (во втором
+столбце файла /etc/fstab, либо в параметре Where= файла конфигурации юнита),
+не является пустым. Это никак не мешает монтированию, однако ранее находившиеся
+в нем файлы будут недоступны. Чтобы получить к ним доступ, вы можете вручную
+перемонтировать эту файловую систему в другую точку.
+
+# Subject: A virtual machine or container has been started
+-- 24d8d4452573402496068381a6312df2
+Subject: Запущена виртуальная машина/контейнер
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) запущена и
+готова к работе.
+
+# Subject: A virtual machine or container has been terminated
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Остановлена виртуальная машина/контейнер
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) выключена.
+
+# Subject: DNSSEC mode has been turned off, as server doesn't support it
+-- 36db2dfa5a9045e1bd4af5f93e1cf057
+Subject: Механизм DNSSEC был отключен, так как DNS-сервер его не поддерживает
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8) resolved.conf(5)
+
+Служба разрешения имен хостов (systemd-resolved.service) определила, что
+указанный в настойках DNS-сервер не поддерживает технологию DNSSEC, и
+автоматически отключила DNSSEC-проверки.
+
+Данное событие возникает, если в файле resolved.conf указан параметр
+DNSSEC=allow-downgrade, и вышестоящий DNS-сервер не поддерживает DNSSEC.
+Обратите внимание, что режим allow-downgrade допускает возможность атаки
+"DNSSEC downgrade", в ходе которой атакующий хакер блокирует проверки DNSSEC
+путем отправки ложных сообщений от имени DNS-сервера.
+
+Возникновение данного события может свидетельствовать как о том, что ваш
+DNS-сервер не поддерживает DNSSEC, так и о том, что некий хакер успешно провел
+против вас атаку, направленную на блокировку DNSSEC-проверок.
+
+# Subject: DNSSEC validation failed
+-- 1675d7f172174098b1108bf8c7dc8f5d
+Subject: Проверка DNSSEC провалена
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+DNS-запрос или отдельная ресурсная запись не прошла проверку DNSSEC.
+Как правило, это свидетельствует о постороннем вмешательстве в канал связи.
+
+# Subject: A DNSSEC trust anchor has been revoked
+-- 4d4408cfd0d144859184d1e65d7c8a65
+Subject: Открытый ключ DNSSEC был отозван
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:systemd-resolved.service(8)
+
+Открытый ключ (trust ahcnor) DNSSEC был отозван. Необходимо настроить новый
+открытый ключ, либо обновить систему, чтобы получить обновленный открытый ключ.
diff --git a/src/grp-journal/catalog/systemd.sr.catalog b/src/grp-journal/catalog/systemd.sr.catalog
new file mode 100644
index 0000000000..cc689b7956
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.sr.catalog
@@ -0,0 +1,262 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Serbian translation
+
+# Формат каталога је документован на
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# Да бисте видели зашто ово радимо, погледајте https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: Журнал је покренут
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системски журналски процес се покренуо, отворио журналске
+датотеке за упис и спреман је за обраду захтева.
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: Журнал је заустављен
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системски журналски процес се зауставио и затворио све тренутно
+отворене журналске датотеке.
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: Поруке од услуге су утишане
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+Услуга је уписала сувише порука за једно време. Поруке
+од услуге су одбачене.
+
+Знајте да су само поруке од ове услуге одбачене, друге
+услуге нису захваћене овим.
+
+Ограничења која подешавају начин на који се поруке одбацују се могу подесити
+помоћу „RateLimitIntervalSec=“ и „RateLimitBurst=“ параметара унутар датотеке
+/etc/systemd/journald.conf. Погледајте journald.conf(5) за појединости.
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: Журналске поруке су изгубљене
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Поруке кернела су изгубљене јер журналски систем није могао да их
+обради довољно брзо.
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је избацио своје језгро
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је пао и избацио своје језгро.
+
+Ово обично значи да постоји грешка у програму који је пао и ова
+грешка треба да се пријави продавцу.
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: Нова сесија @SESSION_ID@ је направљена за корисника @USER_ID@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Нова сесија са ИБ-ом @SESSION_ID@ је направљена за корисника @USER_ID@.
+
+Водећи процес сесије је @LEADER@.
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: Сесија @SESSION_ID@ је окончана
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Сесија са ИБ-ом @SESSION_ID@ је окончана.
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: Ново седиште @SEAT_ID@ је сада доступно
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Ново седиште @SEAT_ID@ је исподешавано и сада је доступно.
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: Седиште @SEAT_ID@ је сада уклоњено
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+Седиште @SEAT_ID@ је сада уклоњено и више није доступно.
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: Време је промењено
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Системски сат је сада подешен на @REALTIME@ микросекунде након 1. јануара 1970. године.
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: Временска зона је промењена на @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Временска зона је промењена на @TIMEZONE@.
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: Подизање система је сада готово
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Све системске услуге које су заказане за подизање су успешно покренуте.
+Знајте да ово не значи да је машина сада беспослена јер услуге могу
+и даље бити заузете завршавањем покретања система.
+
+Подизање кернела је трајало @KERNEL_USEC@ микросекунде.
+
+Подизање почетног РАМ диска је трајало @INITRD_USEC@ микросекунде.
+
+Подизање корисничких програма је трајало @USERSPACE_USEC@ микросекунде.
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: Системско стање спавања @SLEEP@ започето
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Систем је сада ушао у @SLEEP@ стање спавања.
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: Системско стање спавања @SLEEP@ напуштено
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Систем је изашао из @SLEEP@ стања спавања.
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: Гашење система започето
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Систем-де гашење је започето. Гашење је сада почело и све
+системске услуге су окончане и сви системи датотека откачени.
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: Јединица @UNIT@ је почела са покретањем
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је почела са покретањем.
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: Јединица @UNIT@ је завршила са покретањем
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је завршила са покретањем.
+
+Исход покретања је @RESULT@.
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: Јединица @UNIT@ је почела са гашењем
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је почела са гашењем.
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: Јединица @UNIT@ је завршила са гашењем
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је завршила са гашењем.
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: Јединица @UNIT@ је пукла
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је пукла.
+
+Исход је @RESULT@.
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: Јединица @UNIT@ је почела са поновним учитавањем свог подешавања
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је почела са поновним учитавањем свог подешавања
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања
+
+Исход је @RESULT@.
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: Процес @EXECUTABLE@ није могао бити извршен
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Процес @EXECUTABLE@ није могао бити извршен и пукао је.
+
+Овај процес је вратио број грешке @ERRNO@.
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: Једна или више порука није могло бити прослеђено системском записнику
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Једна или више порука није могло бити прослеђено „syslog“ услузи
+која ради упоредно са журнал-деом. Ово обично значи да спроведена
+„syslog“ услуга није могла да издржи брзину свих надолазећих
+порука у реду.
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: Тачка качења није празна
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Директоријум @WHERE@ је наведен као тачка качења (друго поље у
+/etc/fstab датотеци или у „Where=“ пољу систем-де јединичне датотеке)
+и он није празан. Ово не утиче на качење али ће већ постојеће датотеке у
+овом директоријуму постати недоступне. Да бисте видели ове недоступне
+датотеке, ручно прикачите основни систем датотека у другу
+путању.
+
+-- 24d8d4452573402496068381a6312df2
+Subject: Виртуелна машина или контејнер је покренут(а)
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је
+покренута и сада је спремна за коришћење.
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: Виртуелна машина или контејнер је окончан(а)
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је
+угашена.
diff --git a/src/grp-journal/catalog/systemd.zh_CN.catalog b/src/grp-journal/catalog/systemd.zh_CN.catalog
new file mode 100644
index 0000000000..ed59fc9250
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.zh_CN.catalog
@@ -0,0 +1,253 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2015 Boyuan Yang
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Simplified Chinese translation
+
+# 本 catalog 文档格式被记载在
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# 如需了解我们为什么做这些工作,请见 https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: 日志已开始
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系统日志进程已启动,已打开供写入的日志文件并准备好处理请求。
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: 日志已停止
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系统日志进程已终止,并已关闭所有当前活动的日志文件。
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: 由某个服务而来的消息已被抑制
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+某个服务在一个时间周期内记录了太多消息。
+从该服务而来的消息已被丢弃。
+
+请注意只有由有问题的服务传来的消息被丢弃,
+其它服务的消息不受影响。
+
+可以在 /etc/systemd/journald.conf 中设定 RateLimitIntervalSec=
+以及 RateLimitBurst = 的值以控制丢弃信息的限制。
+请参见 journald.conf(5) 以了解详情。
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: 日志消息已遗失
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+因日志系统对内核消息的处理速度不够快,
+部分信息已经遗失。
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: 进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心已转储
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 已崩溃并进行核心转储。
+
+这通常意味着崩溃程序中存在编程错误,并应当将此错误向其开发者报告。
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: 一个新会话 @SESSION_ID@ 已为用户 @USER_ID@ 建立
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+一个 ID 为 @SESSION_ID@ 的新会话已为用户 @USER_ID@ 建立。
+
+该会话的首进程为 @LEADER@。
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: 会话 @SESSION_ID@ 已终止
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+一个 ID 为 @SESSION_ID@ 的会话已终止。
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: 一个新的座位 @SEAT_ID@ 可用
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+一个新的座位 @SEAT_ID@ 已被配置并已可用。
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: 座位 @SEAT_ID@ 已被移除
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+座位 @SEAT_ID@ 已被移除并不再可用。
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: 时间已变更
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系统时钟已变更为1970年1月1日后 @REALTIME@ 微秒。
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: 时区变更为 @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系统时区已变更为 @TIMEZONE@。
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: 系统启动已完成
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+所有系统启动时需要的系统服务均已成功启动。
+请注意这并不代表现在机器已经空闲,因为某些服务可能仍处于完成启动的过程中。
+
+内核启动使用了 @KERNEL_USEC@ 毫秒。
+
+初始内存盘启动使用了 @INITRD_USEC@ 毫秒。
+
+用户空间启动使用了 @USERSPACE_USEC@ 毫秒。
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: 系统已进入 @SLEEP@ 睡眠状态
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-deve
+
+系统现已进入 @SLEEP@ 睡眠状态。
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: 系统已离开 @SLEEP@ 睡眠状态
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系统现已离开 @SLEEP@ 睡眠状态。
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: 系统关机已开始
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系统关机操作已初始化。
+关机已开始,所有系统服务均已结束,所有文件系统已卸载。
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: @UNIT@ 单元已开始启动
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已开始启动。
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: @UNIT@ 单元已结束启动
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已结束启动。
+
+启动结果为“@RESULT@”。
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: @UNIT@ 单元已开始停止操作
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已开始停止操作。
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: @UNIT@ 单元已结束停止操作
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已结束停止操作。
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: @UNIT@ 单元已失败
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已失败。
+
+结果为“@RESULT@”。
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: @UNIT@ 单元已开始重新载入其配置
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已开始重新载入其配置。
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: @UNIT@ 单元已结束配置重载入
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+@UNIT@ 单元已结束配置重载入操作。
+
+结果为“@RESULT@”。
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: 进程 @EXECUTABLE@ 无法执行
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+进程 @EXECUTABLE@ 无法被执行并已失败。
+
+该进程返回的错误代码为 @ERRNO@。
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: 一个或更多消息无法被转发至 syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+有一条或更多的消息无法被转发至与 journald 同时运行的 syslog 服务。
+这通常意味着 syslog 实现无法跟上队列中消息进入的速度。
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: 挂载点不为空
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+目录 @WHERE@ 被指定为挂载点(即 /etc/fstab 文件的第二栏,或 systemd 单元
+文件的 Where= 字段),且该目录非空。
+这并不会影响挂载行为,但该目录中先前已存在的文件将无法被访问。
+如需查看这些文件,请手动将其下的文件系统挂载到另一个位置。
+
+-- 24d8d4452573402496068381a6312df2
+Subject: 一个虚拟机或容器已启动
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被启动并可被使用。
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: 一个虚拟机或容器已被终止
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被关闭并停止。
diff --git a/src/grp-journal/catalog/systemd.zh_TW.catalog b/src/grp-journal/catalog/systemd.zh_TW.catalog
new file mode 100644
index 0000000000..aa5004db08
--- /dev/null
+++ b/src/grp-journal/catalog/systemd.zh_TW.catalog
@@ -0,0 +1,263 @@
+# This file is part of systemd.
+#
+# Copyright 2012 Lennart Poettering
+# Copyright 2015 Jeff Huang
+#
+# 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/>.
+
+# Message catalog for systemd's own messages
+# Traditional Chinese translation
+
+# Catalog 的格式記錄於
+# http://www.freedesktop.org/wiki/Software/systemd/catalog
+
+# For an explanation why we do all this, see https://xkcd.com/1024/
+
+-- f77379a8490b408bbe5f6940505a777b
+Subject: 日誌已開始
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系統日誌行程已啟動,已開啟日誌
+檔案供寫入並準備好對行程的要求做出回應。
+
+-- d93fb3c9c24d451a97cea615ce59c00b
+Subject: 日誌已停止
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系統日誌行程已關閉,且關閉所有目前
+活躍的日誌檔案。
+
+-- a596d6fe7bfa4994828e72309e95d61e
+Subject: 從服務而來的訊息已被抑制
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:journald.conf(5)
+
+有一個服務在一個時間週期內記錄了太多訊息。
+從該服務而來的訊息已被丟棄。
+
+注意,只有有問題的服務之訊息被丟棄,
+其他服務的訊息則不受影響。
+
+可以在 /etc/systemd/journald.conf 中設定
+RateLimitIntervalSec= 以及 RateLimitBurst=
+來控制當訊息要開始被丟棄時的限制。參見 journald.conf(5) 以獲得更多資訊。
+
+-- e9bf28e6e834481bb6f48f548ad13606
+Subject: 日誌訊息已遺失
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+因日誌系統對核心訊息的處理不夠快速,
+部份訊息已遺失。
+
+-- fc2e22bc6ee647b6b90729ab34a250b1
+Subject: 行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心傾印
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: man:core(5)
+
+行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 當掉並核心傾印。
+
+這通常代表了在當掉的程式中的一個程式錯誤
+並需要回報錯誤給其開發者。
+
+-- 8d45620c1a4348dbb17410da57c60c66
+Subject: 新的工作階段 @SESSION_ID@ 已為使用者 @USER_ID@ 建立
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+一個新的工作階段,ID @SESSION_ID@ 已為使用者 @USER_ID@ 建立。
+
+這個工作階段的領導行程為 @LEADER@。
+
+-- 3354939424b4456d9802ca8333ed424a
+Subject: 工作階段 @SESSION_ID@ 已結束
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+一個工作階段,ID @SESSION_ID@ 已結束。
+
+-- fcbefc5da23d428093f97c82a9290f7b
+Subject: 新的座位 @SEAT_ID@ 可用
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+一個新的座位 @SEAT_ID@ 已被設定且現在可用。
+
+-- e7852bfe46784ed0accde04bc864c2d5
+Subject: 座位 @SEAT_ID@ 已被移除
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat
+
+座位 @SEAT_ID@ 已被移除且不再可用。
+
+-- c7a787079b354eaaa9e77b371893cd27
+Subject: 時間變更
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系統時間已變更為1970年1月1日後 @REALTIME@ 微秒。
+
+-- 45f82f4aef7a4bbf942ce861d1f20990
+Subject: 時區變更為 @TIMEZONE@
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系統時區已變更為 @TIMEZONE@。
+
+-- b07a249cd024414a82dd00cd181378ff
+Subject: 系統啟動已完成
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+所有開機所必要的系統服務都已成功啟動。
+注意這並不代表這臺機器有空閒的時間
+可以服務,可能仍忙於完成啟動。
+
+核心啟動需要 @KERNEL_USEC@ 微秒。
+
+初始 RAM 磁碟啟動需要 @INITRD_USEC@ 微秒。
+
+使用者空間啟動需要 @USERSPACE_USEC@ 微秒。
+
+-- 6bbd95ee977941e497c48be27c254128
+Subject: 系統進入 @SLEEP@ 睡眠狀態
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系統現在已進入 @SLEEP@ 睡眠狀態。
+
+-- 8811e6df2a8e40f58a94cea26f8ebf14
+Subject: 系統離開 @SLEEP@ 睡眠狀態
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+系統現在已離開 @SLEEP@ 睡眠狀態。
+
+-- 98268866d1d54a499c4e98921d93bc40
+Subject: 系統關機開始
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+Systemd 關閉已經開始。關閉已開始且所有系統服務
+都已結束,所有的檔案系統也都已被卸載。
+
+-- 7d4958e842da4a758f6c1cdc7b36dcc5
+Subject: 單位 @UNIT@ 已開始啟動
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 已開始啟動。
+
+-- 39f53479d3a045ac8e11786248231fbf
+Subject: 單位 @UNIT@ 啟動已結束
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 啟動已結束。
+
+啟動結果為 @RESULT@。
+
+-- de5b426a63be47a7b6ac3eaac82e2f6f
+Subject: 單位 @UNIT@ 已開始關閉
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 已開始關閉。
+
+-- 9d1aaa27d60140bd96365438aad20286
+Subject: 單位 @UNIT@ 已關閉結束
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 已關閉結束。
+
+-- be02cf6855d2428ba40df7e9d022f03d
+Subject: 單位 @UNIT@ 已失敗
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 已失敗。
+
+結果為 @RESULT@。
+
+-- d34d037fff1847e6ae669a370e694725
+Subject: 單位 @UNIT@ 已開始重新載入其設定
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 已開始重新載入其設定
+
+-- 7b05ebc668384222baa8881179cfda54
+Subject: 單位 @UNIT@ 已結束重新載入其設定
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+單位 @UNIT@ 已結束重新載入其設定
+
+結果為 @RESULT@。
+
+-- 641257651c1b4ec9a8624d7a40a9e1e7
+Subject: 行程 @EXECUTABLE@ 無法執行
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+行程 @EXECUTABLE@ 無法執行且失敗。
+
+由該行程所回傳的錯誤碼為 @ERRNO@。
+
+-- 0027229ca0644181a76c4e92458afa2e
+Subject: 一個或更多訊息無法被轉發到 syslog
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+一個或更多訊息無法被轉發到 syslog 服務
+以及並行執行的 journald。這通常代表著
+syslog 實作並無未跟上佇列中訊息
+的速度。
+
+-- 1dee0369c7fc4736b7099b38ecb46ee7
+Subject: 掛載點不為空
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+目錄 @WHERE@ 被指定為掛載點(在 /etc/fstab 中的
+第二欄或是在 systemd 單位檔案中的 Where= 欄位)且其不為空。
+這並不會干擾掛載,但在此目錄中已存在的檔案
+會變成無法存取的狀態。要檢視這些 over-mounted 的檔案,
+請手動掛載下面的檔案系統到次要
+位置。
+
+-- 24d8d4452573402496068381a6312df2
+Subject: 虛擬機器或容器已啟動
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 現在
+已經開始並已經可以使用。
+
+-- 58432bd3bace477cb514b56381b8a758
+Subject: 虛擬機器或容器已結束
+Defined-By: systemd
+Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
+
+虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 已經
+關閉。
diff --git a/src/grp-journal/grp-remote/.gitignore b/src/grp-journal/grp-remote/.gitignore
new file mode 100644
index 0000000000..06847b65d4
--- /dev/null
+++ b/src/grp-journal/grp-remote/.gitignore
@@ -0,0 +1,2 @@
+/journal-remote.conf
+/journal-upload.conf
diff --git a/src/grp-journal/grp-remote/Makefile b/src/grp-journal/grp-remote/Makefile
new file mode 100644
index 0000000000..66e419a37e
--- /dev/null
+++ b/src/grp-journal/grp-remote/Makefile
@@ -0,0 +1,30 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+nested.subdirs += systemd-journal-gatewayd
+nested.subdirs += systemd-journal-remote
+nested.subdirs += systemd-journal-upload
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/grp-remote/browse.html b/src/grp-journal/grp-remote/browse.html
new file mode 100644
index 0000000000..32848c7673
--- /dev/null
+++ b/src/grp-journal/grp-remote/browse.html
@@ -0,0 +1,544 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Journal</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <style type="text/css">
+ div#divlogs, div#diventry {
+ font-family: monospace;
+ font-size: 7pt;
+ background-color: #ffffff;
+ padding: 1em;
+ margin: 2em 0em;
+ border-radius: 10px 10px 10px 10px;
+ border: 1px solid threedshadow;
+ white-space: nowrap;
+ overflow-x: scroll;
+ }
+ div#diventry {
+ display: none;
+ }
+ div#divlogs {
+ display: block;
+ }
+ body {
+ background-color: #ededed;
+ color: #313739;
+ font: message-box;
+ margin: 3em;
+ }
+ td.timestamp {
+ text-align: right;
+ border-right: 1px dotted lightgrey;
+ padding-right: 5px;
+ }
+ td.process {
+ border-right: 1px dotted lightgrey;
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+ td.message {
+ padding-left: 5px;
+ }
+ td.message > a:link, td.message > a:visited {
+ text-decoration: none;
+ color: #313739;
+ }
+ td.message-error {
+ padding-left: 5px;
+ color: red;
+ font-weight: bold;
+ }
+ td.message-error > a:link, td.message-error > a:visited {
+ text-decoration: none;
+ color: red;
+ }
+ td.message-highlight {
+ padding-left: 5px;
+ font-weight: bold;
+ }
+ td.message-highlight > a:link, td.message-highlight > a:visited {
+ text-decoration: none;
+ color: #313739;
+ }
+ td > a:hover, td > a:active {
+ text-decoration: underline;
+ color: #c13739;
+ }
+ table#tablelogs, table#tableentry {
+ border-collapse: collapse;
+ }
+ td.field {
+ text-align: right;
+ border-right: 1px dotted lightgrey;
+ padding-right: 5px;
+ }
+ td.data {
+ padding-left: 5px;
+ }
+ div#keynav {
+ text-align: center;
+ font-size: 7pt;
+ color: #818789;
+ padding-top: 2em;
+ }
+ span.key {
+ font-weight: bold;
+ color: #313739;
+ }
+ div#buttonnav {
+ text-align: center;
+ }
+ button {
+ font-size: 18pt;
+ font-weight: bold;
+ width: 2em;
+ height: 2em;
+ }
+ div#filternav {
+ text-align: center;
+ }
+ select {
+ width: 50em;
+ }
+ </style>
+</head>
+
+<body>
+ <!-- TODO:
+ - live display
+ - show red lines for reboots -->
+
+ <h1 id="title"></h1>
+
+ <div id="os"></div>
+ <div id="virtualization"></div>
+ <div id="cutoff"></div>
+ <div id="machine"></div>
+ <div id="usage"></div>
+ <div id="showing"></div>
+
+ <div id="filternav">
+ <select id="filter" onchange="onFilterChange(this);" onfocus="onFilterFocus(this);">
+ <option>No filter</option>
+ </select>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <input id="boot" type="checkbox" onchange="onBootChange(this);">Only current boot</input>
+ </div>
+
+ <div id="divlogs"><table id="tablelogs"></table></div>
+ <a name="entry"></a>
+ <div id="diventry"><table id="tableentry"></table></div>
+
+ <div id="buttonnav">
+ <button id="head" onclick="entriesLoadHead();" title="First Page">&#8676;</button>
+ <button id="previous" type="button" onclick="entriesLoadPrevious();" title="Previous Page"/>&#8592;</button>
+ <button id="next" type="button" onclick="entriesLoadNext();" title="Next Page"/>&#8594;</button>
+ <button id="tail" type="button" onclick="entriesLoadTail();" title="Last Page"/>&#8677;</button>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <button id="more" type="button" onclick="entriesMore();" title="More Entries"/>+</button>
+ <button id="less" type="button" onclick="entriesLess();" title="Fewer Entries"/>-</button>
+ </div>
+
+ <div id="keynav">
+ <span class="key">g</span>: First Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">&#8592;, k, BACKSPACE</span>: Previous Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">&#8594;, j, SPACE</span>: Next Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">G</span>: Last Page &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">+</span>: More entries &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="key">-</span>: Fewer entries
+ </div>
+
+ <script type="text/javascript">
+ var first_cursor = null;
+ var last_cursor = null;
+
+ function getNEntries() {
+ var n;
+ n = localStorage["n_entries"];
+ if (n == null)
+ return 50;
+ n = parseInt(n);
+ if (n < 10)
+ return 10;
+ if (n > 1000)
+ return 1000;
+ return n;
+ }
+
+ function showNEntries(n) {
+ var showing = document.getElementById("showing");
+ showing.innerHTML = "Showing <b>" + n.toString() + "</b> entries.";
+ }
+
+ function setNEntries(n) {
+ if (n < 10)
+ return 10;
+ if (n > 1000)
+ return 1000;
+ localStorage["n_entries"] = n.toString();
+ showNEntries(n);
+ }
+
+ function machineLoad() {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/machine");
+ request.onreadystatechange = machineOnResult;
+ request.setRequestHeader("Accept", "application/json");
+ request.send(null);
+ }
+
+ function formatBytes(u) {
+ if (u >= 1024*1024*1024*1024)
+ return (u/1024/1024/1024/1024).toFixed(1) + " TiB";
+ else if (u >= 1024*1024*1024)
+ return (u/1024/1024/1024).toFixed(1) + " GiB";
+ else if (u >= 1024*1024)
+ return (u/1024/1024).toFixed(1) + " MiB";
+ else if (u >= 1024)
+ return (u/1024).toFixed(1) + " KiB";
+ else
+ return u.toString() + " B";
+ }
+
+ function escapeHTML(s) {
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+ }
+
+ function machineOnResult(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var d = JSON.parse(event.currentTarget.responseText);
+
+ var title = document.getElementById("title");
+ title.innerHTML = 'Journal of ' + escapeHTML(d.hostname);
+ document.title = 'Journal of ' + escapeHTML(d.hostname);
+
+ var machine = document.getElementById("machine");
+ machine.innerHTML = 'Machine ID is <b>' + d.machine_id + '</b>, current boot ID is <b>' + d.boot_id + '</b>.';
+
+ var cutoff = document.getElementById("cutoff");
+ var from = new Date(parseInt(d.cutoff_from_realtime) / 1000);
+ var to = new Date(parseInt(d.cutoff_to_realtime) / 1000);
+ cutoff.innerHTML = 'Journal begins at <b>' + from.toLocaleString() + '</b> and ends at <b>' + to.toLocaleString() + '</b>.';
+
+ var usage = document.getElementById("usage");
+ usage.innerHTML = 'Disk usage is <b>' + formatBytes(parseInt(d.usage)) + '</b>.';
+
+ var os = document.getElementById("os");
+ os.innerHTML = 'Operating system is <b>' + escapeHTML(d.os_pretty_name) + '</b>.';
+
+ var virtualization = document.getElementById("virtualization");
+ virtualization.innerHTML = d.virtualization == "bare" ? "Running on <b>bare metal</b>." : "Running on virtualization <b>" + escapeHTML(d.virtualization) + "</b>.";
+ }
+
+ function entriesLoad(range) {
+
+ if (range == null)
+ range = localStorage["cursor"] + ":0";
+ if (range == null)
+ range = "";
+
+ var url = "/entries";
+
+ if (localStorage["filter"] != "" && localStorage["filter"] != null) {
+ url += "?_SYSTEMD_UNIT=" + escape(localStorage["filter"]);
+
+ if (localStorage["boot"] == "1")
+ url += "&boot";
+ } else {
+ if (localStorage["boot"] == "1")
+ url += "?boot";
+ }
+
+ var request = new XMLHttpRequest();
+ request.open("GET", url);
+ request.onreadystatechange = entriesOnResult;
+ request.setRequestHeader("Accept", "application/json");
+ request.setRequestHeader("Range", "entries=" + range + ":" + getNEntries().toString());
+ request.send(null);
+ }
+
+ function entriesLoadNext() {
+ if (last_cursor == null)
+ entriesLoad("");
+ else
+ entriesLoad(last_cursor + ":1");
+ }
+
+ function entriesLoadPrevious() {
+ if (first_cursor == null)
+ entriesLoad("");
+ else
+ entriesLoad(first_cursor + ":-" + getNEntries().toString());
+ }
+
+ function entriesLoadHead() {
+ entriesLoad("");
+ }
+
+ function entriesLoadTail() {
+ entriesLoad(":-" + getNEntries().toString());
+ }
+
+ function entriesOnResult(event) {
+
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var logs = document.getElementById("tablelogs");
+
+ var lc = null;
+ var fc = null;
+
+ var i, l = event.currentTarget.responseText.split('\n');
+
+ if (l.length <= 1) {
+ logs.innerHTML = '<tbody><tr><td colspan="3"><i>No further entries...</i></td></tr></tbody>';
+ return;
+ }
+
+ var buf = '';
+
+ for (i in l) {
+
+ if (l[i] == '')
+ continue;
+
+ var d = JSON.parse(l[i]);
+ if (d.MESSAGE == undefined || d.__CURSOR == undefined)
+ continue;
+
+ if (fc == null)
+ fc = d.__CURSOR;
+ lc = d.__CURSOR;
+
+ var priority;
+ if (d.PRIORITY != undefined)
+ priority = parseInt(d.PRIORITY);
+ else
+ priority = 6;
+
+ if (priority <= 3)
+ clazz = "message-error";
+ else if (priority <= 5)
+ clazz = "message-highlight";
+ else
+ clazz = "message";
+
+ buf += '<tr><td class="timestamp">';
+
+ if (d.__REALTIME_TIMESTAMP != undefined) {
+ var timestamp = new Date(parseInt(d.__REALTIME_TIMESTAMP) / 1000);
+ buf += timestamp.toLocaleString();
+ }
+
+ buf += '</td><td class="process">';
+
+ if (d.SYSLOG_IDENTIFIER != undefined)
+ buf += escapeHTML(d.SYSLOG_IDENTIFIER);
+ else if (d._COMM != undefined)
+ buf += escapeHTML(d._COMM);
+
+ if (d._PID != undefined)
+ buf += "[" + escapeHTML(d._PID) + "]";
+ else if (d.SYSLOG_PID != undefined)
+ buf += "[" + escapeHTML(d.SYSLOG_PID) + "]";
+
+ buf += '</td><td class="' + clazz + '"><a href="#entry" onclick="onMessageClick(\'' + d.__CURSOR + '\');">';
+
+ if (d.MESSAGE == null)
+ buf += "[blob data]";
+ else if (d.MESSAGE instanceof Array)
+ buf += "[" + formatBytes(d.MESSAGE.length) + " blob data]";
+ else
+ buf += escapeHTML(d.MESSAGE);
+
+ buf += '</a></td></tr>';
+ }
+
+ logs.innerHTML = '<tbody>' + buf + '</tbody>';
+
+ if (fc != null) {
+ first_cursor = fc;
+ localStorage["cursor"] = fc;
+ }
+ if (lc != null)
+ last_cursor = lc;
+ }
+
+ function entriesMore() {
+ setNEntries(getNEntries() + 10);
+ entriesLoad(first_cursor);
+ }
+
+ function entriesLess() {
+ setNEntries(getNEntries() - 10);
+ entriesLoad(first_cursor);
+ }
+
+ function onResultMessageClick(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ var d = JSON.parse(event.currentTarget.responseText);
+
+ document.getElementById("diventry").style.display = "block";
+ entry = document.getElementById("tableentry");
+
+ var buf = "";
+ for (var key in d) {
+ var data = d[key];
+
+ if (data == null)
+ data = "[blob data]";
+ else if (data instanceof Array)
+ data = "[" + formatBytes(data.length) + " blob data]";
+ else
+ data = escapeHTML(data);
+
+ buf += '<tr><td class="field">' + key + '</td><td class="data">' + data + '</td></tr>';
+ }
+ entry.innerHTML = '<tbody>' + buf + '</tbody>';
+ }
+
+ function onMessageClick(t) {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/entries?discrete");
+ request.onreadystatechange = onResultMessageClick;
+ request.setRequestHeader("Accept", "application/json");
+ request.setRequestHeader("Range", "entries=" + t + ":0:1");
+ request.send(null);
+ }
+
+ function onKeyUp(event) {
+ switch (event.keyCode) {
+ case 8:
+ case 37:
+ case 75:
+ entriesLoadPrevious();
+ break;
+ case 32:
+ case 39:
+ case 74:
+ entriesLoadNext();
+ break;
+
+ case 71:
+ if (event.shiftKey)
+ entriesLoadTail();
+ else
+ entriesLoadHead();
+ break;
+ case 171:
+ entriesMore();
+ break;
+ case 173:
+ entriesLess();
+ break;
+ }
+ }
+
+ function onMouseWheel(event) {
+ if (event.detail < 0 || event.wheelDelta > 0)
+ entriesLoadPrevious();
+ else
+ entriesLoadNext();
+ }
+
+ function onResultFilterFocus(event) {
+ if ((event.currentTarget.readyState != 4) ||
+ (event.currentTarget.status != 200 && event.currentTarget.status != 0))
+ return;
+
+ f = document.getElementById("filter");
+
+ var l = event.currentTarget.responseText.split('\n');
+ var buf = '<option>No filter</option>';
+ var j = -1;
+
+ for (i in l) {
+
+ if (l[i] == '')
+ continue;
+
+ var d = JSON.parse(l[i]);
+ if (d._SYSTEMD_UNIT == undefined)
+ continue;
+
+ buf += '<option value="' + escape(d._SYSTEMD_UNIT) + '">' + escapeHTML(d._SYSTEMD_UNIT) + '</option>';
+
+ if (d._SYSTEMD_UNIT == localStorage["filter"])
+ j = i;
+ }
+
+ if (j < 0) {
+ if (localStorage["filter"] != null && localStorage["filter"] != "") {
+ buf += '<option value="' + escape(localStorage["filter"]) + '">' + escapeHTML(localStorage["filter"]) + '</option>';
+ j = i + 1;
+ } else
+ j = 0;
+ }
+
+ f.innerHTML = buf;
+ f.selectedIndex = j;
+ }
+
+ function onFilterFocus(w) {
+ var request = new XMLHttpRequest();
+ request.open("GET", "/fields/_SYSTEMD_UNIT");
+ request.onreadystatechange = onResultFilterFocus;
+ request.setRequestHeader("Accept", "application/json");
+ request.send(null);
+ }
+
+ function onFilterChange(w) {
+ if (w.selectedIndex <= 0)
+ localStorage["filter"] = "";
+ else
+ localStorage["filter"] = unescape(w.options[w.selectedIndex].value);
+
+ entriesLoadHead();
+ }
+
+ function onBootChange(w) {
+ localStorage["boot"] = w.checked ? "1" : "0";
+ entriesLoadHead();
+ }
+
+ function initFilter() {
+ f = document.getElementById("filter");
+
+ var buf = '<option>No filter</option>';
+
+ var filter = localStorage["filter"];
+ if (filter != null && filter != "") {
+ buf += '<option value="' + escape(filter) + '">' + escapeHTML(filter) + '</option>';
+ j = 1;
+ } else
+ j = 0;
+
+ f.innerHTML = buf;
+ f.selectedIndex = j;
+ }
+
+ function installHandlers() {
+ document.onkeyup = onKeyUp;
+
+ logs = document.getElementById("divlogs");
+ logs.addEventListener("mousewheel", onMouseWheel, false);
+ logs.addEventListener("DOMMouseScroll", onMouseWheel, false);
+ }
+
+ machineLoad();
+ entriesLoad(null);
+ showNEntries(getNEntries());
+ initFilter();
+ installHandlers();
+ </script>
+</body>
+</html>
diff --git a/src/grp-journal/grp-remote/log-generator.py b/src/grp-journal/grp-remote/log-generator.py
new file mode 100755
index 0000000000..fd6964e758
--- /dev/null
+++ b/src/grp-journal/grp-remote/log-generator.py
@@ -0,0 +1,76 @@
+#!/usr/bin/python
+from __future__ import print_function
+import sys
+import argparse
+
+PARSER = argparse.ArgumentParser()
+PARSER.add_argument('n', type=int)
+PARSER.add_argument('--dots', action='store_true')
+PARSER.add_argument('--data-size', type=int, default=4000)
+PARSER.add_argument('--data-type', choices={'random', 'simple'})
+OPTIONS = PARSER.parse_args()
+
+template = """\
+__CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c
+__REALTIME_TIMESTAMP={realtime_ts}
+__MONOTONIC_TIMESTAMP={monotonic_ts}
+_BOOT_ID=f446871715504074bf7049ef0718fa93
+_TRANSPORT=syslog
+PRIORITY={priority}
+SYSLOG_FACILITY={facility}
+SYSLOG_IDENTIFIER=/USR/SBIN/CRON
+MESSAGE={message}
+_UID=0
+_GID=0
+_MACHINE_ID=69121ca41d12c1b69a7960174c27b618
+_HOSTNAME=hostname
+SYSLOG_PID=25721
+_PID=25721
+_SOURCE_REALTIME_TIMESTAMP={source_realtime_ts}
+DATA={data}
+"""
+
+m = 0x198603b12d7
+realtime_ts = 1404101101501873
+monotonic_ts = 1753961140951
+source_realtime_ts = 1404101101483516
+priority = 3
+facility = 6
+
+src = open('/dev/urandom', 'rb')
+
+bytes = 0
+counter = 0
+
+for i in range(OPTIONS.n):
+ message = repr(src.read(2000))
+ if OPTIONS.data_type == 'random':
+ data = repr(src.read(OPTIONS.data_size))
+ else:
+ # keep the pattern non-repeating so we get a different blob every time
+ data = '{:0{}}'.format(counter, OPTIONS.data_size)
+ counter += 1
+
+ entry = template.format(m=m,
+ realtime_ts=realtime_ts,
+ monotonic_ts=monotonic_ts,
+ source_realtime_ts=source_realtime_ts,
+ priority=priority,
+ facility=facility,
+ message=message,
+ data=data)
+ m += 1
+ realtime_ts += 1
+ monotonic_ts += 1
+ source_realtime_ts += 1
+
+ bytes += len(entry)
+
+ print(entry)
+
+ if OPTIONS.dots:
+ print('.', file=sys.stderr, end='', flush=True)
+
+if OPTIONS.dots:
+ print(file=sys.stderr)
+print('Wrote {} bytes'.format(bytes), file=sys.stderr)
diff --git a/src/grp-journal/grp-remote/microhttpd-util.c b/src/grp-journal/grp-remote/microhttpd-util.c
new file mode 100644
index 0000000000..328e3b5a52
--- /dev/null
+++ b/src/grp-journal/grp-remote/microhttpd-util.c
@@ -0,0 +1,328 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ 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 <stddef.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#endif
+
+#include "basic/alloc-util.h"
+#include "basic/log.h"
+#include "basic/macro.h"
+#include "basic/string-util.h"
+#include "basic/strv.h"
+#include "basic/util.h"
+
+#include "microhttpd-util.h"
+
+void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
+ char *f;
+
+ f = strjoina("microhttpd: ", fmt);
+
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ log_internalv(LOG_INFO, 0, NULL, 0, NULL, f, ap);
+ REENABLE_WARNING;
+}
+
+
+static int mhd_respond_internal(struct MHD_Connection *connection,
+ enum MHD_RequestTerminationCode code,
+ char *buffer,
+ size_t size,
+ enum MHD_ResponseMemoryMode mode) {
+ struct MHD_Response *response;
+ int r;
+
+ assert(connection);
+
+ response = MHD_create_response_from_buffer(size, buffer, mode);
+ if (!response)
+ return MHD_NO;
+
+ log_debug("Queing response %u: %s", code, buffer);
+ MHD_add_response_header(response, "Content-Type", "text/plain");
+ r = MHD_queue_response(connection, code, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+int mhd_respond(struct MHD_Connection *connection,
+ enum MHD_RequestTerminationCode code,
+ const char *message) {
+
+ return mhd_respond_internal(connection, code,
+ (char*) message, strlen(message),
+ MHD_RESPMEM_PERSISTENT);
+}
+
+int mhd_respond_oom(struct MHD_Connection *connection) {
+ return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE, "Out of memory.\n");
+}
+
+int mhd_respondf(struct MHD_Connection *connection,
+ enum MHD_RequestTerminationCode code,
+ const char *format, ...) {
+
+ char *m;
+ int r;
+ va_list ap;
+
+ assert(connection);
+ assert(format);
+
+ va_start(ap, format);
+ r = vasprintf(&m, format, ap);
+ va_end(ap);
+
+ if (r < 0)
+ return respond_oom(connection);
+
+ return mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
+}
+
+#ifdef HAVE_GNUTLS
+
+static struct {
+ const char *const names[4];
+ int level;
+ bool enabled;
+} gnutls_log_map[] = {
+ { {"0"}, LOG_DEBUG },
+ { {"1", "audit"}, LOG_WARNING, true}, /* gnutls session audit */
+ { {"2", "assert"}, LOG_DEBUG }, /* gnutls assert log */
+ { {"3", "hsk", "ext"}, LOG_DEBUG }, /* gnutls handshake log */
+ { {"4", "rec"}, LOG_DEBUG }, /* gnutls record log */
+ { {"5", "dtls"}, LOG_DEBUG }, /* gnutls DTLS log */
+ { {"6", "buf"}, LOG_DEBUG },
+ { {"7", "write", "read"}, LOG_DEBUG },
+ { {"8"}, LOG_DEBUG },
+ { {"9", "enc", "int"}, LOG_DEBUG },
+};
+
+static void log_func_gnutls(int level, const char *message) {
+ assert_se(message);
+
+ if (0 <= level && level < (int) ELEMENTSOF(gnutls_log_map)) {
+ if (gnutls_log_map[level].enabled)
+ log_internal(gnutls_log_map[level].level, 0, NULL, 0, NULL, "gnutls %d/%s: %s", level, gnutls_log_map[level].names[1], message);
+ } else {
+ log_debug("Received GNUTLS message with unknown level %d.", level);
+ log_internal(LOG_DEBUG, 0, NULL, 0, NULL, "gnutls: %s", message);
+ }
+}
+
+static void log_reset_gnutls_level(void) {
+ int i;
+
+ for (i = ELEMENTSOF(gnutls_log_map) - 1; i >= 0; i--)
+ if (gnutls_log_map[i].enabled) {
+ log_debug("Setting gnutls log level to %d", i);
+ gnutls_global_set_log_level(i);
+ break;
+ }
+}
+
+static int log_enable_gnutls_category(const char *cat) {
+ unsigned i;
+
+ if (streq(cat, "all")) {
+ for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
+ gnutls_log_map[i].enabled = true;
+ log_reset_gnutls_level();
+ return 0;
+ } else
+ for (i = 0; i < ELEMENTSOF(gnutls_log_map); i++)
+ if (strv_contains((char**)gnutls_log_map[i].names, cat)) {
+ gnutls_log_map[i].enabled = true;
+ log_reset_gnutls_level();
+ return 0;
+ }
+ log_error("No such log category: %s", cat);
+ return -EINVAL;
+}
+
+int setup_gnutls_logger(char **categories) {
+ char **cat;
+ int r;
+
+ gnutls_global_set_log_function(log_func_gnutls);
+
+ if (categories) {
+ STRV_FOREACH(cat, categories) {
+ r = log_enable_gnutls_category(*cat);
+ if (r < 0)
+ return r;
+ }
+ } else
+ log_reset_gnutls_level();
+
+ return 0;
+}
+
+static int verify_cert_authorized(gnutls_session_t session) {
+ unsigned status;
+ gnutls_certificate_type_t type;
+ gnutls_datum_t out;
+ int r;
+
+ r = gnutls_certificate_verify_peers2(session, &status);
+ if (r < 0)
+ return log_error_errno(r, "gnutls_certificate_verify_peers2 failed: %m");
+
+ type = gnutls_certificate_type_get(session);
+ r = gnutls_certificate_verification_status_print(status, type, &out, 0);
+ if (r < 0)
+ return log_error_errno(r, "gnutls_certificate_verification_status_print failed: %m");
+
+ log_debug("Certificate status: %s", out.data);
+ gnutls_free(out.data);
+
+ return status == 0 ? 0 : -EPERM;
+}
+
+static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
+ const gnutls_datum_t *pcert;
+ unsigned listsize;
+ gnutls_x509_crt_t cert;
+ int r;
+
+ assert(session);
+ assert(client_cert);
+
+ pcert = gnutls_certificate_get_peers(session, &listsize);
+ if (!pcert || !listsize) {
+ log_error("Failed to retrieve certificate chain");
+ return -EINVAL;
+ }
+
+ r = gnutls_x509_crt_init(&cert);
+ if (r < 0) {
+ log_error("Failed to initialize client certificate");
+ return r;
+ }
+
+ /* Note that by passing values between 0 and listsize here, you
+ can get access to the CA's certs */
+ r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
+ if (r < 0) {
+ log_error("Failed to import client certificate");
+ gnutls_x509_crt_deinit(cert);
+ return r;
+ }
+
+ *client_cert = cert;
+ return 0;
+}
+
+static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
+ size_t len = 0;
+ int r;
+
+ assert(buf);
+ assert(*buf == NULL);
+
+ r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
+ if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ log_error("gnutls_x509_crt_get_dn failed");
+ return r;
+ }
+
+ *buf = malloc(len);
+ if (!*buf)
+ return log_oom();
+
+ gnutls_x509_crt_get_dn(client_cert, *buf, &len);
+ return 0;
+}
+
+static inline void gnutls_x509_crt_deinitp(gnutls_x509_crt_t *p) {
+ gnutls_x509_crt_deinit(*p);
+}
+
+int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
+ const union MHD_ConnectionInfo *ci;
+ gnutls_session_t session;
+ _cleanup_(gnutls_x509_crt_deinitp) gnutls_x509_crt_t client_cert = NULL;
+ _cleanup_free_ char *buf = NULL;
+ int r;
+
+ assert(connection);
+ assert(code);
+
+ *code = 0;
+
+ ci = MHD_get_connection_info(connection,
+ MHD_CONNECTION_INFO_GNUTLS_SESSION);
+ if (!ci) {
+ log_error("MHD_get_connection_info failed: session is unencrypted");
+ *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
+ "Encrypted connection is required");
+ return -EPERM;
+ }
+ session = ci->tls_session;
+ assert(session);
+
+ r = get_client_cert(session, &client_cert);
+ if (r < 0) {
+ *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+ "Authorization through certificate is required");
+ return -EPERM;
+ }
+
+ r = get_auth_dn(client_cert, &buf);
+ if (r < 0) {
+ *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+ "Failed to determine distinguished name from certificate");
+ return -EPERM;
+ }
+
+ log_debug("Connection from %s", buf);
+
+ if (hostname) {
+ *hostname = buf;
+ buf = NULL;
+ }
+
+ r = verify_cert_authorized(session);
+ if (r < 0) {
+ log_warning("Client is not authorized");
+ *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+ "Client certificate not signed by recognized authority");
+ }
+ return r;
+}
+
+#else
+int check_permissions(struct MHD_Connection *connection, int *code, char **hostname) {
+ return -EPERM;
+}
+
+int setup_gnutls_logger(char **categories) {
+ if (categories)
+ log_notice("Ignoring specified gnutls logging categories — gnutls not available.");
+ return 0;
+}
+#endif
diff --git a/src/grp-journal/grp-remote/microhttpd-util.h b/src/grp-journal/grp-remote/microhttpd-util.h
new file mode 100644
index 0000000000..178e78f892
--- /dev/null
+++ b/src/grp-journal/grp-remote/microhttpd-util.h
@@ -0,0 +1,60 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ 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 <microhttpd.h>
+#include <stdarg.h>
+
+#include "basic/macro.h"
+
+/* Compatiblity with libmicrohttpd < 0.9.38 */
+#ifndef MHD_HTTP_NOT_ACCEPTABLE
+#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE
+#endif
+
+#if MHD_VERSION < 0x00094203
+#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset
+#endif
+
+void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
+
+/* respond_oom() must be usable with return, hence this form. */
+#define respond_oom(connection) log_oom(), mhd_respond_oom(connection)
+
+int mhd_respondf(struct MHD_Connection *connection,
+ unsigned code,
+ const char *format, ...) _printf_(3,4);
+
+int mhd_respond(struct MHD_Connection *connection,
+ unsigned code,
+ const char *message);
+
+int mhd_respond_oom(struct MHD_Connection *connection);
+
+int check_permissions(struct MHD_Connection *connection, int *code, char **hostname);
+
+/* Set gnutls internal logging function to a callback which uses our
+ * own logging framework.
+ *
+ * gnutls categories are additionally filtered by our internal log
+ * level, so it should be set fairly high to capture all potentially
+ * interesting events without overwhelming detail.
+ */
+int setup_gnutls_logger(char **categories);
diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile b/src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile
new file mode 100644
index 0000000000..2003240fbe
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/Makefile
@@ -0,0 +1,68 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+ifneq ($(HAVE_MICROHTTPD),)
+gatewayddocumentrootdir=$(pkgdatadir)/gatewayd
+
+rootlibexec_PROGRAMS += \
+ systemd-journal-gatewayd
+
+systemd_journal_gatewayd_SOURCES = \
+ src/journal-remote/journal-gatewayd.c \
+ src/journal-remote/microhttpd-util.h \
+ src/journal-remote/microhttpd-util.c
+
+systemd_journal_gatewayd_LDADD = \
+ libshared.la \
+ $(MICROHTTPD_LIBS)
+
+ifneq ($(HAVE_GNUTLS),)
+systemd_journal_gatewayd_LDADD += \
+ $(GNUTLS_LIBS)
+endif # HAVE_GNUTLS
+
+systemd_journal_gatewayd_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(MICROHTTPD_CFLAGS)
+
+systemd_journal_gatewayd_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -DDOCUMENT_ROOT=\"$(gatewayddocumentrootdir)\"
+
+dist_systemunit_DATA += \
+ units/systemd-journal-gatewayd.socket
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-gatewayd.service
+
+dist_gatewayddocumentroot_DATA = \
+ src/journal-remote/browse.html
+
+endif # HAVE_MICROHTTPD
+
+EXTRA_DIST += \
+ units/systemd-journal-gatewayd.service.in
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c b/src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c
new file mode 100644
index 0000000000..76b241a14b
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/journal-gatewayd.c
@@ -0,0 +1,1077 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <getopt.h>
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+#include <microhttpd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-journal.h>
+
+#include "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/hostname-util.h"
+#include "basic/log.h"
+#include "basic/parse-util.h"
+#include "basic/sigbus.h"
+#include "basic/util.h"
+#include "microhttpd-util.h"
+#include "shared/bus-util.h"
+#include "shared/logs-show.h"
+
+#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
+
+static char *arg_key_pem = NULL;
+static char *arg_cert_pem = NULL;
+static char *arg_trust_pem = NULL;
+
+typedef struct RequestMeta {
+ sd_journal *journal;
+
+ OutputMode mode;
+
+ char *cursor;
+ int64_t n_skip;
+ uint64_t n_entries;
+ bool n_entries_set;
+
+ FILE *tmp;
+ uint64_t delta, size;
+
+ int argument_parse_error;
+
+ bool follow;
+ bool discrete;
+
+ uint64_t n_fields;
+ bool n_fields_set;
+} RequestMeta;
+
+static const char* const mime_types[_OUTPUT_MODE_MAX] = {
+ [OUTPUT_SHORT] = "text/plain",
+ [OUTPUT_JSON] = "application/json",
+ [OUTPUT_JSON_SSE] = "text/event-stream",
+ [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
+};
+
+static RequestMeta *request_meta(void **connection_cls) {
+ RequestMeta *m;
+
+ assert(connection_cls);
+ if (*connection_cls)
+ return *connection_cls;
+
+ m = new0(RequestMeta, 1);
+ if (!m)
+ return NULL;
+
+ *connection_cls = m;
+ return m;
+}
+
+static void request_meta_free(
+ void *cls,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ enum MHD_RequestTerminationCode toe) {
+
+ RequestMeta *m = *connection_cls;
+
+ if (!m)
+ return;
+
+ sd_journal_close(m->journal);
+
+ safe_fclose(m->tmp);
+
+ free(m->cursor);
+ free(m);
+}
+
+static int open_journal(RequestMeta *m) {
+ assert(m);
+
+ if (m->journal)
+ return 0;
+
+ return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
+}
+
+static int request_meta_ensure_tmp(RequestMeta *m) {
+ assert(m);
+
+ if (m->tmp)
+ rewind(m->tmp);
+ else {
+ int fd;
+
+ fd = open_tmpfile_unlinkable("/tmp", O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ m->tmp = fdopen(fd, "w+");
+ if (!m->tmp) {
+ safe_close(fd);
+ return -errno;
+ }
+ }
+
+ return 0;
+}
+
+static ssize_t request_reader_entries(
+ void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max) {
+
+ RequestMeta *m = cls;
+ int r;
+ size_t n, k;
+
+ assert(m);
+ assert(buf);
+ assert(max > 0);
+ assert(pos >= m->delta);
+
+ pos -= m->delta;
+
+ while (pos >= m->size) {
+ off_t sz;
+
+ /* End of this entry, so let's serialize the next
+ * one */
+
+ if (m->n_entries_set &&
+ m->n_entries <= 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+
+ if (m->n_skip < 0)
+ r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
+ else if (m->n_skip > 0)
+ r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
+ else
+ r = sd_journal_next(m->journal);
+
+ if (r < 0) {
+ log_error_errno(r, "Failed to advance journal pointer: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ } else if (r == 0) {
+
+ if (m->follow) {
+ r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT);
+ if (r < 0) {
+ log_error_errno(r, "Couldn't wait for journal event: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+ if (r == SD_JOURNAL_NOP)
+ break;
+
+ continue;
+ }
+
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+
+ if (m->discrete) {
+ assert(m->cursor);
+
+ r = sd_journal_test_cursor(m->journal, m->cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to test cursor: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ if (r == 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+ }
+
+ pos -= m->size;
+ m->delta += m->size;
+
+ if (m->n_entries_set)
+ m->n_entries -= 1;
+
+ m->n_skip = 0;
+
+ r = request_meta_ensure_tmp(m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create temporary file: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to serialize item: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ sz = ftello(m->tmp);
+ if (sz == (off_t) -1) {
+ log_error_errno(errno, "Failed to retrieve file position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ m->size = (uint64_t) sz;
+ }
+
+ if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+ log_error_errno(errno, "Failed to seek to position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ n = m->size - pos;
+ if (n < 1)
+ return 0;
+ if (n > max)
+ n = max;
+
+ errno = 0;
+ k = fread(buf, 1, n, m->tmp);
+ if (k != n) {
+ log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ return (ssize_t) k;
+}
+
+static int request_parse_accept(
+ RequestMeta *m,
+ struct MHD_Connection *connection) {
+
+ const char *header;
+
+ assert(m);
+ assert(connection);
+
+ header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
+ if (!header)
+ return 0;
+
+ if (streq(header, mime_types[OUTPUT_JSON]))
+ m->mode = OUTPUT_JSON;
+ else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
+ m->mode = OUTPUT_JSON_SSE;
+ else if (streq(header, mime_types[OUTPUT_EXPORT]))
+ m->mode = OUTPUT_EXPORT;
+ else
+ m->mode = OUTPUT_SHORT;
+
+ return 0;
+}
+
+static int request_parse_range(
+ RequestMeta *m,
+ struct MHD_Connection *connection) {
+
+ const char *range, *colon, *colon2;
+ int r;
+
+ assert(m);
+ assert(connection);
+
+ range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
+ if (!range)
+ return 0;
+
+ if (!startswith(range, "entries="))
+ return 0;
+
+ range += 8;
+ range += strspn(range, WHITESPACE);
+
+ colon = strchr(range, ':');
+ if (!colon)
+ m->cursor = strdup(range);
+ else {
+ const char *p;
+
+ colon2 = strchr(colon + 1, ':');
+ if (colon2) {
+ _cleanup_free_ char *t;
+
+ t = strndup(colon + 1, colon2 - colon - 1);
+ if (!t)
+ return -ENOMEM;
+
+ r = safe_atoi64(t, &m->n_skip);
+ if (r < 0)
+ return r;
+ }
+
+ p = (colon2 ? colon2 : colon) + 1;
+ if (*p) {
+ r = safe_atou64(p, &m->n_entries);
+ if (r < 0)
+ return r;
+
+ if (m->n_entries <= 0)
+ return -EINVAL;
+
+ m->n_entries_set = true;
+ }
+
+ m->cursor = strndup(range, colon - range);
+ }
+
+ if (!m->cursor)
+ return -ENOMEM;
+
+ m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
+ if (isempty(m->cursor))
+ m->cursor = mfree(m->cursor);
+
+ return 0;
+}
+
+static int request_parse_arguments_iterator(
+ void *cls,
+ enum MHD_ValueKind kind,
+ const char *key,
+ const char *value) {
+
+ RequestMeta *m = cls;
+ _cleanup_free_ char *p = NULL;
+ int r;
+
+ assert(m);
+
+ if (isempty(key)) {
+ m->argument_parse_error = -EINVAL;
+ return MHD_NO;
+ }
+
+ if (streq(key, "follow")) {
+ if (isempty(value)) {
+ m->follow = true;
+ return MHD_YES;
+ }
+
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ m->follow = r;
+ return MHD_YES;
+ }
+
+ if (streq(key, "discrete")) {
+ if (isempty(value)) {
+ m->discrete = true;
+ return MHD_YES;
+ }
+
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ m->discrete = r;
+ return MHD_YES;
+ }
+
+ if (streq(key, "boot")) {
+ if (isempty(value))
+ r = true;
+ else {
+ r = parse_boolean(value);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+ }
+
+ if (r) {
+ char match[9 + 32 + 1] = "_BOOT_ID=";
+ sd_id128_t bid;
+
+ r = sd_id128_get_boot(&bid);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get boot ID: %m");
+ return MHD_NO;
+ }
+
+ sd_id128_to_string(bid, match + 9);
+ r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+ }
+
+ return MHD_YES;
+ }
+
+ p = strjoin(key, "=", strempty(value), NULL);
+ if (!p) {
+ m->argument_parse_error = log_oom();
+ return MHD_NO;
+ }
+
+ r = sd_journal_add_match(m->journal, p, 0);
+ if (r < 0) {
+ m->argument_parse_error = r;
+ return MHD_NO;
+ }
+
+ return MHD_YES;
+}
+
+static int request_parse_arguments(
+ RequestMeta *m,
+ struct MHD_Connection *connection) {
+
+ assert(m);
+ assert(connection);
+
+ m->argument_parse_error = 0;
+ MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
+
+ return m->argument_parse_error;
+}
+
+static int request_handler_entries(
+ struct MHD_Connection *connection,
+ void *connection_cls) {
+
+ struct MHD_Response *response;
+ RequestMeta *m = connection_cls;
+ int r;
+
+ assert(connection);
+ assert(m);
+
+ r = open_journal(m);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+ if (request_parse_accept(m, connection) < 0)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+ if (request_parse_range(m, connection) < 0)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
+
+ if (request_parse_arguments(m, connection) < 0)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
+
+ if (m->discrete) {
+ if (!m->cursor)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
+
+ m->n_entries = 1;
+ m->n_entries_set = true;
+ }
+
+ if (m->cursor)
+ r = sd_journal_seek_cursor(m->journal, m->cursor);
+ else if (m->n_skip >= 0)
+ r = sd_journal_seek_head(m->journal);
+ else if (m->n_skip < 0)
+ r = sd_journal_seek_tail(m->journal);
+ if (r < 0)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
+
+ response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
+ if (!response)
+ return respond_oom(connection);
+
+ MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
+
+ r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
+ const char *eq;
+ size_t j;
+
+ eq = memchr(d, '=', l);
+ if (!eq)
+ return -EINVAL;
+
+ j = l - (eq - d + 1);
+
+ if (m == OUTPUT_JSON) {
+ fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
+ json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
+ fputs(" }\n", f);
+ } else {
+ fwrite(eq+1, 1, j, f);
+ fputc('\n', f);
+ }
+
+ return 0;
+}
+
+static ssize_t request_reader_fields(
+ void *cls,
+ uint64_t pos,
+ char *buf,
+ size_t max) {
+
+ RequestMeta *m = cls;
+ int r;
+ size_t n, k;
+
+ assert(m);
+ assert(buf);
+ assert(max > 0);
+ assert(pos >= m->delta);
+
+ pos -= m->delta;
+
+ while (pos >= m->size) {
+ off_t sz;
+ const void *d;
+ size_t l;
+
+ /* End of this field, so let's serialize the next
+ * one */
+
+ if (m->n_fields_set &&
+ m->n_fields <= 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+
+ r = sd_journal_enumerate_unique(m->journal, &d, &l);
+ if (r < 0) {
+ log_error_errno(r, "Failed to advance field index: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ } else if (r == 0)
+ return MHD_CONTENT_READER_END_OF_STREAM;
+
+ pos -= m->size;
+ m->delta += m->size;
+
+ if (m->n_fields_set)
+ m->n_fields -= 1;
+
+ r = request_meta_ensure_tmp(m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create temporary file: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ r = output_field(m->tmp, m->mode, d, l);
+ if (r < 0) {
+ log_error_errno(r, "Failed to serialize item: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ sz = ftello(m->tmp);
+ if (sz == (off_t) -1) {
+ log_error_errno(errno, "Failed to retrieve file position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ m->size = (uint64_t) sz;
+ }
+
+ if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
+ log_error_errno(errno, "Failed to seek to position: %m");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ n = m->size - pos;
+ if (n > max)
+ n = max;
+
+ errno = 0;
+ k = fread(buf, 1, n, m->tmp);
+ if (k != n) {
+ log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
+ return MHD_CONTENT_READER_END_WITH_ERROR;
+ }
+
+ return (ssize_t) k;
+}
+
+static int request_handler_fields(
+ struct MHD_Connection *connection,
+ const char *field,
+ void *connection_cls) {
+
+ struct MHD_Response *response;
+ RequestMeta *m = connection_cls;
+ int r;
+
+ assert(connection);
+ assert(m);
+
+ r = open_journal(m);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+ if (request_parse_accept(m, connection) < 0)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+
+ r = sd_journal_query_unique(m->journal, field);
+ if (r < 0)
+ return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
+
+ response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
+ if (!response)
+ return respond_oom(connection);
+
+ MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
+
+ r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static int request_handler_redirect(
+ struct MHD_Connection *connection,
+ const char *target) {
+
+ char *page;
+ struct MHD_Response *response;
+ int ret;
+
+ assert(connection);
+ assert(target);
+
+ if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
+ return respond_oom(connection);
+
+ response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
+ if (!response) {
+ free(page);
+ return respond_oom(connection);
+ }
+
+ MHD_add_response_header(response, "Content-Type", "text/html");
+ MHD_add_response_header(response, "Location", target);
+
+ ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
+ MHD_destroy_response(response);
+
+ return ret;
+}
+
+static int request_handler_file(
+ struct MHD_Connection *connection,
+ const char *path,
+ const char *mime_type) {
+
+ struct MHD_Response *response;
+ int ret;
+ _cleanup_close_ int fd = -1;
+ struct stat st;
+
+ assert(connection);
+ assert(path);
+ assert(mime_type);
+
+ fd = open(path, O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
+
+ if (fstat(fd, &st) < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
+
+ response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0);
+ if (!response)
+ return respond_oom(connection);
+
+ fd = -1;
+
+ MHD_add_response_header(response, "Content-Type", mime_type);
+
+ ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return ret;
+}
+
+static int get_virtualization(char **v) {
+ _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
+ char *b = NULL;
+ int r;
+
+ r = sd_bus_default_system(&bus);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_get_property_string(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "Virtualization",
+ NULL,
+ &b);
+ if (r < 0)
+ return r;
+
+ if (isempty(b)) {
+ free(b);
+ *v = NULL;
+ return 0;
+ }
+
+ *v = b;
+ return 1;
+}
+
+static int request_handler_machine(
+ struct MHD_Connection *connection,
+ void *connection_cls) {
+
+ struct MHD_Response *response;
+ RequestMeta *m = connection_cls;
+ int r;
+ _cleanup_free_ char* hostname = NULL, *os_name = NULL;
+ uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
+ char *json;
+ sd_id128_t mid, bid;
+ _cleanup_free_ char *v = NULL;
+
+ assert(connection);
+ assert(m);
+
+ r = open_journal(m);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
+
+ r = sd_id128_get_boot(&bid);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
+
+ hostname = gethostname_malloc();
+ if (!hostname)
+ return respond_oom(connection);
+
+ r = sd_journal_get_usage(m->journal, &usage);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+
+ r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
+ if (r < 0)
+ return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+
+ if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
+ (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
+
+ get_virtualization(&v);
+
+ r = asprintf(&json,
+ "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
+ "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
+ "\"hostname\" : \"%s\","
+ "\"os_pretty_name\" : \"%s\","
+ "\"virtualization\" : \"%s\","
+ "\"usage\" : \"%"PRIu64"\","
+ "\"cutoff_from_realtime\" : \"%"PRIu64"\","
+ "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
+ SD_ID128_FORMAT_VAL(mid),
+ SD_ID128_FORMAT_VAL(bid),
+ hostname_cleanup(hostname),
+ os_name ? os_name : "GNU/Linux",
+ v ? v : "bare",
+ usage,
+ cutoff_from,
+ cutoff_to);
+
+ if (r < 0)
+ return respond_oom(connection);
+
+ response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
+ if (!response) {
+ free(json);
+ return respond_oom(connection);
+ }
+
+ MHD_add_response_header(response, "Content-Type", "application/json");
+ r = MHD_queue_response(connection, MHD_HTTP_OK, response);
+ MHD_destroy_response(response);
+
+ return r;
+}
+
+static int request_handler(
+ void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **connection_cls) {
+ int r, code;
+
+ assert(connection);
+ assert(connection_cls);
+ assert(url);
+ assert(method);
+
+ if (!streq(method, "GET"))
+ return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE,
+ "Unsupported method.\n");
+
+
+ if (!*connection_cls) {
+ if (!request_meta(connection_cls))
+ return respond_oom(connection);
+ return MHD_YES;
+ }
+
+ if (arg_trust_pem) {
+ r = check_permissions(connection, &code, NULL);
+ if (r < 0)
+ return code;
+ }
+
+ if (streq(url, "/"))
+ return request_handler_redirect(connection, "/browse");
+
+ if (streq(url, "/entries"))
+ return request_handler_entries(connection, *connection_cls);
+
+ if (startswith(url, "/fields/"))
+ return request_handler_fields(connection, url + 8, *connection_cls);
+
+ if (streq(url, "/browse"))
+ return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
+
+ if (streq(url, "/machine"))
+ return request_handler_machine(connection, *connection_cls);
+
+ return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] ...\n\n"
+ "HTTP server for journal events.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --cert=CERT.PEM Server certificate in PEM format\n"
+ " --key=KEY.PEM Server key in PEM format\n"
+ " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
+ program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_KEY,
+ ARG_CERT,
+ ARG_TRUST,
+ };
+
+ int r, c;
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "key", required_argument, NULL, ARG_KEY },
+ { "cert", required_argument, NULL, ARG_CERT },
+ { "trust", required_argument, NULL, ARG_TRUST },
+ {}
+ };
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+
+ switch(c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_KEY:
+ if (arg_key_pem) {
+ log_error("Key file specified twice");
+ return -EINVAL;
+ }
+ r = read_full_file(optarg, &arg_key_pem, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read key file: %m");
+ assert(arg_key_pem);
+ break;
+
+ case ARG_CERT:
+ if (arg_cert_pem) {
+ log_error("Certificate file specified twice");
+ return -EINVAL;
+ }
+ r = read_full_file(optarg, &arg_cert_pem, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read certificate file: %m");
+ assert(arg_cert_pem);
+ break;
+
+ case ARG_TRUST:
+#ifdef HAVE_GNUTLS
+ if (arg_trust_pem) {
+ log_error("CA certificate file specified twice");
+ return -EINVAL;
+ }
+ r = read_full_file(optarg, &arg_trust_pem, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read CA certificate file: %m");
+ assert(arg_trust_pem);
+ break;
+#else
+ log_error("Option --trust is not available.");
+#endif
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ if (optind < argc) {
+ log_error("This program does not take arguments.");
+ return -EINVAL;
+ }
+
+ if (!!arg_key_pem != !!arg_cert_pem) {
+ log_error("Certificate and key files must be specified together");
+ return -EINVAL;
+ }
+
+ if (arg_trust_pem && !arg_key_pem) {
+ log_error("CA certificate can only be used with certificate file");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ struct MHD_Daemon *d = NULL;
+ int r, n;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r < 0)
+ return EXIT_FAILURE;
+ if (r == 0)
+ return EXIT_SUCCESS;
+
+ sigbus_install();
+
+ r = setup_gnutls_logger(NULL);
+ if (r < 0)
+ return EXIT_FAILURE;
+
+ n = sd_listen_fds(1);
+ if (n < 0) {
+ log_error_errno(n, "Failed to determine passed sockets: %m");
+ goto finish;
+ } else if (n > 1) {
+ log_error("Can't listen on more than one socket.");
+ goto finish;
+ } else {
+ struct MHD_OptionItem opts[] = {
+ { MHD_OPTION_NOTIFY_COMPLETED,
+ (intptr_t) request_meta_free, NULL },
+ { MHD_OPTION_EXTERNAL_LOGGER,
+ (intptr_t) microhttpd_logger, NULL },
+ { MHD_OPTION_END, 0, NULL },
+ { MHD_OPTION_END, 0, NULL },
+ { MHD_OPTION_END, 0, NULL },
+ { MHD_OPTION_END, 0, NULL },
+ { MHD_OPTION_END, 0, NULL }};
+ int opts_pos = 2;
+
+ /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order
+ * to make sure libmicrohttpd doesn't use shutdown()
+ * on our listening socket, which would break socket
+ * re-activation. See
+ *
+ * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
+ * https://github.com/systemd/systemd/pull/1286
+ */
+
+ int flags =
+ MHD_USE_DEBUG |
+ MHD_USE_DUAL_STACK |
+ MHD_USE_PIPE_FOR_SHUTDOWN |
+ MHD_USE_POLL |
+ MHD_USE_THREAD_PER_CONNECTION;
+
+ if (n > 0)
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
+ if (arg_key_pem) {
+ assert(arg_cert_pem);
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
+ flags |= MHD_USE_SSL;
+ }
+ if (arg_trust_pem) {
+ assert(flags & MHD_USE_SSL);
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
+ }
+
+ d = MHD_start_daemon(flags, 19531,
+ NULL, NULL,
+ request_handler, NULL,
+ MHD_OPTION_ARRAY, opts,
+ MHD_OPTION_END);
+ }
+
+ if (!d) {
+ log_error("Failed to start daemon!");
+ goto finish;
+ }
+
+ pause();
+
+ r = EXIT_SUCCESS;
+
+finish:
+ if (d)
+ MHD_stop_daemon(d);
+
+ return r;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.in b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.in
new file mode 100644
index 0000000000..f4f845841d
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.in
@@ -0,0 +1,29 @@
+# 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 Gateway Service
+Documentation=man:systemd-journal-gatewayd(8)
+Requires=systemd-journal-gatewayd.socket
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journal-gatewayd
+User=systemd-journal-gateway
+Group=systemd-journal-gateway
+SupplementaryGroups=systemd-journal
+PrivateTmp=yes
+PrivateDevices=yes
+PrivateNetwork=yes
+ProtectSystem=full
+ProtectHome=yes
+
+# If there are many split upjournal files we need a lot of fds to
+# access them all and combine
+LimitNOFILE=16384
+
+[Install]
+Also=systemd-journal-gatewayd.socket
diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.xml b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.xml
new file mode 100644
index 0000000000..9ed85c3950
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.service.xml
@@ -0,0 +1,302 @@
+<?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 2012 Zbigniew Jędrzejewski-Szmek
+
+ 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-gatewayd.service" conditional='HAVE_MICROHTTPD'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-gatewayd.service</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Zbigniew</firstname>
+ <surname>Jędrzejewski-Szmek</surname>
+ <email>zbyszek@in.waw.pl</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-gatewayd.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-gatewayd.service</refname>
+ <refname>systemd-journal-gatewayd.socket</refname>
+ <refname>systemd-journal-gatewayd</refname>
+ <refpurpose>HTTP server for journal events</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-journal-gatewayd.service</filename></para>
+ <para><filename>systemd-journal-gatewayd.socket</filename></para>
+ <cmdsynopsis>
+ <command>/usr/lib/systemd/systemd-journal-gatewayd</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-journal-gatewayd</command> serves journal
+ events over the network. Clients must connect using
+ HTTP. The server listens on port 19531 by default.
+ If <option>--cert=</option> is specified, the server expects
+ HTTPS connections.</para>
+
+ <para>The program is started by
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ and expects to receive a single socket. Use
+ <command>systemctl start systemd-journal-gatewayd.socket</command> to start
+ the service, and <command>systemctl enable systemd-journal-gatewayd.socket</command>
+ to have it started on boot.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--cert=</option></term>
+
+ <listitem><para>Specify the path to a file containing a server
+ certificate in PEM format. This option switches
+ <command>systemd-journal-gatewayd</command> into HTTPS mode
+ and must be used together with
+ <option>--key=</option>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--key=</option></term>
+
+ <listitem><para>Specify the path to a file containing a server
+ key in PEM format corresponding to the certificate specified
+ with <option>--cert=</option>.</para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Supported URLs</title>
+
+ <para>The following URLs are recognized:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><uri>/browse</uri></term>
+
+ <listitem><para>Interactive browsing.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><uri>/entries[?option1&amp;option2=value...]</uri></term>
+
+ <listitem><para>Retrieval of events in various formats.</para>
+
+ <para>The <option>Accept:</option> part of the HTTP header
+ determines the format. Supported values are described below.
+ </para>
+
+ <para>The <option>Range:</option> part of the HTTP header
+ determines the range of events returned. Supported values are
+ described below.
+ </para>
+
+ <para>GET parameters can be used to modify what events are
+ returned. Supported parameters are described below.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><uri>/machine</uri></term>
+
+ <listitem><para>Return a JSON structure describing the machine.</para>
+
+ <para>Example:
+ <programlisting>{ "machine_id" : "8cf7ed9d451ea194b77a9f118f3dc446",
+ "boot_id" : "3d3c9efaf556496a9b04259ee35df7f7",
+ "hostname" : "fedora",
+ "os_pretty_name" : "Fedora 19 (Rawhide)",
+ "virtualization" : "kvm",
+ ...}</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><uri>/fields/<replaceable>FIELD_NAME</replaceable></uri></term>
+
+ <listitem><para>Return a list of values of this field present in the logs.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Accept header</title>
+
+ <para>
+ <option>Accept: <replaceable>format</replaceable></option>
+ </para>
+
+ <para>Recognized formats:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><constant>text/plain</constant></term>
+
+ <listitem><para>The default. Plaintext syslog-like output,
+ one line per journal entry
+ (like <command>journalctl --output short</command>).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>application/json</constant></term>
+
+ <listitem><para>Entries are formatted as JSON data structures,
+ one per line
+ (like <command>journalctl --output json</command>).
+ See <ulink
+ url="http://www.freedesktop.org/wiki/Software/systemd/json">Journal
+ JSON Format</ulink> for more information.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>text/event-stream</constant></term>
+
+ <listitem><para>Entries are formatted as JSON data structures,
+ wrapped in a format suitable for <ulink
+ url="https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events">
+ Server-Sent Events</ulink>
+ (like <command>journalctl --output json-sse</command>).
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>application/vnd.fdo.journal</constant></term>
+
+ <listitem><para>Entries are serialized into a binary (but
+ mostly text-based) stream suitable for backups and network
+ transfer
+ (like <command>journalctl --output export</command>).
+ See <ulink
+ url="http://www.freedesktop.org/wiki/Software/systemd/export">Journal
+ Export Format</ulink> for more information.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Range header</title>
+
+ <para>
+ <option>Range: entries=<replaceable>cursor</replaceable>[[:<replaceable>num_skip</replaceable>]:<replaceable>num_entries</replaceable>]</option>
+ </para>
+
+ <para>where
+ <option>cursor</option> is a cursor string,
+ <option>num_skip</option> is an integer,
+ <option>num_entries</option> is an unsigned integer.
+ </para>
+
+ <para>Range defaults to all available events.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>URL GET parameters</title>
+
+ <para>Following parameters can be used as part of the URL:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><uri>follow</uri></term>
+
+ <listitem><para>wait for new events
+ (like <command>journalctl --follow</command>, except that
+ the number of events returned is not limited).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><uri>discrete</uri></term>
+
+ <listitem><para>Test that the specified cursor refers to an
+ entry in the journal. Returns just this entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><uri>boot</uri></term>
+
+ <listitem><para>Limit events to the current boot of the system
+ (like <command>journalctl --this-boot</command>).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><uri><replaceable>KEY</replaceable>=<replaceable>match</replaceable></uri></term>
+
+ <listitem><para>Match journal fields. See
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <para>Retrieve events from this boot from local journal
+ in <ulink
+ url="http://www.freedesktop.org/wiki/Software/systemd/export">Journal
+ Export Format</ulink>:
+ <programlisting>curl --silent -H'Accept: application/vnd.fdo.journal' \
+ 'http://localhost:19531/entries?boot'</programlisting>
+ </para>
+
+ <para>Listen for core dumps:
+ <programlisting>curl 'http://localhost:19531/entries?follow&amp;MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1'</programlisting></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/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.socket b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.socket
new file mode 100644
index 0000000000..79d9b04210
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.socket
@@ -0,0 +1,16 @@
+# 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 Gateway Service Socket
+Documentation=man:systemd-journal-gatewayd(8)
+
+[Socket]
+ListenStream=19531
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.sysusers b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.sysusers
new file mode 100644
index 0000000000..379be0852e
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-gatewayd/systemd-journal-gatewayd.sysusers
@@ -0,0 +1,8 @@
+# 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.
+
+u systemd-journal-gateway - "systemd Journal Gateway"
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/Makefile b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile
new file mode 100644
index 0000000000..b387eae386
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile
@@ -0,0 +1,85 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+ifneq ($(HAVE_MICROHTTPD),)
+rootlibexec_PROGRAMS += \
+ systemd-journal-remote
+
+systemd_journal_remote_SOURCES = \
+ src/journal-remote/journal-remote-parse.h \
+ src/journal-remote/journal-remote-parse.c \
+ src/journal-remote/journal-remote-write.h \
+ src/journal-remote/journal-remote-write.c \
+ src/journal-remote/journal-remote.h \
+ src/journal-remote/journal-remote.c
+
+systemd_journal_remote_LDADD = \
+ libjournal-core.la
+
+systemd_journal_remote_SOURCES += \
+ src/journal-remote/microhttpd-util.h \
+ src/journal-remote/microhttpd-util.c
+
+systemd_journal_remote_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(MICROHTTPD_CFLAGS)
+
+systemd_journal_remote_LDADD += \
+ $(MICROHTTPD_LIBS)
+
+ifneq ($(ENABLE_TMPFILES),)
+dist_tmpfiles_DATA += \
+ tmpfiles.d/systemd-remote.conf
+endif # ENABLE_TMPFILES
+
+ifneq ($(HAVE_GNUTLS),)
+systemd_journal_remote_LDADD += \
+ $(GNUTLS_LIBS)
+endif # HAVE_GNUTLS
+
+# systemd-journal-remote make sense mostly with full crypto stack
+dist_systemunit_DATA += \
+ units/systemd-journal-remote.socket
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-remote.service
+
+journal-remote-install-hook: journal-install-hook
+ -$(MKDIR_P) $(DESTDIR)/var/log/journal/remote
+ -chown 0:0 $(DESTDIR)/var/log/journal/remote
+ -chmod 755 $(DESTDIR)/var/log/journal/remote
+
+INSTALL_EXEC_HOOKS += journal-remote-install-hook
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-remote.conf
+
+EXTRA_DIST += \
+ units/systemd-journal-remote.service.in \
+ src/journal-remote/journal-remote.conf.in \
+ src/journal-remote/log-generator.py
+endif # HAVE_MICROHTTPD
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c
new file mode 100644
index 0000000000..c71fedd816
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c
@@ -0,0 +1,507 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/parse-util.h"
+#include "basic/string-util.h"
+#include "journald-native.h"
+
+#include "journal-remote-parse.h"
+
+#define LINE_CHUNK 8*1024u
+
+void source_free(RemoteSource *source) {
+ if (!source)
+ return;
+
+ if (source->fd >= 0 && !source->passive_fd) {
+ log_debug("Closing fd:%d (%s)", source->fd, source->name);
+ safe_close(source->fd);
+ }
+
+ free(source->name);
+ free(source->buf);
+ iovw_free_contents(&source->iovw);
+
+ log_debug("Writer ref count %i", source->writer->n_ref);
+ writer_unref(source->writer);
+
+ sd_event_source_unref(source->event);
+ sd_event_source_unref(source->buffer_event);
+
+ free(source);
+}
+
+/**
+ * Initialize zero-filled source with given values. On success, takes
+ * ownerhship of fd and writer, otherwise does not touch them.
+ */
+RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) {
+
+ RemoteSource *source;
+
+ log_debug("Creating source for %sfd:%d (%s)",
+ passive_fd ? "passive " : "", fd, name);
+
+ assert(fd >= 0);
+
+ source = new0(RemoteSource, 1);
+ if (!source)
+ return NULL;
+
+ source->fd = fd;
+ source->passive_fd = passive_fd;
+ source->name = name;
+ source->writer = writer;
+
+ return source;
+}
+
+static char* realloc_buffer(RemoteSource *source, size_t size) {
+ char *b, *old = source->buf;
+
+ b = GREEDY_REALLOC(source->buf, source->size, size);
+ if (!b)
+ return NULL;
+
+ iovw_rebase(&source->iovw, old, source->buf);
+
+ return b;
+}
+
+static int get_line(RemoteSource *source, char **line, size_t *size) {
+ ssize_t n;
+ char *c = NULL;
+
+ assert(source);
+ assert(source->state == STATE_LINE);
+ assert(source->offset <= source->filled);
+ assert(source->filled <= source->size);
+ assert(source->buf == NULL || source->size > 0);
+ assert(source->fd >= 0);
+
+ for (;;) {
+ if (source->buf) {
+ size_t start = MAX(source->scanned, source->offset);
+
+ c = memchr(source->buf + start, '\n',
+ source->filled - start);
+ if (c != NULL)
+ break;
+ }
+
+ source->scanned = source->filled;
+ if (source->scanned >= DATA_SIZE_MAX) {
+ log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX);
+ return -E2BIG;
+ }
+
+ if (source->passive_fd)
+ /* we have to wait for some data to come to us */
+ return -EAGAIN;
+
+ /* We know that source->filled is at most DATA_SIZE_MAX, so if
+ we reallocate it, we'll increase the size at least a bit. */
+ assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX);
+ if (source->size - source->filled < LINE_CHUNK &&
+ !realloc_buffer(source, MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX)))
+ return log_oom();
+
+ assert(source->buf);
+ assert(source->size - source->filled >= LINE_CHUNK ||
+ source->size == ENTRY_SIZE_MAX);
+
+ n = read(source->fd,
+ source->buf + source->filled,
+ source->size - source->filled);
+ if (n < 0) {
+ if (errno != EAGAIN)
+ log_error_errno(errno, "read(%d, ..., %zu): %m",
+ source->fd,
+ source->size - source->filled);
+ return -errno;
+ } else if (n == 0)
+ return 0;
+
+ source->filled += n;
+ }
+
+ *line = source->buf + source->offset;
+ *size = c + 1 - source->buf - source->offset;
+ source->offset += *size;
+
+ return 1;
+}
+
+int push_data(RemoteSource *source, const char *data, size_t size) {
+ assert(source);
+ assert(source->state != STATE_EOF);
+
+ if (!realloc_buffer(source, source->filled + size)) {
+ log_error("Failed to store received data of size %zu "
+ "(in addition to existing %zu bytes with %zu filled): %s",
+ size, source->size, source->filled, strerror(ENOMEM));
+ return -ENOMEM;
+ }
+
+ memcpy(source->buf + source->filled, data, size);
+ source->filled += size;
+
+ return 0;
+}
+
+static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
+
+ assert(source);
+ assert(source->state == STATE_DATA_START ||
+ source->state == STATE_DATA ||
+ source->state == STATE_DATA_FINISH);
+ assert(size <= DATA_SIZE_MAX);
+ assert(source->offset <= source->filled);
+ assert(source->filled <= source->size);
+ assert(source->buf != NULL || source->size == 0);
+ assert(source->buf == NULL || source->size > 0);
+ assert(source->fd >= 0);
+ assert(data);
+
+ while (source->filled - source->offset < size) {
+ int n;
+
+ if (source->passive_fd)
+ /* we have to wait for some data to come to us */
+ return -EAGAIN;
+
+ if (!realloc_buffer(source, source->offset + size))
+ return log_oom();
+
+ n = read(source->fd, source->buf + source->filled,
+ source->size - source->filled);
+ if (n < 0) {
+ if (errno != EAGAIN)
+ log_error_errno(errno, "read(%d, ..., %zu): %m", source->fd,
+ source->size - source->filled);
+ return -errno;
+ } else if (n == 0)
+ return 0;
+
+ source->filled += n;
+ }
+
+ *data = source->buf + source->offset;
+ source->offset += size;
+
+ return 1;
+}
+
+static int get_data_size(RemoteSource *source) {
+ int r;
+ void *data;
+
+ assert(source);
+ assert(source->state == STATE_DATA_START);
+ assert(source->data_size == 0);
+
+ r = fill_fixed_size(source, &data, sizeof(uint64_t));
+ if (r <= 0)
+ return r;
+
+ source->data_size = le64toh( *(uint64_t *) data );
+ if (source->data_size > DATA_SIZE_MAX) {
+ log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u",
+ source->data_size, DATA_SIZE_MAX);
+ return -EINVAL;
+ }
+ if (source->data_size == 0)
+ log_warning("Binary field with zero length");
+
+ return 1;
+}
+
+static int get_data_data(RemoteSource *source, void **data) {
+ int r;
+
+ assert(source);
+ assert(data);
+ assert(source->state == STATE_DATA);
+
+ r = fill_fixed_size(source, data, source->data_size);
+ if (r <= 0)
+ return r;
+
+ return 1;
+}
+
+static int get_data_newline(RemoteSource *source) {
+ int r;
+ char *data;
+
+ assert(source);
+ assert(source->state == STATE_DATA_FINISH);
+
+ r = fill_fixed_size(source, (void**) &data, 1);
+ if (r <= 0)
+ return r;
+
+ assert(data);
+ if (*data != '\n') {
+ log_error("expected newline, got '%c'", *data);
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+static int process_dunder(RemoteSource *source, char *line, size_t n) {
+ const char *timestamp;
+ int r;
+
+ assert(line);
+ assert(n > 0);
+ assert(line[n-1] == '\n');
+
+ /* XXX: is it worth to support timestamps in extended format?
+ * We don't produce them, but who knows... */
+
+ timestamp = startswith(line, "__CURSOR=");
+ if (timestamp)
+ /* ignore __CURSOR */
+ return 1;
+
+ timestamp = startswith(line, "__REALTIME_TIMESTAMP=");
+ if (timestamp) {
+ long long unsigned x;
+ line[n-1] = '\0';
+ r = safe_atollu(timestamp, &x);
+ if (r < 0)
+ log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp);
+ else
+ source->ts.realtime = x;
+ return r < 0 ? r : 1;
+ }
+
+ timestamp = startswith(line, "__MONOTONIC_TIMESTAMP=");
+ if (timestamp) {
+ long long unsigned x;
+ line[n-1] = '\0';
+ r = safe_atollu(timestamp, &x);
+ if (r < 0)
+ log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp);
+ else
+ source->ts.monotonic = x;
+ return r < 0 ? r : 1;
+ }
+
+ timestamp = startswith(line, "__");
+ if (timestamp) {
+ log_notice("Unknown dunder line %s", line);
+ return 1;
+ }
+
+ /* no dunder */
+ return 0;
+}
+
+static int process_data(RemoteSource *source) {
+ int r;
+
+ switch(source->state) {
+ case STATE_LINE: {
+ char *line, *sep;
+ size_t n = 0;
+
+ assert(source->data_size == 0);
+
+ r = get_line(source, &line, &n);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return r;
+ }
+ assert(n > 0);
+ assert(line[n-1] == '\n');
+
+ if (n == 1) {
+ log_trace("Received empty line, event is ready");
+ return 1;
+ }
+
+ r = process_dunder(source, line, n);
+ if (r != 0)
+ return r < 0 ? r : 0;
+
+ /* MESSAGE=xxx\n
+ or
+ COREDUMP\n
+ LLLLLLLL0011223344...\n
+ */
+ sep = memchr(line, '=', n);
+ if (sep) {
+ /* chomp newline */
+ n--;
+
+ r = iovw_put(&source->iovw, line, n);
+ if (r < 0)
+ return r;
+ } else {
+ /* replace \n with = */
+ line[n-1] = '=';
+
+ source->field_len = n;
+ source->state = STATE_DATA_START;
+
+ /* we cannot put the field in iovec until we have all data */
+ }
+
+ log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary");
+
+ return 0; /* continue */
+ }
+
+ case STATE_DATA_START:
+ assert(source->data_size == 0);
+
+ r = get_data_size(source);
+ // log_debug("get_data_size() -> %d", r);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return 0;
+ }
+
+ source->state = source->data_size > 0 ?
+ STATE_DATA : STATE_DATA_FINISH;
+
+ return 0; /* continue */
+
+ case STATE_DATA: {
+ void *data;
+ char *field;
+
+ assert(source->data_size > 0);
+
+ r = get_data_data(source, &data);
+ // log_debug("get_data_data() -> %d", r);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return 0;
+ }
+
+ assert(data);
+
+ field = (char*) data - sizeof(uint64_t) - source->field_len;
+ memmove(field + sizeof(uint64_t), field, source->field_len);
+
+ r = iovw_put(&source->iovw, field + sizeof(uint64_t), source->field_len + source->data_size);
+ if (r < 0)
+ return r;
+
+ source->state = STATE_DATA_FINISH;
+
+ return 0; /* continue */
+ }
+
+ case STATE_DATA_FINISH:
+ r = get_data_newline(source);
+ // log_debug("get_data_newline() -> %d", r);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return 0;
+ }
+
+ source->data_size = 0;
+ source->state = STATE_LINE;
+
+ return 0; /* continue */
+ default:
+ assert_not_reached("wtf?");
+ }
+}
+
+int process_source(RemoteSource *source, bool compress, bool seal) {
+ size_t remain, target;
+ int r;
+
+ assert(source);
+ assert(source->writer);
+
+ r = process_data(source);
+ if (r <= 0)
+ return r;
+
+ /* We have a full event */
+ log_trace("Received full event from source@%p fd:%d (%s)",
+ source, source->fd, source->name);
+
+ if (!source->iovw.count) {
+ log_warning("Entry with no payload, skipping");
+ goto freeing;
+ }
+
+ assert(source->iovw.iovec);
+ assert(source->iovw.count);
+
+ r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal);
+ if (r < 0)
+ log_error_errno(r, "Failed to write entry of %zu bytes: %m",
+ iovw_size(&source->iovw));
+ else
+ r = 1;
+
+ freeing:
+ iovw_free_contents(&source->iovw);
+
+ /* possibly reset buffer position */
+ remain = source->filled - source->offset;
+
+ if (remain == 0) /* no brainer */
+ source->offset = source->scanned = source->filled = 0;
+ else if (source->offset > source->size - source->filled &&
+ source->offset > remain) {
+ memcpy(source->buf, source->buf + source->offset, remain);
+ source->offset = source->scanned = 0;
+ source->filled = remain;
+ }
+
+ target = source->size;
+ while (target > 16 * LINE_CHUNK && source->filled < target / 2)
+ target /= 2;
+ if (target < source->size) {
+ char *tmp;
+
+ tmp = realloc(source->buf, target);
+ if (!tmp)
+ log_warning("Failed to reallocate buffer to (smaller) size %zu",
+ target);
+ else {
+ log_debug("Reallocated buffer from %zu to %zu bytes",
+ source->size, target);
+ source->buf = tmp;
+ source->size = target;
+ }
+ }
+
+ return r;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h
new file mode 100644
index 0000000000..4f47ea89d6
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h
@@ -0,0 +1,69 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 <systemd/sd-event.h>
+
+#include "journal-remote-write.h"
+
+typedef enum {
+ STATE_LINE = 0, /* waiting to read, or reading line */
+ STATE_DATA_START, /* reading binary data header */
+ STATE_DATA, /* reading binary data */
+ STATE_DATA_FINISH, /* expecting newline */
+ STATE_EOF, /* done */
+} source_state;
+
+typedef struct RemoteSource {
+ char *name;
+ int fd;
+ bool passive_fd;
+
+ char *buf;
+ size_t size; /* total size of the buffer */
+ size_t offset; /* offset to the beginning of live data in the buffer */
+ size_t scanned; /* number of bytes since the beginning of data without a newline */
+ size_t filled; /* total number of bytes in the buffer */
+
+ size_t field_len; /* used for binary fields: the field name length */
+ size_t data_size; /* and the size of the binary data chunk being processed */
+
+ struct iovec_wrapper iovw;
+
+ source_state state;
+ dual_timestamp ts;
+
+ Writer *writer;
+
+ sd_event_source *event;
+ sd_event_source *buffer_event;
+} RemoteSource;
+
+RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer);
+
+static inline size_t source_non_empty(RemoteSource *source) {
+ assert(source);
+
+ return source->filled;
+}
+
+void source_free(RemoteSource *source);
+int push_data(RemoteSource *source, const char *data, size_t size);
+int process_source(RemoteSource *source, bool compress, bool seal);
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c
new file mode 100644
index 0000000000..4e05eda103
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c
@@ -0,0 +1,169 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ 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 "basic/alloc-util.h"
+
+#include "journal-remote.h"
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
+ if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
+ return log_oom();
+
+ iovw->iovec[iovw->count++] = (struct iovec) {data, len};
+ return 0;
+}
+
+void iovw_free_contents(struct iovec_wrapper *iovw) {
+ iovw->iovec = mfree(iovw->iovec);
+ iovw->size_bytes = iovw->count = 0;
+}
+
+size_t iovw_size(struct iovec_wrapper *iovw) {
+ size_t n = 0, i;
+
+ for (i = 0; i < iovw->count; i++)
+ n += iovw->iovec[i].iov_len;
+
+ return n;
+}
+
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
+ size_t i;
+
+ for (i = 0; i < iovw->count; i++)
+ iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int do_rotate(JournalFile **f, bool compress, bool seal) {
+ int r = journal_file_rotate(f, compress, seal, NULL);
+ if (r < 0) {
+ if (*f)
+ log_error_errno(r, "Failed to rotate %s: %m", (*f)->path);
+ else
+ log_error_errno(r, "Failed to create rotated journal: %m");
+ }
+
+ return r;
+}
+
+Writer* writer_new(RemoteServer *server) {
+ Writer *w;
+
+ w = new0(Writer, 1);
+ if (!w)
+ return NULL;
+
+ memset(&w->metrics, 0xFF, sizeof(w->metrics));
+
+ w->mmap = mmap_cache_new();
+ if (!w->mmap) {
+ free(w);
+ return NULL;
+ }
+
+ w->n_ref = 1;
+ w->server = server;
+
+ return w;
+}
+
+Writer* writer_free(Writer *w) {
+ if (!w)
+ return NULL;
+
+ if (w->journal) {
+ log_debug("Closing journal file %s.", w->journal->path);
+ journal_file_close(w->journal);
+ }
+
+ if (w->server && w->hashmap_key)
+ hashmap_remove(w->server->writers, w->hashmap_key);
+
+ free(w->hashmap_key);
+
+ if (w->mmap)
+ mmap_cache_unref(w->mmap);
+
+ free(w);
+
+ return NULL;
+}
+
+Writer* writer_unref(Writer *w) {
+ if (w && (-- w->n_ref <= 0))
+ writer_free(w);
+
+ return NULL;
+}
+
+Writer* writer_ref(Writer *w) {
+ if (w)
+ assert_se(++ w->n_ref >= 2);
+
+ return w;
+}
+
+int writer_write(Writer *w,
+ struct iovec_wrapper *iovw,
+ dual_timestamp *ts,
+ bool compress,
+ bool seal) {
+ int r;
+
+ assert(w);
+ assert(iovw);
+ assert(iovw->count > 0);
+
+ if (journal_file_rotate_suggested(w->journal, 0)) {
+ log_info("%s: Journal header limits reached or header out-of-date, rotating",
+ w->journal->path);
+ r = do_rotate(&w->journal, compress, seal);
+ if (r < 0)
+ return r;
+ }
+
+ r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count,
+ &w->seqnum, NULL, NULL);
+ if (r >= 0) {
+ if (w->server)
+ w->server->event_count += 1;
+ return 1;
+ }
+
+ log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path);
+ r = do_rotate(&w->journal, compress, seal);
+ if (r < 0)
+ return r;
+ else
+ log_debug("%s: Successfully rotated journal", w->journal->path);
+
+ log_debug("Retrying write.");
+ r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count,
+ &w->seqnum, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ if (w->server)
+ w->server->event_count += 1;
+ return 1;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h
new file mode 100644
index 0000000000..a61434ca75
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h
@@ -0,0 +1,70 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 "sd-journal/journal-file.h"
+
+typedef struct RemoteServer RemoteServer;
+
+struct iovec_wrapper {
+ struct iovec *iovec;
+ size_t size_bytes;
+ size_t count;
+};
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len);
+void iovw_free_contents(struct iovec_wrapper *iovw);
+size_t iovw_size(struct iovec_wrapper *iovw);
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new);
+
+typedef struct Writer {
+ JournalFile *journal;
+ JournalMetrics metrics;
+
+ MMapCache *mmap;
+ RemoteServer *server;
+ char *hashmap_key;
+
+ uint64_t seqnum;
+
+ int n_ref;
+} Writer;
+
+Writer* writer_new(RemoteServer* server);
+Writer* writer_free(Writer *w);
+
+Writer* writer_ref(Writer *w);
+Writer* writer_unref(Writer *w);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref);
+#define _cleanup_writer_unref_ _cleanup_(writer_unrefp)
+
+int writer_write(Writer *s,
+ struct iovec_wrapper *iovw,
+ dual_timestamp *ts,
+ bool compress,
+ bool seal);
+
+typedef enum JournalWriteSplitMode {
+ JOURNAL_WRITE_SPLIT_NONE,
+ JOURNAL_WRITE_SPLIT_HOST,
+ _JOURNAL_WRITE_SPLIT_MAX,
+ _JOURNAL_WRITE_SPLIT_INVALID = -1
+} JournalWriteSplitMode;
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c
new file mode 100644
index 0000000000..ac1ba30310
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c
@@ -0,0 +1,1602 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ 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 <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+
+#include <systemd/sd-daemon.h>
+
+#include "basic/alloc-util.h"
+#include "basic/def.h"
+#include "basic/escape.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/macro.h"
+#include "basic/parse-util.h"
+#include "basic/signal-util.h"
+#include "basic/socket-util.h"
+#include "basic/stat-util.h"
+#include "basic/stdio-util.h"
+#include "basic/string-table.h"
+#include "basic/string-util.h"
+#include "basic/strv.h"
+#include "journald-native.h"
+#include "sd-journal/journal-file.h"
+#include "shared/conf-parser.h"
+
+#include "journal-remote-write.h"
+#include "journal-remote.h"
+
+#define REMOTE_JOURNAL_PATH "/var/log/journal/remote"
+
+#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem"
+#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem"
+#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
+
+static char* arg_url = NULL;
+static char* arg_getter = NULL;
+static char* arg_listen_raw = NULL;
+static char* arg_listen_http = NULL;
+static char* arg_listen_https = NULL;
+static char** arg_files = NULL;
+static int arg_compress = true;
+static int arg_seal = false;
+static int http_socket = -1, https_socket = -1;
+static char** arg_gnutls_log = NULL;
+
+static JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST;
+static char* arg_output = NULL;
+
+static char *arg_key = NULL;
+static char *arg_cert = NULL;
+static char *arg_trust = NULL;
+static bool arg_trust_all = false;
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int spawn_child(const char* child, char** argv) {
+ int fd[2];
+ pid_t parent_pid, child_pid;
+ int r;
+
+ if (pipe(fd) < 0)
+ return log_error_errno(errno, "Failed to create pager pipe: %m");
+
+ parent_pid = getpid();
+
+ child_pid = fork();
+ if (child_pid < 0) {
+ r = log_error_errno(errno, "Failed to fork: %m");
+ safe_close_pair(fd);
+ return r;
+ }
+
+ /* In the child */
+ if (child_pid == 0) {
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+
+ r = dup2(fd[1], STDOUT_FILENO);
+ if (r < 0) {
+ log_error_errno(errno, "Failed to dup pipe to stdout: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ safe_close_pair(fd);
+
+ /* Make sure the child goes away when the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Check whether our parent died before we were able
+ * to set the death signal */
+ if (getppid() != parent_pid)
+ _exit(EXIT_SUCCESS);
+
+ execvp(child, argv);
+ log_error_errno(errno, "Failed to exec child %s: %m", child);
+ _exit(EXIT_FAILURE);
+ }
+
+ r = close(fd[1]);
+ if (r < 0)
+ log_warning_errno(errno, "Failed to close write end of pipe: %m");
+
+ return fd[0];
+}
+
+static int spawn_curl(const char* url) {
+ char **argv = STRV_MAKE("curl",
+ "-HAccept: application/vnd.fdo.journal",
+ "--silent",
+ "--show-error",
+ url);
+ int r;
+
+ r = spawn_child("curl", argv);
+ if (r < 0)
+ log_error_errno(r, "Failed to spawn curl: %m");
+ return r;
+}
+
+static int spawn_getter(const char *getter) {
+ int r;
+ _cleanup_strv_free_ char **words = NULL;
+
+ assert(getter);
+ r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES);
+ if (r < 0)
+ return log_error_errno(r, "Failed to split getter option: %m");
+
+ r = spawn_child(words[0], words);
+ if (r < 0)
+ log_error_errno(r, "Failed to spawn getter %s: %m", getter);
+
+ return r;
+}
+
+#define filename_escape(s) xescape((s), "/ ")
+
+static int open_output(Writer *w, const char* host) {
+ _cleanup_free_ char *_output = NULL;
+ const char *output;
+ int r;
+
+ switch (arg_split_mode) {
+ case JOURNAL_WRITE_SPLIT_NONE:
+ output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal";
+ break;
+
+ case JOURNAL_WRITE_SPLIT_HOST: {
+ _cleanup_free_ char *name;
+
+ assert(host);
+
+ name = filename_escape(host);
+ if (!name)
+ return log_oom();
+
+ r = asprintf(&_output, "%s/remote-%s.journal",
+ arg_output ?: REMOTE_JOURNAL_PATH,
+ name);
+ if (r < 0)
+ return log_oom();
+
+ output = _output;
+ break;
+ }
+
+ default:
+ assert_not_reached("what?");
+ }
+
+ r = journal_file_open_reliably(output,
+ O_RDWR|O_CREAT, 0640,
+ arg_compress, arg_seal,
+ &w->metrics,
+ w->mmap, NULL,
+ NULL, &w->journal);
+ if (r < 0)
+ log_error_errno(r, "Failed to open output journal %s: %m",
+ output);
+ else
+ log_debug("Opened output file %s", w->journal->path);
+ return r;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int init_writer_hashmap(RemoteServer *s) {
+ static const struct hash_ops *hash_ops[] = {
+ [JOURNAL_WRITE_SPLIT_NONE] = NULL,
+ [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops,
+ };
+
+ assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops));
+
+ s->writers = hashmap_new(hash_ops[arg_split_mode]);
+ if (!s->writers)
+ return log_oom();
+
+ return 0;
+}
+
+static int get_writer(RemoteServer *s, const char *host,
+ Writer **writer) {
+ const void *key;
+ _cleanup_writer_unref_ Writer *w = NULL;
+ int r;
+
+ switch(arg_split_mode) {
+ case JOURNAL_WRITE_SPLIT_NONE:
+ key = "one and only";
+ break;
+
+ case JOURNAL_WRITE_SPLIT_HOST:
+ assert(host);
+ key = host;
+ break;
+
+ default:
+ assert_not_reached("what split mode?");
+ }
+
+ w = hashmap_get(s->writers, key);
+ if (w)
+ writer_ref(w);
+ else {
+ w = writer_new(s);
+ if (!w)
+ return log_oom();
+
+ if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) {
+ w->hashmap_key = strdup(key);
+ if (!w->hashmap_key)
+ return log_oom();
+ }
+
+ r = open_output(w, host);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(s->writers, w->hashmap_key ?: key, w);
+ if (r < 0)
+ return r;
+ }
+
+ *writer = w;
+ w = NULL;
+ return 0;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+/* This should go away as soon as µhttpd allows state to be passed around. */
+static RemoteServer *server;
+
+static int dispatch_raw_source_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static int dispatch_raw_source_until_block(sd_event_source *event,
+ void *userdata);
+static int dispatch_blocking_source_event(sd_event_source *event,
+ void *userdata);
+static int dispatch_raw_connection_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static int dispatch_http_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+
+static int get_source_for_fd(RemoteServer *s,
+ int fd, char *name, RemoteSource **source) {
+ Writer *writer;
+ int r;
+
+ /* This takes ownership of name, but only on success. */
+
+ assert(fd >= 0);
+ assert(source);
+
+ if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
+ return log_oom();
+
+ r = get_writer(s, name, &writer);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get writer for source %s: %m",
+ name);
+
+ if (s->sources[fd] == NULL) {
+ s->sources[fd] = source_new(fd, false, name, writer);
+ if (!s->sources[fd]) {
+ writer_unref(writer);
+ return log_oom();
+ }
+
+ s->active++;
+ }
+
+ *source = s->sources[fd];
+ return 0;
+}
+
+static int remove_source(RemoteServer *s, int fd) {
+ RemoteSource *source;
+
+ assert(s);
+ assert(fd >= 0 && fd < (ssize_t) s->sources_size);
+
+ source = s->sources[fd];
+ if (source) {
+ /* this closes fd too */
+ source_free(source);
+ s->sources[fd] = NULL;
+ s->active--;
+ }
+
+ return 0;
+}
+
+static int add_source(RemoteServer *s, int fd, char* name, bool own_name) {
+
+ RemoteSource *source = NULL;
+ int r;
+
+ /* This takes ownership of name, even on failure, if own_name is true. */
+
+ assert(s);
+ assert(fd >= 0);
+ assert(name);
+
+ if (!own_name) {
+ name = strdup(name);
+ if (!name)
+ return log_oom();
+ }
+
+ r = get_source_for_fd(s, fd, name, &source);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create source for fd:%d (%s): %m",
+ fd, name);
+ free(name);
+ return r;
+ }
+
+ r = sd_event_add_io(s->events, &source->event,
+ fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI,
+ dispatch_raw_source_event, source);
+ if (r == 0) {
+ /* Add additional source for buffer processing. It will be
+ * enabled later. */
+ r = sd_event_add_defer(s->events, &source->buffer_event,
+ dispatch_raw_source_until_block, source);
+ if (r == 0)
+ sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF);
+ } else if (r == -EPERM) {
+ log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name);
+ r = sd_event_add_defer(s->events, &source->event,
+ dispatch_blocking_source_event, source);
+ if (r == 0)
+ sd_event_source_set_enabled(source->event, SD_EVENT_ON);
+ }
+ if (r < 0) {
+ log_error_errno(r, "Failed to register event source for fd:%d: %m",
+ fd);
+ goto error;
+ }
+
+ r = sd_event_source_set_description(source->event, name);
+ if (r < 0) {
+ log_error_errno(r, "Failed to set source name for fd:%d: %m", fd);
+ goto error;
+ }
+
+ return 1; /* work to do */
+
+ error:
+ remove_source(s, fd);
+ return r;
+}
+
+static int add_raw_socket(RemoteServer *s, int fd) {
+ int r;
+ _cleanup_close_ int fd_ = fd;
+ char name[sizeof("raw-socket-")-1 + DECIMAL_STR_MAX(int) + 1];
+
+ assert(fd >= 0);
+
+ r = sd_event_add_io(s->events, &s->listen_event,
+ fd, EPOLLIN,
+ dispatch_raw_connection_event, s);
+ if (r < 0)
+ return r;
+
+ xsprintf(name, "raw-socket-%d", fd);
+
+ r = sd_event_source_set_description(s->listen_event, name);
+ if (r < 0)
+ return r;
+
+ fd_ = -1;
+ s->active++;
+ return 0;
+}
+
+static int setup_raw_socket(RemoteServer *s, const char *address) {
+ int fd;
+
+ fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ return add_raw_socket(s, fd);
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int request_meta(void **connection_cls, int fd, char *hostname) {
+ RemoteSource *source;
+ Writer *writer;
+ int r;
+
+ assert(connection_cls);
+ if (*connection_cls)
+ return 0;
+
+ r = get_writer(server, hostname, &writer);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get writer for source %s: %m",
+ hostname);
+
+ source = source_new(fd, true, hostname, writer);
+ if (!source) {
+ writer_unref(writer);
+ return log_oom();
+ }
+
+ log_debug("Added RemoteSource as connection metadata %p", source);
+
+ *connection_cls = source;
+ return 0;
+}
+
+static void request_meta_free(void *cls,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ enum MHD_RequestTerminationCode toe) {
+ RemoteSource *s;
+
+ assert(connection_cls);
+ s = *connection_cls;
+
+ if (s) {
+ log_debug("Cleaning up connection metadata %p", s);
+ source_free(s);
+ *connection_cls = NULL;
+ }
+}
+
+static int process_http_upload(
+ struct MHD_Connection *connection,
+ const char *upload_data,
+ size_t *upload_data_size,
+ RemoteSource *source) {
+
+ bool finished = false;
+ size_t remaining;
+ int r;
+
+ assert(source);
+
+ log_trace("%s: connection %p, %zu bytes",
+ __func__, connection, *upload_data_size);
+
+ if (*upload_data_size) {
+ log_trace("Received %zu bytes", *upload_data_size);
+
+ r = push_data(source, upload_data, *upload_data_size);
+ if (r < 0)
+ return mhd_respond_oom(connection);
+
+ *upload_data_size = 0;
+ } else
+ finished = true;
+
+ for (;;) {
+ r = process_source(source, arg_compress, arg_seal);
+ if (r == -EAGAIN)
+ break;
+ else if (r < 0) {
+ log_warning("Failed to process data for connection %p", connection);
+ if (r == -E2BIG)
+ return mhd_respondf(connection,
+ MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+ "Entry is too large, maximum is %u bytes.\n",
+ DATA_SIZE_MAX);
+ else
+ return mhd_respondf(connection,
+ MHD_HTTP_UNPROCESSABLE_ENTITY,
+ "Processing failed: %s.", strerror(-r));
+ }
+ }
+
+ if (!finished)
+ return MHD_YES;
+
+ /* The upload is finished */
+
+ remaining = source_non_empty(source);
+ if (remaining > 0) {
+ log_warning("Premature EOFbyte. %zu bytes lost.", remaining);
+ return mhd_respondf(connection, MHD_HTTP_EXPECTATION_FAILED,
+ "Premature EOF. %zu bytes of trailing data not processed.",
+ remaining);
+ }
+
+ return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
+};
+
+static int request_handler(
+ void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **connection_cls) {
+
+ const char *header;
+ int r, code, fd;
+ _cleanup_free_ char *hostname = NULL;
+
+ assert(connection);
+ assert(connection_cls);
+ assert(url);
+ assert(method);
+
+ log_trace("Handling a connection %s %s %s", method, url, version);
+
+ if (*connection_cls)
+ return process_http_upload(connection,
+ upload_data, upload_data_size,
+ *connection_cls);
+
+ if (!streq(method, "POST"))
+ return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE,
+ "Unsupported method.\n");
+
+ if (!streq(url, "/upload"))
+ return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
+ "Not found.\n");
+
+ header = MHD_lookup_connection_value(connection,
+ MHD_HEADER_KIND, "Content-Type");
+ if (!header || !streq(header, "application/vnd.fdo.journal"))
+ return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
+ "Content-Type: application/vnd.fdo.journal"
+ " is required.\n");
+
+ {
+ const union MHD_ConnectionInfo *ci;
+
+ ci = MHD_get_connection_info(connection,
+ MHD_CONNECTION_INFO_CONNECTION_FD);
+ if (!ci) {
+ log_error("MHD_get_connection_info failed: cannot get remote fd");
+ return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "Cannot check remote address");
+ }
+
+ fd = ci->connect_fd;
+ assert(fd >= 0);
+ }
+
+ if (server->check_trust) {
+ r = check_permissions(connection, &code, &hostname);
+ if (r < 0)
+ return code;
+ } else {
+ r = getpeername_pretty(fd, false, &hostname);
+ if (r < 0)
+ return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "Cannot check remote hostname");
+ }
+
+ assert(hostname);
+
+ r = request_meta(connection_cls, fd, hostname);
+ if (r == -ENOMEM)
+ return respond_oom(connection);
+ else if (r < 0)
+ return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
+ strerror(-r));
+
+ hostname = NULL;
+ return MHD_YES;
+}
+
+static int setup_microhttpd_server(RemoteServer *s,
+ int fd,
+ const char *key,
+ const char *cert,
+ const char *trust) {
+ struct MHD_OptionItem opts[] = {
+ { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free},
+ { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger},
+ { MHD_OPTION_LISTEN_SOCKET, fd},
+ { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024},
+ { MHD_OPTION_END},
+ { MHD_OPTION_END},
+ { MHD_OPTION_END},
+ { MHD_OPTION_END}};
+ int opts_pos = 4;
+ int flags =
+ MHD_USE_DEBUG |
+ MHD_USE_DUAL_STACK |
+ MHD_USE_EPOLL_LINUX_ONLY |
+ MHD_USE_PEDANTIC_CHECKS |
+ MHD_USE_PIPE_FOR_SHUTDOWN;
+
+ const union MHD_DaemonInfo *info;
+ int r, epoll_fd;
+ MHDDaemonWrapper *d;
+
+ assert(fd >= 0);
+
+ r = fd_nonblock(fd, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd);
+
+ if (key) {
+ assert(cert);
+
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key};
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert};
+
+ flags |= MHD_USE_SSL;
+
+ if (trust)
+ opts[opts_pos++] = (struct MHD_OptionItem)
+ {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust};
+ }
+
+ d = new(MHDDaemonWrapper, 1);
+ if (!d)
+ return log_oom();
+
+ d->fd = (uint64_t) fd;
+
+ d->daemon = MHD_start_daemon(flags, 0,
+ NULL, NULL,
+ request_handler, NULL,
+ MHD_OPTION_ARRAY, opts,
+ MHD_OPTION_END);
+ if (!d->daemon) {
+ log_error("Failed to start µhttp daemon");
+ r = -EINVAL;
+ goto error;
+ }
+
+ log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
+ key ? "HTTPS" : "HTTP", fd, d);
+
+
+ info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
+ if (!info) {
+ log_error("µhttp returned NULL daemon info");
+ r = -EOPNOTSUPP;
+ goto error;
+ }
+
+ epoll_fd = info->listen_fd;
+ if (epoll_fd < 0) {
+ log_error("µhttp epoll fd is invalid");
+ r = -EUCLEAN;
+ goto error;
+ }
+
+ r = sd_event_add_io(s->events, &d->event,
+ epoll_fd, EPOLLIN,
+ dispatch_http_event, d);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add event callback: %m");
+ goto error;
+ }
+
+ r = sd_event_source_set_description(d->event, "epoll-fd");
+ if (r < 0) {
+ log_error_errno(r, "Failed to set source name: %m");
+ goto error;
+ }
+
+ r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops);
+ if (r < 0) {
+ log_oom();
+ goto error;
+ }
+
+ r = hashmap_put(s->daemons, &d->fd, d);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add daemon to hashmap: %m");
+ goto error;
+ }
+
+ s->active++;
+ return 0;
+
+error:
+ MHD_stop_daemon(d->daemon);
+ free(d->daemon);
+ free(d);
+ return r;
+}
+
+static int setup_microhttpd_socket(RemoteServer *s,
+ const char *address,
+ const char *key,
+ const char *cert,
+ const char *trust) {
+ int fd;
+
+ fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC);
+ if (fd < 0)
+ return fd;
+
+ return setup_microhttpd_server(s, fd, key, cert, trust);
+}
+
+static int dispatch_http_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ MHDDaemonWrapper *d = userdata;
+ int r;
+
+ assert(d);
+
+ r = MHD_run(d->daemon);
+ if (r == MHD_NO) {
+ log_error("MHD_run failed!");
+ // XXX: unregister daemon
+ return -EINVAL;
+ }
+
+ return 1; /* work to do */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int setup_signals(RemoteServer *s) {
+ int r;
+
+ assert(s);
+
+ assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0);
+
+ r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int negative_fd(const char *spec) {
+ /* Return a non-positive number as its inverse, -EINVAL otherwise. */
+
+ int fd, r;
+
+ r = safe_atoi(spec, &fd);
+ if (r < 0)
+ return r;
+
+ if (fd > 0)
+ return -EINVAL;
+ else
+ return -fd;
+}
+
+static int remoteserver_init(RemoteServer *s,
+ const char* key,
+ const char* cert,
+ const char* trust) {
+ int r, n, fd;
+ char **file;
+
+ assert(s);
+
+ if ((arg_listen_raw || arg_listen_http) && trust) {
+ log_error("Option --trust makes all non-HTTPS connections untrusted.");
+ return -EINVAL;
+ }
+
+ r = sd_event_default(&s->events);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event loop: %m");
+
+ setup_signals(s);
+
+ assert(server == NULL);
+ server = s;
+
+ r = init_writer_hashmap(s);
+ if (r < 0)
+ return r;
+
+ n = sd_listen_fds(true);
+ if (n < 0)
+ return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
+ else
+ log_debug("Received %d descriptors", n);
+
+ if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) {
+ log_error("Received fewer sockets than expected");
+ return -EBADFD;
+ }
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
+ if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
+ log_debug("Received a listening socket (fd:%d)", fd);
+
+ if (fd == http_socket)
+ r = setup_microhttpd_server(s, fd, NULL, NULL, NULL);
+ else if (fd == https_socket)
+ r = setup_microhttpd_server(s, fd, key, cert, trust);
+ else
+ r = add_raw_socket(s, fd);
+ } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
+ char *hostname;
+
+ r = getpeername_pretty(fd, false, &hostname);
+ if (r < 0)
+ return log_error_errno(r, "Failed to retrieve remote name: %m");
+
+ log_debug("Received a connection socket (fd:%d) from %s", fd, hostname);
+
+ r = add_source(s, fd, hostname, true);
+ } else {
+ log_error("Unknown socket passed on fd:%d", fd);
+
+ return -EINVAL;
+ }
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to register socket (fd:%d): %m",
+ fd);
+ }
+
+ if (arg_getter) {
+ log_info("Spawning getter %s...", arg_getter);
+ fd = spawn_getter(arg_getter);
+ if (fd < 0)
+ return fd;
+
+ r = add_source(s, fd, (char*) arg_output, false);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_url) {
+ const char *url;
+ char *hostname, *p;
+
+ if (!strstr(arg_url, "/entries")) {
+ if (endswith(arg_url, "/"))
+ url = strjoina(arg_url, "entries");
+ else
+ url = strjoina(arg_url, "/entries");
+ }
+ else
+ url = strdupa(arg_url);
+
+ log_info("Spawning curl %s...", url);
+ fd = spawn_curl(url);
+ if (fd < 0)
+ return fd;
+
+ hostname =
+ startswith(arg_url, "https://") ?:
+ startswith(arg_url, "http://") ?:
+ arg_url;
+
+ hostname = strdupa(hostname);
+ if ((p = strchr(hostname, '/')))
+ *p = '\0';
+ if ((p = strchr(hostname, ':')))
+ *p = '\0';
+
+ r = add_source(s, fd, hostname, false);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_listen_raw) {
+ log_debug("Listening on a socket...");
+ r = setup_raw_socket(s, arg_listen_raw);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_listen_http) {
+ r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ if (arg_listen_https) {
+ r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust);
+ if (r < 0)
+ return r;
+ }
+
+ STRV_FOREACH(file, arg_files) {
+ const char *output_name;
+
+ if (streq(*file, "-")) {
+ log_debug("Using standard input as source.");
+
+ fd = STDIN_FILENO;
+ output_name = "stdin";
+ } else {
+ log_debug("Reading file %s...", *file);
+
+ fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open %s: %m", *file);
+ output_name = *file;
+ }
+
+ r = add_source(s, fd, (char*) output_name, false);
+ if (r < 0)
+ return r;
+ }
+
+ if (s->active == 0) {
+ log_error("Zero sources specified");
+ return -EINVAL;
+ }
+
+ if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) {
+ /* In this case we know what the writer will be
+ called, so we can create it and verify that we can
+ create output as expected. */
+ r = get_writer(s, NULL, &s->_single_writer);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void server_destroy(RemoteServer *s) {
+ size_t i;
+ MHDDaemonWrapper *d;
+
+ while ((d = hashmap_steal_first(s->daemons))) {
+ MHD_stop_daemon(d->daemon);
+ sd_event_source_unref(d->event);
+ free(d);
+ }
+
+ hashmap_free(s->daemons);
+
+ assert(s->sources_size == 0 || s->sources);
+ for (i = 0; i < s->sources_size; i++)
+ remove_source(s, i);
+ free(s->sources);
+
+ writer_unref(s->_single_writer);
+ hashmap_free(s->writers);
+
+ sd_event_source_unref(s->sigterm_event);
+ sd_event_source_unref(s->sigint_event);
+ sd_event_source_unref(s->listen_event);
+ sd_event_unref(s->events);
+
+ /* fds that we're listening on remain open... */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int handle_raw_source(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ RemoteServer *s) {
+
+ RemoteSource *source;
+ int r;
+
+ /* Returns 1 if there might be more data pending,
+ * 0 if data is currently exhausted, negative on error.
+ */
+
+ assert(fd >= 0 && fd < (ssize_t) s->sources_size);
+ source = s->sources[fd];
+ assert(source->fd == fd);
+
+ r = process_source(source, arg_compress, arg_seal);
+ if (source->state == STATE_EOF) {
+ size_t remaining;
+
+ log_debug("EOF reached with source fd:%d (%s)",
+ source->fd, source->name);
+
+ remaining = source_non_empty(source);
+ if (remaining > 0)
+ log_notice("Premature EOF. %zu bytes lost.", remaining);
+ remove_source(s, source->fd);
+ log_debug("%zu active sources remaining", s->active);
+ return 0;
+ } else if (r == -E2BIG) {
+ log_notice_errno(E2BIG, "Entry too big, skipped");
+ return 1;
+ } else if (r == -EAGAIN) {
+ return 0;
+ } else if (r < 0) {
+ log_debug_errno(r, "Closing connection: %m");
+ remove_source(server, fd);
+ return 0;
+ } else
+ return 1;
+}
+
+static int dispatch_raw_source_until_block(sd_event_source *event,
+ void *userdata) {
+ RemoteSource *source = userdata;
+ int r;
+
+ /* Make sure event stays around even if source is destroyed */
+ sd_event_source_ref(event);
+
+ r = handle_raw_source(event, source->fd, EPOLLIN, server);
+ if (r != 1)
+ /* No more data for now */
+ sd_event_source_set_enabled(event, SD_EVENT_OFF);
+
+ sd_event_source_unref(event);
+
+ return r;
+}
+
+static int dispatch_raw_source_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ RemoteSource *source = userdata;
+ int r;
+
+ assert(source->event);
+ assert(source->buffer_event);
+
+ r = handle_raw_source(event, fd, EPOLLIN, server);
+ if (r == 1)
+ /* Might have more data. We need to rerun the handler
+ * until we are sure the buffer is exhausted. */
+ sd_event_source_set_enabled(source->buffer_event, SD_EVENT_ON);
+
+ return r;
+}
+
+static int dispatch_blocking_source_event(sd_event_source *event,
+ void *userdata) {
+ RemoteSource *source = userdata;
+
+ return handle_raw_source(event, source->fd, EPOLLIN, server);
+}
+
+static int accept_connection(const char* type, int fd,
+ SocketAddress *addr, char **hostname) {
+ int fd2, r;
+
+ log_debug("Accepting new %s connection on fd:%d", type, fd);
+ fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (fd2 < 0)
+ return log_error_errno(errno, "accept() on fd:%d failed: %m", fd);
+
+ switch(socket_address_family(addr)) {
+ case AF_INET:
+ case AF_INET6: {
+ _cleanup_free_ char *a = NULL;
+ char *b;
+
+ r = socket_address_print(addr, &a);
+ if (r < 0) {
+ log_error_errno(r, "socket_address_print(): %m");
+ close(fd2);
+ return r;
+ }
+
+ r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b);
+ if (r < 0) {
+ log_error_errno(r, "Resolving hostname failed: %m");
+ close(fd2);
+ return r;
+ }
+
+ log_debug("Accepted %s %s connection from %s",
+ type,
+ socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
+ a);
+
+ *hostname = b;
+
+ return fd2;
+ };
+ default:
+ log_error("Rejected %s connection with unsupported family %d",
+ type, socket_address_family(addr));
+ close(fd2);
+
+ return -EINVAL;
+ }
+}
+
+static int dispatch_raw_connection_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ RemoteServer *s = userdata;
+ int fd2;
+ SocketAddress addr = {
+ .size = sizeof(union sockaddr_union),
+ .type = SOCK_STREAM,
+ };
+ char *hostname = NULL;
+
+ fd2 = accept_connection("raw", fd, &addr, &hostname);
+ if (fd2 < 0)
+ return fd2;
+
+ return add_source(s, fd2, hostname, true);
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
+ [JOURNAL_WRITE_SPLIT_NONE] = "none",
+ [JOURNAL_WRITE_SPLIT_HOST] = "host",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode);
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode,
+ journal_write_split_mode,
+ JournalWriteSplitMode,
+ "Failed to parse split mode setting");
+
+static int parse_config(void) {
+ const ConfigTableItem items[] = {
+ { "Remote", "Seal", config_parse_bool, 0, &arg_seal },
+ { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode },
+ { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key },
+ { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
+ { "Remote", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
+ {}};
+
+ return config_parse_many(PKGSYSCONFDIR "/journal-remote.conf",
+ CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"),
+ "Remote\0", config_item_table_lookup, items,
+ false, NULL);
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] {FILE|-}...\n\n"
+ "Write external journal events to journal file(s).\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --url=URL Read events from systemd-journal-gatewayd at URL\n"
+ " --getter=COMMAND Read events from the output of COMMAND\n"
+ " --listen-raw=ADDR Listen for connections at ADDR\n"
+ " --listen-http=ADDR Listen for HTTP connections at ADDR\n"
+ " --listen-https=ADDR Listen for HTTPS connections at ADDR\n"
+ " -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
+ " --compress[=BOOL] XZ-compress the output journal (default: yes)\n"
+ " --seal[=BOOL] Use event sealing (default: no)\n"
+ " --key=FILENAME SSL key in PEM format (default:\n"
+ " \"" PRIV_KEY_FILE "\")\n"
+ " --cert=FILENAME SSL certificate in PEM format (default:\n"
+ " \"" CERT_FILE "\")\n"
+ " --trust=FILENAME|all SSL CA certificate or disable checking (default:\n"
+ " \"" TRUST_FILE "\")\n"
+ " --gnutls-log=CATEGORY...\n"
+ " Specify a list of gnutls logging categories\n"
+ " --split-mode=none|host How many output files to create\n"
+ "\n"
+ "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_URL,
+ ARG_LISTEN_RAW,
+ ARG_LISTEN_HTTP,
+ ARG_LISTEN_HTTPS,
+ ARG_GETTER,
+ ARG_SPLIT_MODE,
+ ARG_COMPRESS,
+ ARG_SEAL,
+ ARG_KEY,
+ ARG_CERT,
+ ARG_TRUST,
+ ARG_GNUTLS_LOG,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "url", required_argument, NULL, ARG_URL },
+ { "getter", required_argument, NULL, ARG_GETTER },
+ { "listen-raw", required_argument, NULL, ARG_LISTEN_RAW },
+ { "listen-http", required_argument, NULL, ARG_LISTEN_HTTP },
+ { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
+ { "output", required_argument, NULL, 'o' },
+ { "split-mode", required_argument, NULL, ARG_SPLIT_MODE },
+ { "compress", optional_argument, NULL, ARG_COMPRESS },
+ { "seal", optional_argument, NULL, ARG_SEAL },
+ { "key", required_argument, NULL, ARG_KEY },
+ { "cert", required_argument, NULL, ARG_CERT },
+ { "trust", required_argument, NULL, ARG_TRUST },
+ { "gnutls-log", required_argument, NULL, ARG_GNUTLS_LOG },
+ {}
+ };
+
+ int c, r;
+ bool type_a, type_b;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
+ switch(c) {
+ case 'h':
+ help();
+ return 0 /* done */;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_URL:
+ if (arg_url) {
+ log_error("cannot currently set more than one --url");
+ return -EINVAL;
+ }
+
+ arg_url = optarg;
+ break;
+
+ case ARG_GETTER:
+ if (arg_getter) {
+ log_error("cannot currently use --getter more than once");
+ return -EINVAL;
+ }
+
+ arg_getter = optarg;
+ break;
+
+ case ARG_LISTEN_RAW:
+ if (arg_listen_raw) {
+ log_error("cannot currently use --listen-raw more than once");
+ return -EINVAL;
+ }
+
+ arg_listen_raw = optarg;
+ break;
+
+ case ARG_LISTEN_HTTP:
+ if (arg_listen_http || http_socket >= 0) {
+ log_error("cannot currently use --listen-http more than once");
+ return -EINVAL;
+ }
+
+ r = negative_fd(optarg);
+ if (r >= 0)
+ http_socket = r;
+ else
+ arg_listen_http = optarg;
+ break;
+
+ case ARG_LISTEN_HTTPS:
+ if (arg_listen_https || https_socket >= 0) {
+ log_error("cannot currently use --listen-https more than once");
+ return -EINVAL;
+ }
+
+ r = negative_fd(optarg);
+ if (r >= 0)
+ https_socket = r;
+ else
+ arg_listen_https = optarg;
+
+ break;
+
+ case ARG_KEY:
+ if (arg_key) {
+ log_error("Key file specified twice");
+ return -EINVAL;
+ }
+
+ arg_key = strdup(optarg);
+ if (!arg_key)
+ return log_oom();
+
+ break;
+
+ case ARG_CERT:
+ if (arg_cert) {
+ log_error("Certificate file specified twice");
+ return -EINVAL;
+ }
+
+ arg_cert = strdup(optarg);
+ if (!arg_cert)
+ return log_oom();
+
+ break;
+
+ case ARG_TRUST:
+ if (arg_trust || arg_trust_all) {
+ log_error("Confusing trusted CA configuration");
+ return -EINVAL;
+ }
+
+ if (streq(optarg, "all"))
+ arg_trust_all = true;
+ else {
+#ifdef HAVE_GNUTLS
+ arg_trust = strdup(optarg);
+ if (!arg_trust)
+ return log_oom();
+#else
+ log_error("Option --trust is not available.");
+ return -EINVAL;
+#endif
+ }
+
+ break;
+
+ case 'o':
+ if (arg_output) {
+ log_error("cannot use --output/-o more than once");
+ return -EINVAL;
+ }
+
+ arg_output = optarg;
+ break;
+
+ case ARG_SPLIT_MODE:
+ arg_split_mode = journal_write_split_mode_from_string(optarg);
+ if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) {
+ log_error("Invalid split mode: %s", optarg);
+ return -EINVAL;
+ }
+ break;
+
+ case ARG_COMPRESS:
+ if (optarg) {
+ r = parse_boolean(optarg);
+ if (r < 0) {
+ log_error("Failed to parse --compress= parameter.");
+ return -EINVAL;
+ }
+
+ arg_compress = !!r;
+ } else
+ arg_compress = true;
+
+ break;
+
+ case ARG_SEAL:
+ if (optarg) {
+ r = parse_boolean(optarg);
+ if (r < 0) {
+ log_error("Failed to parse --seal= parameter.");
+ return -EINVAL;
+ }
+
+ arg_seal = !!r;
+ } else
+ arg_seal = true;
+
+ break;
+
+ case ARG_GNUTLS_LOG: {
+#ifdef HAVE_GNUTLS
+ const char* p = optarg;
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, ",", 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m");
+
+ if (r == 0)
+ break;
+
+ if (strv_push(&arg_gnutls_log, word) < 0)
+ return log_oom();
+
+ word = NULL;
+ }
+ break;
+#else
+ log_error("Option --gnutls-log is not available.");
+ return -EINVAL;
+#endif
+ }
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unknown option code.");
+ }
+
+ if (optind < argc)
+ arg_files = argv + optind;
+
+ type_a = arg_getter || !strv_isempty(arg_files);
+ type_b = arg_url
+ || arg_listen_raw
+ || arg_listen_http || arg_listen_https
+ || sd_listen_fds(false) > 0;
+ if (type_a && type_b) {
+ log_error("Cannot use file input or --getter with "
+ "--arg-listen-... or socket activation.");
+ return -EINVAL;
+ }
+ if (type_a) {
+ if (!arg_output) {
+ log_error("Option --output must be specified with file input or --getter.");
+ return -EINVAL;
+ }
+
+ arg_split_mode = JOURNAL_WRITE_SPLIT_NONE;
+ }
+
+ if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE
+ && arg_output && is_dir(arg_output, true) > 0) {
+ log_error("For SplitMode=none, output must be a file.");
+ return -EINVAL;
+ }
+
+ if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST
+ && arg_output && is_dir(arg_output, true) <= 0) {
+ log_error("For SplitMode=host, output must be a directory.");
+ return -EINVAL;
+ }
+
+ log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s",
+ journal_write_split_mode_to_string(arg_split_mode),
+ strna(arg_key),
+ strna(arg_cert),
+ strna(arg_trust));
+
+ return 1 /* work to do */;
+}
+
+static int load_certificates(char **key, char **cert, char **trust) {
+ int r;
+
+ r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read key from file '%s': %m",
+ arg_key ?: PRIV_KEY_FILE);
+
+ r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read certificate from file '%s': %m",
+ arg_cert ?: CERT_FILE);
+
+ if (arg_trust_all)
+ log_info("Certificate checking disabled.");
+ else {
+ r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
+ arg_trust ?: TRUST_FILE);
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv) {
+ RemoteServer s = {};
+ int r;
+ _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL;
+
+ log_show_color(true);
+ log_parse_environment();
+
+ r = parse_config();
+ if (r < 0)
+ return EXIT_FAILURE;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+
+ if (arg_listen_http || arg_listen_https) {
+ r = setup_gnutls_logger(arg_gnutls_log);
+ if (r < 0)
+ return EXIT_FAILURE;
+ }
+
+ if (arg_listen_https || https_socket >= 0)
+ if (load_certificates(&key, &cert, &trust) < 0)
+ return EXIT_FAILURE;
+
+ if (remoteserver_init(&s, key, cert, trust) < 0)
+ return EXIT_FAILURE;
+
+ r = sd_event_set_watchdog(s.events, true);
+ if (r < 0)
+ log_error_errno(r, "Failed to enable watchdog: %m");
+ else
+ log_debug("Watchdog is %s.", r > 0 ? "enabled" : "disabled");
+
+ log_debug("%s running as pid "PID_FMT,
+ program_invocation_short_name, getpid());
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing requests...");
+
+ while (s.active) {
+ r = sd_event_get_state(s.events);
+ if (r < 0)
+ break;
+ if (r == SD_EVENT_FINISHED)
+ break;
+
+ r = sd_event_run(s.events, -1);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ break;
+ }
+ }
+
+ sd_notifyf(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count);
+ log_info("Finishing after writing %" PRIu64 " entries", s.event_count);
+
+ server_destroy(&s);
+
+ free(arg_key);
+ free(arg_cert);
+ free(arg_trust);
+
+ return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in
new file mode 100644
index 0000000000..7122d63362
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in
@@ -0,0 +1,6 @@
+[Remote]
+# Seal=false
+# SplitMode=host
+# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem
+# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem
+# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml
new file mode 100644
index 0000000000..2d345963d9
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml
@@ -0,0 +1,121 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!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 Chris Morgan
+
+ 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="journal-remote.conf" conditional='HAVE_MICROHTTPD'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>journal-remote.conf</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Chris</firstname>
+ <surname>Morgan</surname>
+ <email>chmorgan@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>journal-remote.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>journal-remote.conf</refname>
+ <refname>journal-remote.conf.d</refname>
+ <refpurpose>Journal remote service configuration files</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/etc/systemd/journal-remote.conf</filename></para>
+ <para><filename>/etc/systemd/journald.conf.d/*.conf</filename></para>
+ <para><filename>/run/systemd/journald.conf.d/*.conf</filename></para>
+ <para><filename>/usr/lib/systemd/journald.conf.d/*.conf</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>These files configure various parameters of the systemd-remote-journal
+ application,
+ <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>All options are configured in the
+ <literal>[Remote]</literal> section:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><varname>Seal=</varname></term>
+
+ <listitem><para>Periodically sign the data in the journal using Forward Secure Sealing.
+ </para></listitem>
+ </varlistentry>
+
+
+ <varlistentry>
+ <term><varname>SplitMode=</varname></term>
+
+ <listitem><para>One of <literal>host</literal> or <literal>none</literal>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>ServerKeyFile=</varname></term>
+
+ <listitem><para>SSL key in PEM format.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>ServerCertificateFile=</varname></term>
+
+ <listitem><para>SSL CA certificate in PEM format.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>TrustedCertificateFile=</varname></term>
+
+ <listitem><para>SSL CA certificate.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h
new file mode 100644
index 0000000000..6abfc1019d
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h
@@ -0,0 +1,53 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 <systemd/sd-event.h>
+
+#include "basic/hashmap.h"
+#include "microhttpd-util.h"
+
+#include "journal-remote-parse.h"
+#include "journal-remote-write.h"
+
+typedef struct MHDDaemonWrapper MHDDaemonWrapper;
+
+struct MHDDaemonWrapper {
+ uint64_t fd;
+ struct MHD_Daemon *daemon;
+
+ sd_event_source *event;
+};
+
+struct RemoteServer {
+ RemoteSource **sources;
+ size_t sources_size;
+ size_t active;
+
+ sd_event *events;
+ sd_event_source *sigterm_event, *sigint_event, *listen_event;
+
+ Hashmap *writers;
+ Writer *_single_writer;
+ uint64_t event_count;
+
+ bool check_trust;
+ Hashmap *daemons;
+};
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in
new file mode 100644
index 0000000000..fdf3da4b64
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in
@@ -0,0 +1,25 @@
+# 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 Remote Sink Service
+Documentation=man:systemd-journal-remote(8) man:journal-remote.conf(5)
+Requires=systemd-journal-remote.socket
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journal-remote \
+ --listen-https=-3 \
+ --output=/var/log/journal/remote/
+User=systemd-journal-remote
+Group=systemd-journal-remote
+PrivateTmp=yes
+PrivateDevices=yes
+PrivateNetwork=yes
+WatchdogSec=3min
+
+[Install]
+Also=systemd-journal-remote.socket
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket
new file mode 100644
index 0000000000..076dcae8a3
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket
@@ -0,0 +1,15 @@
+# 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 Remote Sink Socket
+
+[Socket]
+ListenStream=19532
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers
new file mode 100644
index 0000000000..ca20c24896
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers
@@ -0,0 +1,8 @@
+# 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.
+
+u systemd-journal-remote - "systemd Journal Remote"
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml
new file mode 100644
index 0000000000..3899f175d4
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml
@@ -0,0 +1,325 @@
+<?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 2012 Zbigniew Jędrzejewski-Szmek
+
+ 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-remote" conditional='HAVE_MICROHTTPD'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-remote</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Zbigniew</firstname>
+ <surname>Jędrzejewski-Szmek</surname>
+ <email>zbyszek@in.waw.pl</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-remote</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-remote</refname>
+ <refpurpose>Receive journal messages over the network</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-remote</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">-o/--output=<replaceable>DIR</replaceable>|<replaceable>FILE</replaceable></arg>
+ <arg choice="opt" rep="repeat">SOURCES</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <filename>systemd-journal-remote</filename> is a command to
+ receive serialized journal events and store them to the journal.
+ Input streams are in the
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/export">
+ Journal Export Format
+ </ulink>,
+ i.e. like the output from
+ <command>journalctl --output=export</command>. For transport over
+ the network, this serialized stream is usually carried over an
+ HTTPS connection.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Sources</title>
+
+ <para>
+ Sources can be either "active"
+ (<command>systemd-journal-remote</command> requests and pulls
+ the data), or "passive"
+ (<command>systemd-journal-remote</command> waits for a
+ connection and then receives events pushed by the other side).
+ </para>
+
+ <para>
+ <command>systemd-journal-remote</command> can read more than one
+ event stream at a time. They will be interleaved in the output
+ file. In case of "active" connections, each "source" is one
+ stream, and in case of "passive" connections, each connection can
+ result in a separate stream. Sockets can be configured in
+ "accept" mode (i.e. only one connection), or "listen" mode (i.e.
+ multiple connections, each resulting in a stream).
+ </para>
+
+ <para>
+ When there are no more connections, and no more can be created
+ (there are no listening sockets), then
+ <command>systemd-journal-remote</command> will exit.
+ </para>
+
+ <para>Active sources can be specified in the following
+ ways:</para>
+
+ <variablelist>
+ <varlistentry>
+ <listitem><para>When <option>-</option> is given as a
+ positional argument, events will be read from standard input.
+ Other positional arguments will be treated as filenames
+ to open and read from.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--url=<replaceable>ADDRESS</replaceable></option></term>
+
+ <listitem><para>With the
+ <option>--url=<replaceable>ADDRESS</replaceable></option> option,
+ events will be retrieved using HTTP from
+ <replaceable>ADDRESS</replaceable>. This URL should refer to the
+ root of a remote
+ <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ instance (e.g. <ulink>http://some.host:19531/</ulink> or
+ <ulink>https://some.host:19531/</ulink>).</para></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>Passive sources can be specified in the following
+ ways:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--listen-raw=<replaceable>ADDRESS</replaceable></option></term>
+
+ <listitem><para><replaceable>ADDRESS</replaceable> must be an
+ address suitable for <option>ListenStream=</option> (cf.
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+ <command>systemd-journal-remote</command> will listen on this
+ socket for connections. Each connection is expected to be a
+ stream of journal events.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--listen-http=<replaceable>ADDRESS</replaceable></option></term>
+ <term><option>--listen-https=<replaceable>ADDRESS</replaceable></option></term>
+
+ <listitem><para><replaceable>ADDRESS</replaceable> must be
+ either a negative integer, in which case it will be
+ interpreted as the (negated) file descriptor number, or an
+ address suitable for <option>ListenStream=</option> (c.f.
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+ In the first case, matching file descriptor must be inherited
+ through
+ <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>.
+ In the second case, an HTTP or HTTPS server will be spawned on
+ this port, respectively for <option>--listen-http</option> and
+ <option>--listen-https</option>. Currently, only POST requests
+ to <filename>/upload</filename> with <literal>Content-Type:
+ application/vnd.fdo.journal</literal> are supported.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>$LISTEN_FDS</varname></term>
+
+ <listitem><para><command>systemd-journal-remote</command>
+ supports the
+ <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>
+ protocol. Open sockets inherited through socket activation
+ behave like those opened with <option>--listen-raw=</option>
+ described above, unless they are specified as an argument in
+ <option>--listen-http=-<replaceable>n</replaceable></option>
+ or
+ <option>--listen-https=-<replaceable>n</replaceable></option>
+ above. In the latter case, an HTTP or HTTPS server will be
+ spawned using this descriptor and connections must be made
+ over the HTTP protocol.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Sinks</title>
+
+ <para>The location of the output journal can be specified
+ with <option>-o</option> or <option>--output=</option>. For "active"
+ sources, this option is required.
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--output=<replaceable>FILE</replaceable></option></term>
+
+ <listitem><para>Will write to this journal file. The filename
+ must end with <filename>.journal</filename>. The file will be
+ created if it does not exist. If necessary (journal file full,
+ or corrupted), the file will be renamed following normal
+ journald rules and a new journal file will be created in its
+ stead.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--output=<replaceable>DIR</replaceable></option></term>
+
+ <listitem><para>Will create journal files underneath directory
+ <replaceable>DIR</replaceable>. The directory must exist. If
+ necessary (journal files over size, or corrupted), journal
+ files will be rotated following normal journald rules. Names
+ of files underneath <replaceable>DIR</replaceable> will be
+ generated using the rules described below.</para></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>If <option>--output=</option> is not used, the output
+ directory <filename>/var/log/journal/remote/</filename> will be
+ used. In case the output file is not specified, journal files
+ will be created underneath the selected directory. Files will be
+ called
+ <filename>remote-<replaceable>hostname</replaceable>.journal</filename>,
+ where the <replaceable>hostname</replaceable> part is the
+ escaped hostname of the source endpoint of the connection, or the
+ numerical address if the hostname cannot be determined.</para>
+
+ <para>In case of "active" sources, the output file name must
+ always be given explicitly.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--split-mode</option></term>
+
+ <listitem><para>One of <constant>none</constant> or
+ <constant>host</constant>. For the first, only one output
+ journal file is used. For the latter, a separate output file
+ is used, based on the hostname of the other endpoint of a
+ connection.</para>
+
+ <para>In case of "active" sources, the output file name must
+ always be given explicitly and only <constant>none</constant>
+ is allowed.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--compress</option></term>
+ <term><option>--no-compress</option></term>
+
+ <listitem><para>Compress or not, respectively, the data in the
+ journal using XZ.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--seal</option></term>
+ <term><option>--no-seal</option></term>
+
+ <listitem><para>Periodically sign or not, respectively, the
+ data in the journal using Forward Secure Sealing.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--getter=<replaceable>PROG --option1 --option2</replaceable></option></term>
+
+ <listitem><para>Program to invoke to retrieve data. The journal
+ event stream must be generated on standard output.</para>
+
+ <para>Examples:</para>
+
+ <programlisting>--getter='curl "-HAccept: application/vnd.fdo.journal" https://some.host:19531/'</programlisting>
+
+ <programlisting>--getter='wget --header="Accept: application/vnd.fdo.journal" -O- https://some.host:19531/'</programlisting>
+ </listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <para>Copy local journal events to a different journal directory:
+ <programlisting>
+journalctl -o export | systemd-journal-remote -o /tmp/dir -
+ </programlisting>
+ </para>
+
+ <para>Retrieve all available events from a remote
+ <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ instance and store them in
+ <filename>/var/log/journal/remote/remote-some.host.journal</filename>:
+ <programlisting>
+systemd-journal-remote --url http://some.host:19531/
+ </programlisting>
+ </para>
+
+ <para>Retrieve current boot events and wait for new events from a remote
+ <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ instance, and store them in
+ <filename>/var/log/journal/remote/remote-some.host.journal</filename>:
+ <programlisting>
+systemd-journal-remote --url http://some.host:19531/entries?boot&amp;follow
+ </programlisting>
+ </para>
+</refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd-journal-upload</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>journal-remote.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/Makefile b/src/grp-journal/grp-remote/systemd-journal-upload/Makefile
new file mode 100644
index 0000000000..3c6b92832b
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/Makefile
@@ -0,0 +1,55 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+ifneq ($(HAVE_LIBCURL),)
+rootlibexec_PROGRAMS += \
+ systemd-journal-upload
+
+systemd_journal_upload_SOURCES = \
+ src/journal-remote/journal-upload.h \
+ src/journal-remote/journal-upload.c \
+ src/journal-remote/journal-upload-journal.c
+
+systemd_journal_upload_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(LIBCURL_CFLAGS)
+
+systemd_journal_upload_LDADD = \
+ libshared.la \
+ $(LIBCURL_LIBS)
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-upload.service
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-upload.conf
+
+endif # HAVE_LIBCURL
+
+EXTRA_DIST += \
+ units/systemd-journal-upload.service.in \
+ src/journal-remote/journal-upload.conf.in
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c
new file mode 100644
index 0000000000..61d287da97
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload-journal.c
@@ -0,0 +1,424 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 <curl/curl.h>
+#include <stdbool.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "basic/alloc-util.h"
+#include "basic/log.h"
+#include "basic/utf8.h"
+#include "basic/util.h"
+
+#include "journal-upload.h"
+
+/**
+ * Write up to size bytes to buf. Return negative on error, and number of
+ * bytes written otherwise. The last case is a kind of an error too.
+ */
+static ssize_t write_entry(char *buf, size_t size, Uploader *u) {
+ int r;
+ size_t pos = 0;
+
+ assert(size <= SSIZE_MAX);
+
+ for (;;) {
+
+ switch(u->entry_state) {
+ case ENTRY_CURSOR: {
+ u->current_cursor = mfree(u->current_cursor);
+
+ r = sd_journal_get_cursor(u->journal, &u->current_cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get cursor: %m");
+
+ r = snprintf(buf + pos, size - pos,
+ "__CURSOR=%s\n", u->current_cursor);
+ if (pos + r > size)
+ /* not enough space */
+ return pos;
+
+ u->entry_state++;
+
+ if (pos + r == size) {
+ /* exactly one character short, but we don't need it */
+ buf[size - 1] = '\n';
+ return size;
+ }
+
+ pos += r;
+ } /* fall through */
+
+ case ENTRY_REALTIME: {
+ usec_t realtime;
+
+ r = sd_journal_get_realtime_usec(u->journal, &realtime);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get realtime timestamp: %m");
+
+ r = snprintf(buf + pos, size - pos,
+ "__REALTIME_TIMESTAMP="USEC_FMT"\n", realtime);
+ if (r + pos > size)
+ /* not enough space */
+ return pos;
+
+ u->entry_state++;
+
+ if (r + pos == size) {
+ /* exactly one character short, but we don't need it */
+ buf[size - 1] = '\n';
+ return size;
+ }
+
+ pos += r;
+ } /* fall through */
+
+ case ENTRY_MONOTONIC: {
+ usec_t monotonic;
+ sd_id128_t boot_id;
+
+ r = sd_journal_get_monotonic_usec(u->journal, &monotonic, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get monotonic timestamp: %m");
+
+ r = snprintf(buf + pos, size - pos,
+ "__MONOTONIC_TIMESTAMP="USEC_FMT"\n", monotonic);
+ if (r + pos > size)
+ /* not enough space */
+ return pos;
+
+ u->entry_state++;
+
+ if (r + pos == size) {
+ /* exactly one character short, but we don't need it */
+ buf[size - 1] = '\n';
+ return size;
+ }
+
+ pos += r;
+ } /* fall through */
+
+ case ENTRY_BOOT_ID: {
+ sd_id128_t boot_id;
+ char sid[33];
+
+ r = sd_journal_get_monotonic_usec(u->journal, NULL, &boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get monotonic timestamp: %m");
+
+ r = snprintf(buf + pos, size - pos,
+ "_BOOT_ID=%s\n", sd_id128_to_string(boot_id, sid));
+ if (r + pos > size)
+ /* not enough space */
+ return pos;
+
+ u->entry_state++;
+
+ if (r + pos == size) {
+ /* exactly one character short, but we don't need it */
+ buf[size - 1] = '\n';
+ return size;
+ }
+
+ pos += r;
+ } /* fall through */
+
+ case ENTRY_NEW_FIELD: {
+ u->field_pos = 0;
+
+ r = sd_journal_enumerate_data(u->journal,
+ &u->field_data,
+ &u->field_length);
+ if (r < 0)
+ return log_error_errno(r, "Failed to move to next field in entry: %m");
+ else if (r == 0) {
+ u->entry_state = ENTRY_OUTRO;
+ continue;
+ }
+
+ if (!utf8_is_printable_newline(u->field_data,
+ u->field_length, false)) {
+ u->entry_state = ENTRY_BINARY_FIELD_START;
+ continue;
+ }
+
+ u->entry_state++;
+ } /* fall through */
+
+ case ENTRY_TEXT_FIELD:
+ case ENTRY_BINARY_FIELD: {
+ bool done;
+ size_t tocopy;
+
+ done = size - pos > u->field_length - u->field_pos;
+ if (done)
+ tocopy = u->field_length - u->field_pos;
+ else
+ tocopy = size - pos;
+
+ memcpy(buf + pos,
+ (char*) u->field_data + u->field_pos,
+ tocopy);
+
+ if (done) {
+ buf[pos + tocopy] = '\n';
+ pos += tocopy + 1;
+ u->entry_state = ENTRY_NEW_FIELD;
+ continue;
+ } else {
+ u->field_pos += tocopy;
+ return size;
+ }
+ }
+
+ case ENTRY_BINARY_FIELD_START: {
+ const char *c;
+ size_t len;
+
+ c = memchr(u->field_data, '=', u->field_length);
+ if (!c || c == u->field_data) {
+ log_error("Invalid field.");
+ return -EINVAL;
+ }
+
+ len = c - (const char*)u->field_data;
+
+ /* need space for label + '\n' */
+ if (size - pos < len + 1)
+ return pos;
+
+ memcpy(buf + pos, u->field_data, len);
+ buf[pos + len] = '\n';
+ pos += len + 1;
+
+ u->field_pos = len + 1;
+ u->entry_state++;
+ } /* fall through */
+
+ case ENTRY_BINARY_FIELD_SIZE: {
+ uint64_t le64;
+
+ /* need space for uint64_t */
+ if (size - pos < 8)
+ return pos;
+
+ le64 = htole64(u->field_length - u->field_pos);
+ memcpy(buf + pos, &le64, 8);
+ pos += 8;
+
+ u->entry_state++;
+ continue;
+ }
+
+ case ENTRY_OUTRO:
+ /* need space for '\n' */
+ if (size - pos < 1)
+ return pos;
+
+ buf[pos++] = '\n';
+ u->entry_state++;
+ u->entries_sent++;
+
+ return pos;
+
+ default:
+ assert_not_reached("WTF?");
+ }
+ }
+ assert_not_reached("WTF?");
+}
+
+static inline void check_update_watchdog(Uploader *u) {
+ usec_t after;
+ usec_t elapsed_time;
+
+ if (u->watchdog_usec <= 0)
+ return;
+
+ after = now(CLOCK_MONOTONIC);
+ elapsed_time = usec_sub(after, u->watchdog_timestamp);
+ if (elapsed_time > u->watchdog_usec / 2) {
+ log_debug("Update watchdog timer");
+ sd_notify(false, "WATCHDOG=1");
+ u->watchdog_timestamp = after;
+ }
+}
+
+static size_t journal_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
+ Uploader *u = userp;
+ int r;
+ sd_journal *j;
+ size_t filled = 0;
+ ssize_t w;
+
+ assert(u);
+ assert(nmemb <= SSIZE_MAX / size);
+
+ check_update_watchdog(u);
+
+ j = u->journal;
+
+ while (j && filled < size * nmemb) {
+ if (u->entry_state == ENTRY_DONE) {
+ r = sd_journal_next(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to move to next entry in journal: %m");
+ return CURL_READFUNC_ABORT;
+ } else if (r == 0) {
+ if (u->input_event)
+ log_debug("No more entries, waiting for journal.");
+ else {
+ log_info("No more entries, closing journal.");
+ close_journal_input(u);
+ }
+
+ u->uploading = false;
+
+ break;
+ }
+
+ u->entry_state = ENTRY_CURSOR;
+ }
+
+ w = write_entry((char*)buf + filled, size * nmemb - filled, u);
+ if (w < 0)
+ return CURL_READFUNC_ABORT;
+ filled += w;
+
+ if (filled == 0) {
+ log_error("Buffer space is too small to write entry.");
+ return CURL_READFUNC_ABORT;
+ } else if (u->entry_state != ENTRY_DONE)
+ /* This means that all available space was used up */
+ break;
+
+ log_debug("Entry %zu (%s) has been uploaded.",
+ u->entries_sent, u->current_cursor);
+ }
+
+ return filled;
+}
+
+void close_journal_input(Uploader *u) {
+ assert(u);
+
+ if (u->journal) {
+ log_debug("Closing journal input.");
+
+ sd_journal_close(u->journal);
+ u->journal = NULL;
+ }
+ u->timeout = 0;
+}
+
+static int process_journal_input(Uploader *u, int skip) {
+ int r;
+
+ if (u->uploading)
+ return 0;
+
+ r = sd_journal_next_skip(u->journal, skip);
+ if (r < 0)
+ return log_error_errno(r, "Failed to skip to next entry: %m");
+ else if (r < skip)
+ return 0;
+
+ /* have data */
+ u->entry_state = ENTRY_CURSOR;
+ return start_upload(u, journal_input_callback, u);
+}
+
+int check_journal_input(Uploader *u) {
+ if (u->input_event) {
+ int r;
+
+ r = sd_journal_process(u->journal);
+ if (r < 0) {
+ log_error_errno(r, "Failed to process journal: %m");
+ close_journal_input(u);
+ return r;
+ }
+
+ if (r == SD_JOURNAL_NOP)
+ return 0;
+ }
+
+ return process_journal_input(u, 1);
+}
+
+static int dispatch_journal_input(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userp) {
+ Uploader *u = userp;
+
+ assert(u);
+
+ if (u->uploading)
+ return 0;
+
+ log_debug("Detected journal input, checking for new data.");
+ return check_journal_input(u);
+}
+
+int open_journal_for_upload(Uploader *u,
+ sd_journal *j,
+ const char *cursor,
+ bool after_cursor,
+ bool follow) {
+ int fd, r, events;
+
+ u->journal = j;
+
+ sd_journal_set_data_threshold(j, 0);
+
+ if (follow) {
+ fd = sd_journal_get_fd(j);
+ if (fd < 0)
+ return log_error_errno(fd, "sd_journal_get_fd failed: %m");
+
+ events = sd_journal_get_events(j);
+
+ r = sd_journal_reliable_fd(j);
+ assert(r >= 0);
+ if (r > 0)
+ u->timeout = -1;
+ else
+ u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT;
+
+ r = sd_event_add_io(u->events, &u->input_event,
+ fd, events, dispatch_journal_input, u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to register input event: %m");
+
+ log_debug("Listening for journal events on fd:%d, timeout %d",
+ fd, u->timeout == (uint64_t) -1 ? -1 : (int) u->timeout);
+ } else
+ log_debug("Not listening for journal events.");
+
+ if (cursor) {
+ r = sd_journal_seek_cursor(j, cursor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to seek to cursor %s: %m",
+ cursor);
+ }
+
+ return process_journal_input(u, 1 + !!after_cursor);
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c
new file mode 100644
index 0000000000..82d8331e76
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.c
@@ -0,0 +1,881 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 <curl/curl.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "basic/alloc-util.h"
+#include "basic/def.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/formats-util.h"
+#include "basic/glob-util.h"
+#include "basic/log.h"
+#include "basic/mkdir.h"
+#include "basic/parse-util.h"
+#include "basic/sigbus.h"
+#include "basic/signal-util.h"
+#include "basic/string-util.h"
+#include "basic/util.h"
+#include "shared/conf-parser.h"
+
+#include "journal-upload.h"
+
+#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
+#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
+#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
+#define DEFAULT_PORT 19532
+
+static const char* arg_url = NULL;
+static const char *arg_key = NULL;
+static const char *arg_cert = NULL;
+static const char *arg_trust = NULL;
+static const char *arg_directory = NULL;
+static char **arg_file = NULL;
+static const char *arg_cursor = NULL;
+static bool arg_after_cursor = false;
+static int arg_journal_type = 0;
+static const char *arg_machine = NULL;
+static bool arg_merge = false;
+static int arg_follow = -1;
+static const char *arg_save_state = NULL;
+
+static void close_fd_input(Uploader *u);
+
+#define SERVER_ANSWER_KEEP 2048
+
+#define STATE_FILE "/var/lib/systemd/journal-upload/state"
+
+#define easy_setopt(curl, opt, value, level, cmd) \
+ do { \
+ code = curl_easy_setopt(curl, opt, value); \
+ if (code) { \
+ log_full(level, \
+ "curl_easy_setopt " #opt " failed: %s", \
+ curl_easy_strerror(code)); \
+ cmd; \
+ } \
+ } while (0)
+
+static size_t output_callback(char *buf,
+ size_t size,
+ size_t nmemb,
+ void *userp) {
+ Uploader *u = userp;
+
+ assert(u);
+
+ log_debug("The server answers (%zu bytes): %.*s",
+ size*nmemb, (int)(size*nmemb), buf);
+
+ if (nmemb && !u->answer) {
+ u->answer = strndup(buf, size*nmemb);
+ if (!u->answer)
+ log_warning_errno(ENOMEM, "Failed to store server answer (%zu bytes): %m",
+ size*nmemb);
+ }
+
+ return size * nmemb;
+}
+
+static int check_cursor_updating(Uploader *u) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ if (!u->state_file)
+ return 0;
+
+ r = mkdir_parents(u->state_file, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
+ u->state_file);
+
+ r = fopen_temporary(u->state_file, &f, &temp_path);
+ if (r < 0)
+ return log_error_errno(r, "Cannot save state to %s: %m",
+ u->state_file);
+ unlink(temp_path);
+
+ return 0;
+}
+
+static int update_cursor_state(Uploader *u) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ if (!u->state_file || !u->last_cursor)
+ return 0;
+
+ r = fopen_temporary(u->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "LAST_CURSOR=%s\n",
+ u->last_cursor);
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, u->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ (void) unlink(u->state_file);
+
+ return log_error_errno(r, "Failed to save state %s: %m", u->state_file);
+}
+
+static int load_cursor_state(Uploader *u) {
+ int r;
+
+ if (!u->state_file)
+ return 0;
+
+ r = parse_env_file(u->state_file, NEWLINE,
+ "LAST_CURSOR", &u->last_cursor,
+ NULL);
+
+ if (r == -ENOENT)
+ log_debug("State file %s is not present.", u->state_file);
+ else if (r < 0)
+ return log_error_errno(r, "Failed to read state file %s: %m",
+ u->state_file);
+ else
+ log_debug("Last cursor was %s", u->last_cursor);
+
+ return 0;
+}
+
+
+
+int start_upload(Uploader *u,
+ size_t (*input_callback)(void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *userdata),
+ void *data) {
+ CURLcode code;
+
+ assert(u);
+ assert(input_callback);
+
+ if (!u->header) {
+ struct curl_slist *h;
+
+ h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
+ if (!h)
+ return log_oom();
+
+ h = curl_slist_append(h, "Transfer-Encoding: chunked");
+ if (!h) {
+ curl_slist_free_all(h);
+ return log_oom();
+ }
+
+ h = curl_slist_append(h, "Accept: text/plain");
+ if (!h) {
+ curl_slist_free_all(h);
+ return log_oom();
+ }
+
+ u->header = h;
+ }
+
+ if (!u->easy) {
+ CURL *curl;
+
+ curl = curl_easy_init();
+ if (!curl) {
+ log_error("Call to curl_easy_init failed.");
+ return -ENOSR;
+ }
+
+ /* tell it to POST to the URL */
+ easy_setopt(curl, CURLOPT_POST, 1L,
+ LOG_ERR, return -EXFULL);
+
+ easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
+ LOG_ERR, return -EXFULL);
+
+ /* set where to write to */
+ easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
+ LOG_ERR, return -EXFULL);
+
+ easy_setopt(curl, CURLOPT_WRITEDATA, data,
+ LOG_ERR, return -EXFULL);
+
+ /* set where to read from */
+ easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
+ LOG_ERR, return -EXFULL);
+
+ easy_setopt(curl, CURLOPT_READDATA, data,
+ LOG_ERR, return -EXFULL);
+
+ /* use our special own mime type and chunked transfer */
+ easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
+ LOG_ERR, return -EXFULL);
+
+ if (_unlikely_(log_get_max_level() >= LOG_DEBUG))
+ /* enable verbose for easier tracing */
+ easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
+
+ easy_setopt(curl, CURLOPT_USERAGENT,
+ "systemd-journal-upload " PACKAGE_STRING,
+ LOG_WARNING, );
+
+ if (arg_key || startswith(u->url, "https://")) {
+ easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
+ LOG_ERR, return -EXFULL);
+ easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
+ LOG_ERR, return -EXFULL);
+ }
+
+ if (streq_ptr(arg_trust, "all"))
+ easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0,
+ LOG_ERR, return -EUCLEAN);
+ else if (arg_trust || startswith(u->url, "https://"))
+ easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
+ LOG_ERR, return -EXFULL);
+
+ if (arg_key || arg_trust)
+ easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
+ LOG_WARNING, );
+
+ u->easy = curl;
+ } else {
+ /* truncate the potential old error message */
+ u->error[0] = '\0';
+
+ free(u->answer);
+ u->answer = 0;
+ }
+
+ /* upload to this place */
+ code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
+ if (code) {
+ log_error("curl_easy_setopt CURLOPT_URL failed: %s",
+ curl_easy_strerror(code));
+ return -EXFULL;
+ }
+
+ u->uploading = true;
+
+ return 0;
+}
+
+static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
+ Uploader *u = userp;
+
+ ssize_t r;
+
+ assert(u);
+ assert(nmemb <= SSIZE_MAX / size);
+
+ if (u->input < 0)
+ return 0;
+
+ r = read(u->input, buf, size * nmemb);
+ log_debug("%s: allowed %zu, read %zd", __func__, size*nmemb, r);
+
+ if (r > 0)
+ return r;
+
+ u->uploading = false;
+ if (r == 0) {
+ log_debug("Reached EOF");
+ close_fd_input(u);
+ return 0;
+ } else {
+ log_error_errno(errno, "Aborting transfer after read error on input: %m.");
+ return CURL_READFUNC_ABORT;
+ }
+}
+
+static void close_fd_input(Uploader *u) {
+ assert(u);
+
+ if (u->input >= 0)
+ close_nointr(u->input);
+ u->input = -1;
+ u->timeout = 0;
+}
+
+static int dispatch_fd_input(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userp) {
+ Uploader *u = userp;
+
+ assert(u);
+ assert(fd >= 0);
+
+ if (revents & EPOLLHUP) {
+ log_debug("Received HUP");
+ close_fd_input(u);
+ return 0;
+ }
+
+ if (!(revents & EPOLLIN)) {
+ log_warning("Unexpected poll event %"PRIu32".", revents);
+ return -EINVAL;
+ }
+
+ if (u->uploading) {
+ log_warning("dispatch_fd_input called when uploading, ignoring.");
+ return 0;
+ }
+
+ return start_upload(u, fd_input_callback, u);
+}
+
+static int open_file_for_upload(Uploader *u, const char *filename) {
+ int fd, r = 0;
+
+ if (streq(filename, "-"))
+ fd = STDIN_FILENO;
+ else {
+ fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to open %s: %m", filename);
+ }
+
+ u->input = fd;
+
+ if (arg_follow) {
+ r = sd_event_add_io(u->events, &u->input_event,
+ fd, EPOLLIN, dispatch_fd_input, u);
+ if (r < 0) {
+ if (r != -EPERM || arg_follow > 0)
+ return log_error_errno(r, "Failed to register input event: %m");
+
+ /* Normal files should just be consumed without polling. */
+ r = start_upload(u, fd_input_callback, u);
+ }
+ }
+
+ return r;
+}
+
+static int dispatch_sigterm(sd_event_source *event,
+ const struct signalfd_siginfo *si,
+ void *userdata) {
+ Uploader *u = userdata;
+
+ assert(u);
+
+ log_received_signal(LOG_INFO, si);
+
+ close_fd_input(u);
+ close_journal_input(u);
+
+ sd_event_exit(u->events, 0);
+ return 0;
+}
+
+static int setup_signals(Uploader *u) {
+ int r;
+
+ assert(u);
+
+ assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0);
+
+ r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
+ int r;
+ const char *host, *proto = "";
+
+ assert(u);
+ assert(url);
+
+ memzero(u, sizeof(Uploader));
+ u->input = -1;
+
+ if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) {
+ host = url;
+ proto = "https://";
+ }
+
+ if (strchr(host, ':'))
+ u->url = strjoin(proto, url, "/upload", NULL);
+ else {
+ char *t;
+ size_t x;
+
+ t = strdupa(url);
+ x = strlen(t);
+ while (x > 0 && t[x - 1] == '/')
+ t[x - 1] = '\0';
+
+ u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL);
+ }
+ if (!u->url)
+ return log_oom();
+
+ u->state_file = state_file;
+
+ r = sd_event_default(&u->events);
+ if (r < 0)
+ return log_error_errno(r, "sd_event_default failed: %m");
+
+ r = setup_signals(u);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set up signals: %m");
+
+ (void) sd_watchdog_enabled(false, &u->watchdog_usec);
+
+ return load_cursor_state(u);
+}
+
+static void destroy_uploader(Uploader *u) {
+ assert(u);
+
+ curl_easy_cleanup(u->easy);
+ curl_slist_free_all(u->header);
+ free(u->answer);
+
+ free(u->last_cursor);
+ free(u->current_cursor);
+
+ free(u->url);
+
+ u->input_event = sd_event_source_unref(u->input_event);
+
+ close_fd_input(u);
+ close_journal_input(u);
+
+ sd_event_source_unref(u->sigterm_event);
+ sd_event_source_unref(u->sigint_event);
+ sd_event_unref(u->events);
+}
+
+static int perform_upload(Uploader *u) {
+ CURLcode code;
+ long status;
+
+ assert(u);
+
+ u->watchdog_timestamp = now(CLOCK_MONOTONIC);
+ code = curl_easy_perform(u->easy);
+ if (code) {
+ if (u->error[0])
+ log_error("Upload to %s failed: %.*s",
+ u->url, (int) sizeof(u->error), u->error);
+ else
+ log_error("Upload to %s failed: %s",
+ u->url, curl_easy_strerror(code));
+ return -EIO;
+ }
+
+ code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
+ if (code) {
+ log_error("Failed to retrieve response code: %s",
+ curl_easy_strerror(code));
+ return -EUCLEAN;
+ }
+
+ if (status >= 300) {
+ log_error("Upload to %s failed with code %ld: %s",
+ u->url, status, strna(u->answer));
+ return -EIO;
+ } else if (status < 200) {
+ log_error("Upload to %s finished with unexpected code %ld: %s",
+ u->url, status, strna(u->answer));
+ return -EIO;
+ } else
+ log_debug("Upload finished successfully with code %ld: %s",
+ status, strna(u->answer));
+
+ free(u->last_cursor);
+ u->last_cursor = u->current_cursor;
+ u->current_cursor = NULL;
+
+ return update_cursor_state(u);
+}
+
+static int parse_config(void) {
+ const ConfigTableItem items[] = {
+ { "Upload", "URL", config_parse_string, 0, &arg_url },
+ { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key },
+ { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
+ { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
+ {}};
+
+ return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf",
+ CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"),
+ "Upload\0", config_item_table_lookup, items,
+ false, NULL);
+}
+
+static void help(void) {
+ printf("%s -u URL {FILE|-}...\n\n"
+ "Upload journal events to a remote server.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -u --url=URL Upload to this address (default port "
+ STRINGIFY(DEFAULT_PORT) ")\n"
+ " --key=FILENAME Specify key in PEM format (default:\n"
+ " \"" PRIV_KEY_FILE "\")\n"
+ " --cert=FILENAME Specify certificate in PEM format (default:\n"
+ " \"" CERT_FILE "\")\n"
+ " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
+ " \"" TRUST_FILE "\")\n"
+ " --system Use the system journal\n"
+ " --user Use the user journal for the current user\n"
+ " -m --merge Use all available journals\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " -D --directory=PATH Use journal files from directory\n"
+ " --file=PATH Use this journal file\n"
+ " --cursor=CURSOR Start at the specified cursor\n"
+ " --after-cursor=CURSOR Start after the specified cursor\n"
+ " --follow[=BOOL] Do [not] wait for input\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_KEY,
+ ARG_CERT,
+ ARG_TRUST,
+ ARG_USER,
+ ARG_SYSTEM,
+ ARG_FILE,
+ ARG_CURSOR,
+ ARG_AFTER_CURSOR,
+ ARG_FOLLOW,
+ ARG_SAVE_STATE,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "url", required_argument, NULL, 'u' },
+ { "key", required_argument, NULL, ARG_KEY },
+ { "cert", required_argument, NULL, ARG_CERT },
+ { "trust", required_argument, NULL, ARG_TRUST },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "user", no_argument, NULL, ARG_USER },
+ { "merge", no_argument, NULL, 'm' },
+ { "machine", required_argument, NULL, 'M' },
+ { "directory", required_argument, NULL, 'D' },
+ { "file", required_argument, NULL, ARG_FILE },
+ { "cursor", required_argument, NULL, ARG_CURSOR },
+ { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
+ { "follow", optional_argument, NULL, ARG_FOLLOW },
+ { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ opterr = 0;
+
+ while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
+ switch(c) {
+ case 'h':
+ help();
+ return 0 /* done */;
+
+ case ARG_VERSION:
+ return version();
+
+ case 'u':
+ if (arg_url) {
+ log_error("cannot use more than one --url");
+ return -EINVAL;
+ }
+
+ arg_url = optarg;
+ break;
+
+ case ARG_KEY:
+ if (arg_key) {
+ log_error("cannot use more than one --key");
+ return -EINVAL;
+ }
+
+ arg_key = optarg;
+ break;
+
+ case ARG_CERT:
+ if (arg_cert) {
+ log_error("cannot use more than one --cert");
+ return -EINVAL;
+ }
+
+ arg_cert = optarg;
+ break;
+
+ case ARG_TRUST:
+ if (arg_trust) {
+ log_error("cannot use more than one --trust");
+ return -EINVAL;
+ }
+
+ arg_trust = optarg;
+ break;
+
+ case ARG_SYSTEM:
+ arg_journal_type |= SD_JOURNAL_SYSTEM;
+ break;
+
+ case ARG_USER:
+ arg_journal_type |= SD_JOURNAL_CURRENT_USER;
+ break;
+
+ case 'm':
+ arg_merge = true;
+ break;
+
+ case 'M':
+ if (arg_machine) {
+ log_error("cannot use more than one --machine/-M");
+ return -EINVAL;
+ }
+
+ arg_machine = optarg;
+ break;
+
+ case 'D':
+ if (arg_directory) {
+ log_error("cannot use more than one --directory/-D");
+ return -EINVAL;
+ }
+
+ arg_directory = optarg;
+ break;
+
+ case ARG_FILE:
+ r = glob_extend(&arg_file, optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add paths: %m");
+ break;
+
+ case ARG_CURSOR:
+ if (arg_cursor) {
+ log_error("cannot use more than one --cursor/--after-cursor");
+ return -EINVAL;
+ }
+
+ arg_cursor = optarg;
+ break;
+
+ case ARG_AFTER_CURSOR:
+ if (arg_cursor) {
+ log_error("cannot use more than one --cursor/--after-cursor");
+ return -EINVAL;
+ }
+
+ arg_cursor = optarg;
+ arg_after_cursor = true;
+ break;
+
+ case ARG_FOLLOW:
+ if (optarg) {
+ r = parse_boolean(optarg);
+ if (r < 0) {
+ log_error("Failed to parse --follow= parameter.");
+ return -EINVAL;
+ }
+
+ arg_follow = !!r;
+ } else
+ arg_follow = true;
+
+ 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 (!arg_url) {
+ log_error("Required --url/-u option missing.");
+ return -EINVAL;
+ }
+
+ if (!!arg_key != !!arg_cert) {
+ log_error("Options --key and --cert must be used together.");
+ return -EINVAL;
+ }
+
+ if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
+ log_error("Input arguments make no sense with journal input.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+static int open_journal(sd_journal **j) {
+ int r;
+
+ if (arg_directory)
+ r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
+ else if (arg_file)
+ r = sd_journal_open_files(j, (const char**) arg_file, 0);
+ else if (arg_machine)
+ r = sd_journal_open_container(j, arg_machine, 0);
+ else
+ r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
+ if (r < 0)
+ log_error_errno(r, "Failed to open %s: %m",
+ arg_directory ? arg_directory : arg_file ? "files" : "journal");
+ return r;
+}
+
+int main(int argc, char **argv) {
+ Uploader u;
+ int r;
+ bool use_journal;
+
+ log_show_color(true);
+ log_parse_environment();
+
+ r = parse_config();
+ if (r < 0)
+ goto finish;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ sigbus_install();
+
+ r = setup_uploader(&u, arg_url, arg_save_state);
+ if (r < 0)
+ goto cleanup;
+
+ sd_event_set_watchdog(u.events, true);
+
+ r = check_cursor_updating(&u);
+ if (r < 0)
+ goto cleanup;
+
+ log_debug("%s running as pid "PID_FMT,
+ program_invocation_short_name, getpid());
+
+ use_journal = optind >= argc;
+ if (use_journal) {
+ sd_journal *j;
+ r = open_journal(&j);
+ if (r < 0)
+ goto finish;
+ r = open_journal_for_upload(&u, j,
+ arg_cursor ?: u.last_cursor,
+ arg_cursor ? arg_after_cursor : true,
+ !!arg_follow);
+ if (r < 0)
+ goto finish;
+ }
+
+ sd_notify(false,
+ "READY=1\n"
+ "STATUS=Processing input...");
+
+ for (;;) {
+ r = sd_event_get_state(u.events);
+ if (r < 0)
+ break;
+ if (r == SD_EVENT_FINISHED)
+ break;
+
+ if (use_journal) {
+ if (!u.journal)
+ break;
+
+ r = check_journal_input(&u);
+ } else if (u.input < 0 && !use_journal) {
+ if (optind >= argc)
+ break;
+
+ log_debug("Using %s as input.", argv[optind]);
+ r = open_file_for_upload(&u, argv[optind++]);
+ }
+ if (r < 0)
+ goto cleanup;
+
+ if (u.uploading) {
+ r = perform_upload(&u);
+ if (r < 0)
+ break;
+ }
+
+ r = sd_event_run(u.events, u.timeout);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ break;
+ }
+ }
+
+cleanup:
+ sd_notify(false,
+ "STOPPING=1\n"
+ "STATUS=Shutting down...");
+
+ destroy_uploader(&u);
+
+finish:
+ return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in
new file mode 100644
index 0000000000..c5670682e8
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.conf.in
@@ -0,0 +1,5 @@
+[Upload]
+# URL=
+# ServerKeyFile=@CERTIFICATEROOT@/private/journal-upload.pem
+# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-upload.pem
+# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h
new file mode 100644
index 0000000000..2decc7b22e
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/journal-upload.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <inttypes.h>
+
+#include <systemd/sd-event.h>
+#include <systemd/sd-journal.h>
+
+#include "basic/time-util.h"
+
+typedef enum {
+ ENTRY_CURSOR = 0, /* Nothing actually written yet. */
+ ENTRY_REALTIME,
+ ENTRY_MONOTONIC,
+ ENTRY_BOOT_ID,
+ ENTRY_NEW_FIELD, /* In between fields. */
+ ENTRY_TEXT_FIELD, /* In the middle of a text field. */
+ ENTRY_BINARY_FIELD_START, /* Writing the name of a binary field. */
+ ENTRY_BINARY_FIELD_SIZE, /* Writing the size of a binary field. */
+ ENTRY_BINARY_FIELD, /* In the middle of a binary field. */
+ ENTRY_OUTRO, /* Writing '\n' */
+ ENTRY_DONE, /* Need to move to a new field. */
+} entry_state;
+
+typedef struct Uploader {
+ sd_event *events;
+ sd_event_source *sigint_event, *sigterm_event;
+
+ char *url;
+ CURL *easy;
+ bool uploading;
+ char error[CURL_ERROR_SIZE];
+ struct curl_slist *header;
+ char *answer;
+
+ sd_event_source *input_event;
+ uint64_t timeout;
+
+ /* fd stuff */
+ int input;
+
+ /* journal stuff */
+ sd_journal* journal;
+
+ entry_state entry_state;
+ const void *field_data;
+ size_t field_pos, field_length;
+
+ /* general metrics */
+ const char *state_file;
+
+ size_t entries_sent;
+ char *last_cursor, *current_cursor;
+ usec_t watchdog_timestamp;
+ usec_t watchdog_usec;
+} Uploader;
+
+#define JOURNAL_UPLOAD_POLL_TIMEOUT (10 * USEC_PER_SEC)
+
+int start_upload(Uploader *u,
+ size_t (*input_callback)(void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *userdata),
+ void *data);
+
+int open_journal_for_upload(Uploader *u,
+ sd_journal *j,
+ const char *cursor,
+ bool after_cursor,
+ bool follow);
+void close_journal_input(Uploader *u);
+int check_journal_input(Uploader *u);
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.service.in b/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.service.in
new file mode 100644
index 0000000000..1f488ff425
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.service.in
@@ -0,0 +1,27 @@
+# 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 Remote Upload Service
+Documentation=man:systemd-journal-upload(8)
+After=network.target
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journal-upload \
+ --save-state
+User=systemd-journal-upload
+SupplementaryGroups=systemd-journal
+PrivateTmp=yes
+PrivateDevices=yes
+WatchdogSec=3min
+
+# If there are many split up journal files we need a lot of fds to
+# access them all and combine
+LimitNOFILE=16384
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.sysusers b/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.sysusers
new file mode 100644
index 0000000000..927d400279
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.sysusers
@@ -0,0 +1,8 @@
+# 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.
+
+u systemd-journal-upload - "systemd Journal Upload"
diff --git a/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.xml b/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.xml
new file mode 100644
index 0000000000..f9723dea89
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-upload/systemd-journal-upload.xml
@@ -0,0 +1,263 @@
+<?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 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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-upload" conditional='HAVE_MICROHTTPD'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-upload</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Zbigniew</firstname>
+ <surname>Jędrzejewski-Szmek</surname>
+ <email>zbyszek@in.waw.pl</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-upload</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-upload</refname>
+ <refpurpose>Send journal messages over the network</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-upload</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">-u/--url=<replaceable>URL</replaceable></arg>
+ <arg choice="opt" rep="repeat">SOURCES</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>systemd-journal-upload</command> will upload journal
+ entries to the URL specified with <option>--url</option>. Unless
+ limited by one of the options specified below, all journal
+ entries accessible to the user the program is running as will be
+ uploaded, and then the program will wait and send new entries
+ as they become available.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-u</option></term>
+ <term><option>--url=<optional>https://</optional><replaceable>URL</replaceable></option></term>
+ <term><option>--url=<optional>http://</optional><replaceable>URL</replaceable></option></term>
+
+ <listitem><para>Upload to the specified
+ address. <replaceable>URL</replaceable> may specify either
+ just the hostname or both the protocol and
+ hostname. <constant>https</constant> is the default.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--system</option></term>
+ <term><option>--user</option></term>
+
+ <listitem><para>Limit uploaded entries to entries from system
+ services and the kernel, or to entries from services of
+ current user. This has the same meaning as
+ <option>--system</option> and <option>--user</option> options
+ for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. If
+ neither is specified, all accessible entries are uploaded.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-m</option></term>
+ <term><option>--merge</option></term>
+
+ <listitem><para>Upload entries interleaved from all available
+ journals, including other machines. This has the same meaning
+ as <option>--merge</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-D</option></term>
+ <term><option>--directory=<replaceable>DIR</replaceable></option></term>
+
+ <listitem><para>Takes a directory path as argument. Upload
+ entries from the specified journal directory
+ <replaceable>DIR</replaceable> instead of the default runtime
+ and system journal paths. This has the same meaning as
+ <option>--directory</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--file=<replaceable>GLOB</replaceable></option></term>
+
+ <listitem><para>Takes a file glob as an argument. Upload
+ entries from the specified journal files matching
+ <replaceable>GLOB</replaceable> instead of the default runtime
+ and system journal paths. May be specified multiple times, in
+ which case files will be suitably interleaved. This has the same meaning as
+ <option>--file</option> option for
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Upload 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>--after-cursor=</option></term>
+
+ <listitem><para>Upload entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the this cursor. This has the same meaning as
+ <option>--after-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>Upload 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-upload/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>Examples</title>
+ <example>
+ <title>Setting up certificates for authentication</title>
+
+ <para>Certificates signed by a trusted authority are used to
+ verify that the server to which messages are uploaded is
+ legitimate, and vice versa, that the client is trusted.</para>
+
+ <para>A suitable set of certificates can be generated with
+ <command>openssl</command>:</para>
+
+ <programlisting>openssl req -newkey rsa:2048 -days 3650 -x509 -nodes \
+ -out ca.pem -keyout ca.key -subj '/CN=Certificate authority/'
+
+cat &gt;ca.conf &lt;&lt;EOF
+[ ca ]
+default_ca = this
+
+[ this ]
+new_certs_dir = .
+certificate = ca.pem
+database = ./index
+private_key = ca.key
+serial = ./serial
+default_days = 3650
+default_md = default
+policy = policy_anything
+
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+EOF
+
+touch index
+echo 0001 &gt;serial
+
+SERVER=server
+CLIENT=client
+
+openssl req -newkey rsa:1024 -nodes -out $SERVER.csr -keyout $SERVER.key -subj "/CN=$SERVER/"
+openssl ca -batch -config ca.conf -notext -in $SERVER.csr -out $SERVER.pem
+
+openssl req -newkey rsa:1024 -nodes -out $CLIENT.csr -keyout $CLIENT.key -subj "/CN=$CLIENT/"
+openssl ca -batch -config ca.conf -notext -in $CLIENT.csr -out $CLIENT.pem
+</programlisting>
+
+ <para>Generated files <filename>ca.pem</filename>,
+ <filename>server.pem</filename>, and
+ <filename>server.key</filename> should be installed on server,
+ and <filename>ca.pem</filename>,
+ <filename>client.pem</filename>, and
+ <filename>client.key</filename> on the client. The location of
+ those files can be specified using
+ <varname>TrustedCertificateFile=</varname>,
+ <varname>ServerCertificateFile=</varname>,
+ <varname>ServerKeyFile=</varname>, in
+ <filename>/etc/systemd/journal-remote.conf</filename> and
+ <filename>/etc/systemd/journal-upload.conf</filename>,
+ respectively. The default locations can be queried by using
+ <command>systemd-journal-remote --help</command> and
+ <command>systemd-journal-upload --help</command>.</para>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/grp-journal/grp-remote/systemd-remote.tmpfiles b/src/grp-journal/grp-remote/systemd-remote.tmpfiles
new file mode 100644
index 0000000000..e19230f648
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-remote.tmpfiles
@@ -0,0 +1,13 @@
+# 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.
+
+# See tmpfiles.d(5) for details
+
+d /var/lib/systemd/journal-upload 0755 systemd-journal-upload systemd-journal-upload - -
+
+z /var/log/journal/remote 2755 systemd-journal-remote systemd-journal-remote - -
+z /run/log/journal/remote 2755 systemd-journal-remote systemd-journal-remote - -
diff --git a/src/grp-journal/journal-nocow.tmpfiles b/src/grp-journal/journal-nocow.tmpfiles
new file mode 100644
index 0000000000..e7938c8911
--- /dev/null
+++ b/src/grp-journal/journal-nocow.tmpfiles
@@ -0,0 +1,27 @@
+# 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.
+
+# See tmpfiles.d(5) for details
+
+# Set the NOCOW attribute for directories of journal files. This flag
+# is inheredited by their new files and sub-directories. Matters only
+# for btrfs filesystems.
+#
+# WARNING: Enabling the NOCOW attribute improves journal performance
+# substantially, but also disables the btrfs checksum logic. In
+# btrfs RAID filesystems the checksums are needed for rebuilding
+# corrupted files. Without checksums such rebuilds are not
+# possible.
+#
+# In a single-disk filesystem (or a filesystem without redundancy)
+# enabling the NOCOW attribute for journal files is safe, because
+# they have their own checksums and a rebuilding wouldn't be possible
+# in any case.
+
+h /var/log/journal - - - - +C
+h /var/log/journal/%m - - - - +C
+h /var/log/journal/remote - - - - +C
diff --git a/src/grp-journal/journalctl/Makefile b/src/grp-journal/journalctl/Makefile
new file mode 100644
index 0000000000..fcf118f4d3
--- /dev/null
+++ b/src/grp-journal/journalctl/Makefile
@@ -0,0 +1,62 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+# using _CFLAGS = in the conditional below would suppress AM_CFLAGS
+journalctl_CFLAGS = \
+ $(AM_CFLAGS)
+
+journalctl_SOURCES = \
+ src/journal/journalctl.c
+
+journalctl_LDADD = \
+ libshared.la \
+ libudev-core.la
+
+ifneq ($(HAVE_QRENCODE),)
+journalctl_SOURCES += \
+ src/journal/journal-qrcode.c \
+ src/journal/journal-qrcode.h
+
+journalctl_CFLAGS += \
+ $(QRENCODE_CFLAGS)
+
+journalctl_LDADD += \
+ $(QRENCODE_LIBS)
+endif # HAVE_QRENCODE
+
+rootbin_PROGRAMS += \
+ journalctl
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-flush.service \
+ units/systemd-journal-catalog-update.service
+SYSINIT_TARGET_WANTS += \
+ systemd-journal-flush.service \
+ systemd-journal-catalog-update.service
+EXTRA_DIST += \
+ units/systemd-journal-flush.service.in \
+ units/systemd-journal-catalog-update.service.in
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/journalctl/journal-qrcode.c b/src/grp-journal/journalctl/journal-qrcode.c
new file mode 100644
index 0000000000..e38730d65c
--- /dev/null
+++ b/src/grp-journal/journalctl/journal-qrcode.c
@@ -0,0 +1,135 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <assert.h>
+#include <errno.h>
+#include <qrencode.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "journal-qrcode.h"
+
+#define WHITE_ON_BLACK "\033[40;37;1m"
+#define NORMAL "\033[0m"
+
+static void print_border(FILE *output, unsigned width) {
+ unsigned x, y;
+
+ /* Four rows of border */
+ for (y = 0; y < 4; y += 2) {
+ fputs(WHITE_ON_BLACK, output);
+
+ for (x = 0; x < 4 + width + 4; x++)
+ fputs("\342\226\210", output);
+
+ fputs(NORMAL "\n", output);
+ }
+}
+
+int print_qr_code(
+ FILE *output,
+ const void *seed,
+ size_t seed_size,
+ uint64_t start,
+ uint64_t interval,
+ const char *hn,
+ sd_id128_t machine) {
+
+ FILE *f;
+ char *url = NULL;
+ size_t url_size = 0, i;
+ QRcode* qr;
+ unsigned x, y;
+
+ assert(seed);
+ assert(seed_size > 0);
+
+ f = open_memstream(&url, &url_size);
+ if (!f)
+ return -ENOMEM;
+
+ fputs("fss://", f);
+
+ for (i = 0; i < seed_size; i++) {
+ if (i > 0 && i % 3 == 0)
+ fputc('-', f);
+ fprintf(f, "%02x", ((uint8_t*) seed)[i]);
+ }
+
+ fprintf(f, "/%"PRIx64"-%"PRIx64"?machine=" SD_ID128_FORMAT_STR,
+ start,
+ interval,
+ SD_ID128_FORMAT_VAL(machine));
+
+ if (hn)
+ fprintf(f, ";hostname=%s", hn);
+
+ if (ferror(f)) {
+ fclose(f);
+ free(url);
+ return -ENOMEM;
+ }
+
+ fclose(f);
+
+ qr = QRcode_encodeString(url, 0, QR_ECLEVEL_L, QR_MODE_8, 1);
+ free(url);
+
+ if (!qr)
+ return -ENOMEM;
+
+ print_border(output, qr->width);
+
+ for (y = 0; y < (unsigned) qr->width; y += 2) {
+ const uint8_t *row1, *row2;
+
+ row1 = qr->data + qr->width * y;
+ row2 = row1 + qr->width;
+
+ fputs(WHITE_ON_BLACK, output);
+ for (x = 0; x < 4; x++)
+ fputs("\342\226\210", output);
+
+ for (x = 0; x < (unsigned) qr->width; x ++) {
+ bool a, b;
+
+ a = row1[x] & 1;
+ b = (y+1) < (unsigned) qr->width ? (row2[x] & 1) : false;
+
+ if (a && b)
+ fputc(' ', output);
+ else if (a)
+ fputs("\342\226\204", output);
+ else if (b)
+ fputs("\342\226\200", output);
+ else
+ fputs("\342\226\210", output);
+ }
+
+ for (x = 0; x < 4; x++)
+ fputs("\342\226\210", output);
+ fputs(NORMAL "\n", output);
+ }
+
+ print_border(output, qr->width);
+
+ QRcode_free(qr);
+ return 0;
+}
diff --git a/src/grp-journal/journalctl/journal-qrcode.h b/src/grp-journal/journalctl/journal-qrcode.h
new file mode 100644
index 0000000000..34a779d5be
--- /dev/null
+++ b/src/grp-journal/journalctl/journal-qrcode.h
@@ -0,0 +1,27 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <inttypes.h>
+#include <stdio.h>
+
+#include <systemd/sd-id128.h>
+
+int print_qr_code(FILE *f, const void *seed, size_t seed_size, uint64_t start, uint64_t interval, const char *hn, sd_id128_t machine);
diff --git a/src/grp-journal/journalctl/journalctl.c b/src/grp-journal/journalctl/journalctl.c
new file mode 100644
index 0000000000..8901b46ae5
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.c
@@ -0,0 +1,2602 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <locale.h>
+#include <poll.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <linux/fs.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-journal.h>
+
+#include "basic/alloc-util.h"
+#include "basic/chattr-util.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/fs-util.h"
+#include "basic/glob-util.h"
+#include "basic/hostname-util.h"
+#include "basic/io-util.h"
+#include "basic/locale-util.h"
+#include "basic/log.h"
+#include "basic/mkdir.h"
+#include "basic/parse-util.h"
+#include "basic/path-util.h"
+#include "basic/rlimit-util.h"
+#include "basic/set.h"
+#include "basic/sigbus.h"
+#include "basic/strv.h"
+#include "basic/syslog-util.h"
+#include "basic/terminal-util.h"
+#include "basic/unit-name.h"
+#include "basic/user-util.h"
+#include "sd-bus/bus-error.h"
+#include "sd-journal/catalog.h"
+#include "sd-journal/fsprg.h"
+#include "sd-journal/journal-def.h"
+#include "sd-journal/journal-internal.h"
+#include "sd-journal/journal-vacuum.h"
+#include "sd-journal/journal-verify.h"
+#include "shared/acl-util.h"
+#include "shared/bus-util.h"
+#include "shared/logs-show.h"
+#include "shared/pager.h"
+#include "shared/udev-util.h"
+#include "udev.h"
+
+#include "journal-qrcode.h"
+
+#define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
+
+enum {
+ /* Special values for arg_lines */
+ ARG_LINES_DEFAULT = -2,
+ ARG_LINES_ALL = -1,
+};
+
+static OutputMode arg_output = OUTPUT_SHORT;
+static bool arg_utc = false;
+static bool arg_pager_end = false;
+static bool arg_follow = false;
+static bool arg_full = true;
+static bool arg_all = false;
+static bool arg_no_pager = false;
+static int arg_lines = ARG_LINES_DEFAULT;
+static bool arg_no_tail = false;
+static bool arg_quiet = false;
+static bool arg_merge = false;
+static bool arg_boot = false;
+static sd_id128_t arg_boot_id = {};
+static int arg_boot_offset = 0;
+static bool arg_dmesg = false;
+static bool arg_no_hostname = false;
+static const char *arg_cursor = NULL;
+static const char *arg_after_cursor = NULL;
+static bool arg_show_cursor = false;
+static const char *arg_directory = NULL;
+static char **arg_file = NULL;
+static bool arg_file_stdin = false;
+static int arg_priorities = 0xFF;
+static const char *arg_verify_key = NULL;
+#ifdef HAVE_GCRYPT
+static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
+static bool arg_force = false;
+#endif
+static usec_t arg_since, arg_until;
+static bool arg_since_set = false, arg_until_set = false;
+static char **arg_syslog_identifier = NULL;
+static char **arg_system_units = NULL;
+static char **arg_user_units = NULL;
+static const char *arg_field = NULL;
+static bool arg_catalog = false;
+static bool arg_reverse = false;
+static int arg_journal_type = 0;
+static char *arg_root = NULL;
+static const char *arg_machine = NULL;
+static uint64_t arg_vacuum_size = 0;
+static uint64_t arg_vacuum_n_files = 0;
+static usec_t arg_vacuum_time = 0;
+
+static enum {
+ ACTION_SHOW,
+ ACTION_NEW_ID128,
+ ACTION_PRINT_HEADER,
+ ACTION_SETUP_KEYS,
+ ACTION_VERIFY,
+ ACTION_DISK_USAGE,
+ ACTION_LIST_CATALOG,
+ ACTION_DUMP_CATALOG,
+ ACTION_UPDATE_CATALOG,
+ ACTION_LIST_BOOTS,
+ ACTION_FLUSH,
+ ACTION_SYNC,
+ ACTION_ROTATE,
+ ACTION_VACUUM,
+ ACTION_LIST_FIELDS,
+ ACTION_LIST_FIELD_NAMES,
+} arg_action = ACTION_SHOW;
+
+typedef struct BootId {
+ sd_id128_t id;
+ uint64_t first;
+ uint64_t last;
+ LIST_FIELDS(struct BootId, boot_list);
+} BootId;
+
+static int add_matches_for_device(sd_journal *j, const char *devpath) {
+ int r;
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ struct udev_device *d = NULL;
+ struct stat st;
+
+ assert(j);
+ assert(devpath);
+
+ if (!path_startswith(devpath, "/dev/")) {
+ log_error("Devpath does not start with /dev/");
+ return -EINVAL;
+ }
+
+ udev = udev_new();
+ if (!udev)
+ return log_oom();
+
+ r = stat(devpath, &st);
+ if (r < 0)
+ log_error_errno(errno, "Couldn't stat file: %m");
+
+ d = device = udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev);
+ if (!device)
+ return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev));
+
+ while (d) {
+ _cleanup_free_ char *match = NULL;
+ const char *subsys, *sysname, *devnode;
+
+ subsys = udev_device_get_subsystem(d);
+ if (!subsys) {
+ d = udev_device_get_parent(d);
+ continue;
+ }
+
+ sysname = udev_device_get_sysname(d);
+ if (!sysname) {
+ d = udev_device_get_parent(d);
+ continue;
+ }
+
+ match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL);
+ if (!match)
+ return log_oom();
+
+ r = sd_journal_add_match(j, match, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+
+ devnode = udev_device_get_devnode(d);
+ if (devnode) {
+ _cleanup_free_ char *match1 = NULL;
+
+ r = stat(devnode, &st);
+ if (r < 0)
+ return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode);
+
+ r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev));
+ if (r < 0)
+ return log_oom();
+
+ r = sd_journal_add_match(j, match1, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+ }
+
+ d = udev_device_get_parent(d);
+ }
+
+ r = add_match_this_boot(j, arg_machine);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match for the current boot: %m");
+
+ return 0;
+}
+
+static char *format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) {
+
+ if (arg_utc)
+ return format_timestamp_utc(buf, l, t);
+
+ return format_timestamp(buf, l, t);
+}
+
+static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset) {
+ sd_id128_t id = SD_ID128_NULL;
+ int off = 0, r;
+
+ if (strlen(x) >= 32) {
+ char *t;
+
+ t = strndupa(x, 32);
+ r = sd_id128_from_string(t, &id);
+ if (r >= 0)
+ x += 32;
+
+ if (*x != '-' && *x != '+' && *x != 0)
+ return -EINVAL;
+
+ if (*x != 0) {
+ r = safe_atoi(x, &off);
+ if (r < 0)
+ return r;
+ }
+ } else {
+ r = safe_atoi(x, &off);
+ if (r < 0)
+ return r;
+ }
+
+ if (boot_id)
+ *boot_id = id;
+
+ if (offset)
+ *offset = off;
+
+ return 0;
+}
+
+static void help(void) {
+
+ pager_open(arg_no_pager, arg_pager_end);
+
+ printf("%s [OPTIONS...] [MATCHES...]\n\n"
+ "Query the journal.\n\n"
+ "Options:\n"
+ " --system Show the system journal\n"
+ " --user Show the user journal for the current user\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " -S --since=DATE Show entries not older than the specified date\n"
+ " -U --until=DATE Show entries not newer than the specified date\n"
+ " -c --cursor=CURSOR Show entries starting at the specified cursor\n"
+ " --after-cursor=CURSOR Show entries after the specified cursor\n"
+ " --show-cursor Print the cursor after all the entries\n"
+ " -b --boot[=ID] Show current boot or the specified boot\n"
+ " --list-boots Show terse information about recorded boots\n"
+ " -k --dmesg Show kernel message log from the current boot\n"
+ " -u --unit=UNIT Show logs from the specified unit\n"
+ " --user-unit=UNIT Show logs from the specified user unit\n"
+ " -t --identifier=STRING Show entries with the specified syslog identifier\n"
+ " -p --priority=RANGE Show entries with the specified priority\n"
+ " -e --pager-end Immediately jump to the end in the pager\n"
+ " -f --follow Follow the journal\n"
+ " -n --lines[=INTEGER] Number of journal entries to show\n"
+ " --no-tail Show all lines, even in follow mode\n"
+ " -r --reverse Show the newest entries first\n"
+ " -o --output=STRING Change journal output mode (short, short-iso,\n"
+ " short-precise, short-monotonic, verbose,\n"
+ " export, json, json-pretty, json-sse, cat)\n"
+ " --utc Express time in Coordinated Universal Time (UTC)\n"
+ " -x --catalog Add message explanations where available\n"
+ " --no-full Ellipsize fields\n"
+ " -a --all Show all fields, including long and unprintable\n"
+ " -q --quiet Do not show info messages and privilege warning\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-hostname Suppress output of hostname field\n"
+ " -m --merge Show entries from all available journals\n"
+ " -D --directory=PATH Show journal files from directory\n"
+ " --file=PATH Show journal file\n"
+ " --root=ROOT Operate on catalog files below a root directory\n"
+#ifdef HAVE_GCRYPT
+ " --interval=TIME Time interval for changing the FSS sealing key\n"
+ " --verify-key=KEY Specify FSS verification key\n"
+ " --force Override of the FSS key pair with --setup-keys\n"
+#endif
+ "\nCommands:\n"
+ " -h --help Show this help text\n"
+ " --version Show package version\n"
+ " -N --fields List all field names currently used\n"
+ " -F --field=FIELD List all values that a specified field takes\n"
+ " --disk-usage Show total disk usage of all journal files\n"
+ " --vacuum-size=BYTES Reduce disk usage below specified size\n"
+ " --vacuum-files=INT Leave only the specified number of journal files\n"
+ " --vacuum-time=TIME Remove journal files older than specified time\n"
+ " --verify Verify journal file consistency\n"
+ " --sync Synchronize unwritten journal messages to disk\n"
+ " --flush Flush all journal data from /run into /var\n"
+ " --rotate Request immediate rotation of the journal files\n"
+ " --header Show journal header information\n"
+ " --list-catalog Show all message IDs in the catalog\n"
+ " --dump-catalog Show entries in the message catalog\n"
+ " --update-catalog Update the message catalog database\n"
+ " --new-id128 Generate a new 128-bit ID\n"
+#ifdef HAVE_GCRYPT
+ " --setup-keys Generate a new FSS key pair\n"
+#endif
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_FULL,
+ ARG_NO_TAIL,
+ ARG_NEW_ID128,
+ ARG_LIST_BOOTS,
+ ARG_USER,
+ ARG_SYSTEM,
+ ARG_ROOT,
+ ARG_HEADER,
+ ARG_SETUP_KEYS,
+ ARG_FILE,
+ ARG_INTERVAL,
+ ARG_VERIFY,
+ ARG_VERIFY_KEY,
+ ARG_DISK_USAGE,
+ ARG_AFTER_CURSOR,
+ ARG_SHOW_CURSOR,
+ ARG_USER_UNIT,
+ ARG_LIST_CATALOG,
+ ARG_DUMP_CATALOG,
+ ARG_UPDATE_CATALOG,
+ ARG_FORCE,
+ ARG_UTC,
+ ARG_SYNC,
+ ARG_FLUSH,
+ ARG_ROTATE,
+ ARG_VACUUM_SIZE,
+ ARG_VACUUM_FILES,
+ ARG_VACUUM_TIME,
+ ARG_NO_HOSTNAME,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version" , no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "pager-end", no_argument, NULL, 'e' },
+ { "follow", no_argument, NULL, 'f' },
+ { "force", no_argument, NULL, ARG_FORCE },
+ { "output", required_argument, NULL, 'o' },
+ { "all", no_argument, NULL, 'a' },
+ { "full", no_argument, NULL, 'l' },
+ { "no-full", no_argument, NULL, ARG_NO_FULL },
+ { "lines", optional_argument, NULL, 'n' },
+ { "no-tail", no_argument, NULL, ARG_NO_TAIL },
+ { "new-id128", no_argument, NULL, ARG_NEW_ID128 },
+ { "quiet", no_argument, NULL, 'q' },
+ { "merge", no_argument, NULL, 'm' },
+ { "boot", optional_argument, NULL, 'b' },
+ { "list-boots", no_argument, NULL, ARG_LIST_BOOTS },
+ { "this-boot", optional_argument, NULL, 'b' }, /* deprecated */
+ { "dmesg", no_argument, NULL, 'k' },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "user", no_argument, NULL, ARG_USER },
+ { "directory", required_argument, NULL, 'D' },
+ { "file", required_argument, NULL, ARG_FILE },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "header", no_argument, NULL, ARG_HEADER },
+ { "identifier", required_argument, NULL, 't' },
+ { "priority", required_argument, NULL, 'p' },
+ { "setup-keys", no_argument, NULL, ARG_SETUP_KEYS },
+ { "interval", required_argument, NULL, ARG_INTERVAL },
+ { "verify", no_argument, NULL, ARG_VERIFY },
+ { "verify-key", required_argument, NULL, ARG_VERIFY_KEY },
+ { "disk-usage", no_argument, NULL, ARG_DISK_USAGE },
+ { "cursor", required_argument, NULL, 'c' },
+ { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
+ { "show-cursor", no_argument, NULL, ARG_SHOW_CURSOR },
+ { "since", required_argument, NULL, 'S' },
+ { "until", required_argument, NULL, 'U' },
+ { "unit", required_argument, NULL, 'u' },
+ { "user-unit", required_argument, NULL, ARG_USER_UNIT },
+ { "field", required_argument, NULL, 'F' },
+ { "fields", no_argument, NULL, 'N' },
+ { "catalog", no_argument, NULL, 'x' },
+ { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG },
+ { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG },
+ { "update-catalog", no_argument, NULL, ARG_UPDATE_CATALOG },
+ { "reverse", no_argument, NULL, 'r' },
+ { "machine", required_argument, NULL, 'M' },
+ { "utc", no_argument, NULL, ARG_UTC },
+ { "flush", no_argument, NULL, ARG_FLUSH },
+ { "sync", no_argument, NULL, ARG_SYNC },
+ { "rotate", no_argument, NULL, ARG_ROTATE },
+ { "vacuum-size", required_argument, NULL, ARG_VACUUM_SIZE },
+ { "vacuum-files", required_argument, NULL, ARG_VACUUM_FILES },
+ { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME },
+ { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case 'e':
+ arg_pager_end = true;
+
+ if (arg_lines == ARG_LINES_DEFAULT)
+ arg_lines = 1000;
+
+ break;
+
+ case 'f':
+ arg_follow = true;
+ break;
+
+ case 'o':
+ arg_output = output_mode_from_string(optarg);
+ if (arg_output < 0) {
+ log_error("Unknown output format '%s'.", optarg);
+ return -EINVAL;
+ }
+
+ if (arg_output == OUTPUT_EXPORT ||
+ arg_output == OUTPUT_JSON ||
+ arg_output == OUTPUT_JSON_PRETTY ||
+ arg_output == OUTPUT_JSON_SSE ||
+ arg_output == OUTPUT_CAT)
+ arg_quiet = true;
+
+ break;
+
+ case 'l':
+ arg_full = true;
+ break;
+
+ case ARG_NO_FULL:
+ arg_full = false;
+ break;
+
+ case 'a':
+ arg_all = true;
+ break;
+
+ case 'n':
+ if (optarg) {
+ if (streq(optarg, "all"))
+ arg_lines = ARG_LINES_ALL;
+ else {
+ r = safe_atoi(optarg, &arg_lines);
+ if (r < 0 || arg_lines < 0) {
+ log_error("Failed to parse lines '%s'", optarg);
+ return -EINVAL;
+ }
+ }
+ } else {
+ arg_lines = 10;
+
+ /* Hmm, no argument? Maybe the next
+ * word on the command line is
+ * supposed to be the argument? Let's
+ * see if there is one, and is
+ * parsable. */
+ if (optind < argc) {
+ int n;
+ if (streq(argv[optind], "all")) {
+ arg_lines = ARG_LINES_ALL;
+ optind++;
+ } else if (safe_atoi(argv[optind], &n) >= 0 && n >= 0) {
+ arg_lines = n;
+ optind++;
+ }
+ }
+ }
+
+ break;
+
+ case ARG_NO_TAIL:
+ arg_no_tail = true;
+ break;
+
+ case ARG_NEW_ID128:
+ arg_action = ACTION_NEW_ID128;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case 'm':
+ arg_merge = true;
+ break;
+
+ case 'b':
+ arg_boot = true;
+
+ if (optarg) {
+ r = parse_boot_descriptor(optarg, &arg_boot_id, &arg_boot_offset);
+ if (r < 0) {
+ log_error("Failed to parse boot descriptor '%s'", optarg);
+ return -EINVAL;
+ }
+ } else {
+
+ /* Hmm, no argument? Maybe the next
+ * word on the command line is
+ * supposed to be the argument? Let's
+ * see if there is one and is parsable
+ * as a boot descriptor... */
+
+ if (optind < argc &&
+ parse_boot_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset) >= 0)
+ optind++;
+ }
+
+ break;
+
+ case ARG_LIST_BOOTS:
+ arg_action = ACTION_LIST_BOOTS;
+ break;
+
+ case 'k':
+ arg_boot = arg_dmesg = true;
+ break;
+
+ case ARG_SYSTEM:
+ arg_journal_type |= SD_JOURNAL_SYSTEM;
+ break;
+
+ case ARG_USER:
+ arg_journal_type |= SD_JOURNAL_CURRENT_USER;
+ break;
+
+ case 'M':
+ arg_machine = optarg;
+ break;
+
+ case 'D':
+ arg_directory = optarg;
+ break;
+
+ case ARG_FILE:
+ if (streq(optarg, "-"))
+ /* An undocumented feature: we can read journal files from STDIN. We don't document
+ * this though, since after all we only support this for mmap-able, seekable files, and
+ * not for example pipes which are probably the primary usecase for reading things from
+ * STDIN. To avoid confusion we hence don't document this feature. */
+ arg_file_stdin = true;
+ else {
+ r = glob_extend(&arg_file, optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add paths: %m");
+ }
+ break;
+
+ case ARG_ROOT:
+ r = parse_path_argument_and_warn(optarg, true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case 'c':
+ arg_cursor = optarg;
+ break;
+
+ case ARG_AFTER_CURSOR:
+ arg_after_cursor = optarg;
+ break;
+
+ case ARG_SHOW_CURSOR:
+ arg_show_cursor = true;
+ break;
+
+ case ARG_HEADER:
+ arg_action = ACTION_PRINT_HEADER;
+ break;
+
+ case ARG_VERIFY:
+ arg_action = ACTION_VERIFY;
+ break;
+
+ case ARG_DISK_USAGE:
+ arg_action = ACTION_DISK_USAGE;
+ break;
+
+ case ARG_VACUUM_SIZE:
+ r = parse_size(optarg, 1024, &arg_vacuum_size);
+ if (r < 0) {
+ log_error("Failed to parse vacuum size: %s", optarg);
+ return r;
+ }
+
+ arg_action = ACTION_VACUUM;
+ break;
+
+ case ARG_VACUUM_FILES:
+ r = safe_atou64(optarg, &arg_vacuum_n_files);
+ if (r < 0) {
+ log_error("Failed to parse vacuum files: %s", optarg);
+ return r;
+ }
+
+ arg_action = ACTION_VACUUM;
+ break;
+
+ case ARG_VACUUM_TIME:
+ r = parse_sec(optarg, &arg_vacuum_time);
+ if (r < 0) {
+ log_error("Failed to parse vacuum time: %s", optarg);
+ return r;
+ }
+
+ arg_action = ACTION_VACUUM;
+ break;
+
+#ifdef HAVE_GCRYPT
+ case ARG_FORCE:
+ arg_force = true;
+ break;
+
+ case ARG_SETUP_KEYS:
+ arg_action = ACTION_SETUP_KEYS;
+ break;
+
+
+ case ARG_VERIFY_KEY:
+ arg_action = ACTION_VERIFY;
+ arg_verify_key = optarg;
+ arg_merge = false;
+ break;
+
+ case ARG_INTERVAL:
+ r = parse_sec(optarg, &arg_interval);
+ if (r < 0 || arg_interval <= 0) {
+ log_error("Failed to parse sealing key change interval: %s", optarg);
+ return -EINVAL;
+ }
+ break;
+#else
+ case ARG_SETUP_KEYS:
+ case ARG_VERIFY_KEY:
+ case ARG_INTERVAL:
+ case ARG_FORCE:
+ log_error("Forward-secure sealing not available.");
+ return -EOPNOTSUPP;
+#endif
+
+ case 'p': {
+ const char *dots;
+
+ dots = strstr(optarg, "..");
+ if (dots) {
+ char *a;
+ int from, to, i;
+
+ /* a range */
+ a = strndup(optarg, dots - optarg);
+ if (!a)
+ return log_oom();
+
+ from = log_level_from_string(a);
+ to = log_level_from_string(dots + 2);
+ free(a);
+
+ if (from < 0 || to < 0) {
+ log_error("Failed to parse log level range %s", optarg);
+ return -EINVAL;
+ }
+
+ arg_priorities = 0;
+
+ if (from < to) {
+ for (i = from; i <= to; i++)
+ arg_priorities |= 1 << i;
+ } else {
+ for (i = to; i <= from; i++)
+ arg_priorities |= 1 << i;
+ }
+
+ } else {
+ int p, i;
+
+ p = log_level_from_string(optarg);
+ if (p < 0) {
+ log_error("Unknown log level %s", optarg);
+ return -EINVAL;
+ }
+
+ arg_priorities = 0;
+
+ for (i = 0; i <= p; i++)
+ arg_priorities |= 1 << i;
+ }
+
+ break;
+ }
+
+ case 'S':
+ r = parse_timestamp(optarg, &arg_since);
+ if (r < 0) {
+ log_error("Failed to parse timestamp: %s", optarg);
+ return -EINVAL;
+ }
+ arg_since_set = true;
+ break;
+
+ case 'U':
+ r = parse_timestamp(optarg, &arg_until);
+ if (r < 0) {
+ log_error("Failed to parse timestamp: %s", optarg);
+ return -EINVAL;
+ }
+ arg_until_set = true;
+ break;
+
+ case 't':
+ r = strv_extend(&arg_syslog_identifier, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case 'u':
+ r = strv_extend(&arg_system_units, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case ARG_USER_UNIT:
+ r = strv_extend(&arg_user_units, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case 'F':
+ arg_action = ACTION_LIST_FIELDS;
+ arg_field = optarg;
+ break;
+
+ case 'N':
+ arg_action = ACTION_LIST_FIELD_NAMES;
+ break;
+
+ case ARG_NO_HOSTNAME:
+ arg_no_hostname = true;
+ break;
+
+ case 'x':
+ arg_catalog = true;
+ break;
+
+ case ARG_LIST_CATALOG:
+ arg_action = ACTION_LIST_CATALOG;
+ break;
+
+ case ARG_DUMP_CATALOG:
+ arg_action = ACTION_DUMP_CATALOG;
+ break;
+
+ case ARG_UPDATE_CATALOG:
+ arg_action = ACTION_UPDATE_CATALOG;
+ break;
+
+ case 'r':
+ arg_reverse = true;
+ break;
+
+ case ARG_UTC:
+ arg_utc = true;
+ break;
+
+ case ARG_FLUSH:
+ arg_action = ACTION_FLUSH;
+ break;
+
+ case ARG_ROTATE:
+ arg_action = ACTION_ROTATE;
+ break;
+
+ case ARG_SYNC:
+ arg_action = ACTION_SYNC;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ if (arg_follow && !arg_no_tail && !arg_since && arg_lines == ARG_LINES_DEFAULT)
+ arg_lines = 10;
+
+ if (!!arg_directory + !!arg_file + !!arg_machine > 1) {
+ log_error("Please specify either -D/--directory= or --file= or -M/--machine=, not more than one.");
+ return -EINVAL;
+ }
+
+ if (arg_since_set && arg_until_set && arg_since > arg_until) {
+ log_error("--since= must be before --until=.");
+ return -EINVAL;
+ }
+
+ if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1) {
+ log_error("Please specify only one of --since=, --cursor=, and --after-cursor.");
+ return -EINVAL;
+ }
+
+ if (arg_follow && arg_reverse) {
+ log_error("Please specify either --reverse= or --follow=, not both.");
+ return -EINVAL;
+ }
+
+ if (!IN_SET(arg_action, ACTION_SHOW, ACTION_DUMP_CATALOG, ACTION_LIST_CATALOG) && optind < argc) {
+ log_error("Extraneous arguments starting with '%s'", argv[optind]);
+ return -EINVAL;
+ }
+
+ if ((arg_boot || arg_action == ACTION_LIST_BOOTS) && (arg_file || arg_directory || arg_merge)) {
+ log_error("Using --boot or --list-boots with --file, --directory or --merge is not supported.");
+ return -EINVAL;
+ }
+
+ if (!strv_isempty(arg_system_units) && (arg_journal_type == SD_JOURNAL_CURRENT_USER)) {
+
+ /* Specifying --user and --unit= at the same time makes no sense (as the former excludes the user
+ * journal, but the latter excludes the system journal, thus resulting in empty output). Let's be nice
+ * to users, and automatically turn --unit= into --user-unit= if combined with --user. */
+ r = strv_extend_strv(&arg_user_units, arg_system_units, true);
+ if (r < 0)
+ return -ENOMEM;
+
+ arg_system_units = strv_free(arg_system_units);
+ }
+
+ return 1;
+}
+
+static int generate_new_id128(void) {
+ sd_id128_t id;
+ int r;
+ unsigned i;
+
+ r = sd_id128_randomize(&id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate ID: %m");
+
+ printf("As string:\n"
+ SD_ID128_FORMAT_STR "\n\n"
+ "As UUID:\n"
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
+ "As macro:\n"
+ "#define MESSAGE_XYZ SD_ID128_MAKE(",
+ SD_ID128_FORMAT_VAL(id),
+ SD_ID128_FORMAT_VAL(id));
+ for (i = 0; i < 16; i++)
+ printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
+ fputs(")\n\n", stdout);
+
+ printf("As Python constant:\n"
+ ">>> import uuid\n"
+ ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n",
+ SD_ID128_FORMAT_VAL(id));
+
+ return 0;
+}
+
+static int add_matches(sd_journal *j, char **args) {
+ char **i;
+ bool have_term = false;
+
+ assert(j);
+
+ STRV_FOREACH(i, args) {
+ int r;
+
+ if (streq(*i, "+")) {
+ if (!have_term)
+ break;
+ r = sd_journal_add_disjunction(j);
+ have_term = false;
+
+ } else if (path_is_absolute(*i)) {
+ _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL;
+ const char *path;
+ struct stat st;
+
+ p = canonicalize_file_name(*i);
+ path = p ?: *i;
+
+ if (lstat(path, &st) < 0)
+ return log_error_errno(errno, "Couldn't stat file: %m");
+
+ if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
+ if (executable_is_script(path, &interpreter) > 0) {
+ _cleanup_free_ char *comm;
+
+ comm = strndup(basename(path), 15);
+ if (!comm)
+ return log_oom();
+
+ t = strappend("_COMM=", comm);
+ if (!t)
+ return log_oom();
+
+ /* Append _EXE only if the interpreter is not a link.
+ Otherwise, it might be outdated often. */
+ if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) {
+ t2 = strappend("_EXE=", interpreter);
+ if (!t2)
+ return log_oom();
+ }
+ } else {
+ t = strappend("_EXE=", path);
+ if (!t)
+ return log_oom();
+ }
+
+ r = sd_journal_add_match(j, t, 0);
+
+ if (r >=0 && t2)
+ r = sd_journal_add_match(j, t2, 0);
+
+ } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
+ r = add_matches_for_device(j, path);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("File is neither a device node, nor regular file, nor executable: %s", *i);
+ return -EINVAL;
+ }
+
+ have_term = true;
+ } else {
+ r = sd_journal_add_match(j, *i, 0);
+ have_term = true;
+ }
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match '%s': %m", *i);
+ }
+
+ if (!strv_isempty(args) && !have_term) {
+ log_error("\"+\" can only be used between terms");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void boot_id_free_all(BootId *l) {
+
+ while (l) {
+ BootId *i = l;
+ LIST_REMOVE(boot_list, l, i);
+ free(i);
+ }
+}
+
+static int discover_next_boot(sd_journal *j,
+ sd_id128_t previous_boot_id,
+ bool advance_older,
+ BootId **ret) {
+
+ _cleanup_free_ BootId *next_boot = NULL;
+ char match[9+32+1] = "_BOOT_ID=";
+ sd_id128_t boot_id;
+ int r;
+
+ assert(j);
+ assert(ret);
+
+ /* We expect the journal to be on the last position of a boot
+ * (in relation to the direction we are going), so that the next
+ * invocation of sd_journal_next/previous will be from a different
+ * boot. We then collect any information we desire and then jump
+ * to the last location of the new boot by using a _BOOT_ID match
+ * coming from the other journal direction. */
+
+ /* Make sure we aren't restricted by any _BOOT_ID matches, so that
+ * we can actually advance to a *different* boot. */
+ sd_journal_flush_matches(j);
+
+ do {
+ if (advance_older)
+ r = sd_journal_previous(j);
+ else
+ r = sd_journal_next(j);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ return 0; /* End of journal, yay. */
+
+ r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
+ if (r < 0)
+ return r;
+
+ /* We iterate through this in a loop, until the boot ID differs from the previous one. Note that
+ * normally, this will only require a single iteration, as we seeked to the last entry of the previous
+ * boot entry already. However, it might happen that the per-journal-field entry arrays are less
+ * complete than the main entry array, and hence might reference an entry that's not actually the last
+ * one of the boot ID as last one. Let's hence use the per-field array is initial seek position to
+ * speed things up, but let's not trust that it is complete, and hence, manually advance as
+ * necessary. */
+
+ } while (sd_id128_equal(boot_id, previous_boot_id));
+
+ next_boot = new0(BootId, 1);
+ if (!next_boot)
+ return -ENOMEM;
+
+ next_boot->id = boot_id;
+
+ r = sd_journal_get_realtime_usec(j, &next_boot->first);
+ if (r < 0)
+ return r;
+
+ /* Now seek to the last occurrence of this boot ID. */
+ sd_id128_to_string(next_boot->id, match + 9);
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_seek_head(j);
+ else
+ r = sd_journal_seek_tail(j);
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_next(j);
+ else
+ r = sd_journal_previous(j);
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ return -ENODATA; /* This shouldn't happen. We just came from this very boot ID. */
+
+ r = sd_journal_get_realtime_usec(j, &next_boot->last);
+ if (r < 0)
+ return r;
+
+ *ret = next_boot;
+ next_boot = NULL;
+
+ return 0;
+}
+
+static int get_boots(
+ sd_journal *j,
+ BootId **boots,
+ sd_id128_t *query_ref_boot,
+ int ref_boot_offset) {
+
+ bool skip_once;
+ int r, count = 0;
+ BootId *head = NULL, *tail = NULL;
+ const bool advance_older = query_ref_boot && ref_boot_offset <= 0;
+ sd_id128_t previous_boot_id;
+
+ assert(j);
+
+ /* Adjust for the asymmetry that offset 0 is
+ * the last (and current) boot, while 1 is considered the
+ * (chronological) first boot in the journal. */
+ skip_once = query_ref_boot && sd_id128_is_null(*query_ref_boot) && ref_boot_offset < 0;
+
+ /* Advance to the earliest/latest occurrence of our reference
+ * boot ID (taking our lookup direction into account), so that
+ * discover_next_boot() can do its job.
+ * If no reference is given, the journal head/tail will do,
+ * they're "virtual" boots after all. */
+ if (query_ref_boot && !sd_id128_is_null(*query_ref_boot)) {
+ char match[9+32+1] = "_BOOT_ID=";
+
+ sd_journal_flush_matches(j);
+
+ sd_id128_to_string(*query_ref_boot, match + 9);
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_seek_head(j); /* seek to oldest */
+ else
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ if (r < 0)
+ return r;
+
+ if (advance_older)
+ r = sd_journal_next(j); /* read the oldest entry */
+ else
+ r = sd_journal_previous(j); /* read the most recently added entry */
+ if (r < 0)
+ return r;
+ else if (r == 0)
+ goto finish;
+ else if (ref_boot_offset == 0) {
+ count = 1;
+ goto finish;
+ }
+
+ /* At this point the read pointer is positioned at the oldest/newest occurence of the reference boot
+ * ID. After flushing the matches, one more invocation of _previous()/_next() will hence place us at
+ * the following entry, which must then have an older/newer boot ID */
+ } else {
+
+ if (advance_older)
+ r = sd_journal_seek_tail(j); /* seek to newest */
+ else
+ r = sd_journal_seek_head(j); /* seek to oldest */
+ if (r < 0)
+ return r;
+
+ /* No sd_journal_next()/_previous() here.
+ *
+ * At this point the read pointer is positioned after the newest/before the oldest entry in the whole
+ * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest
+ * entry we have. */
+ }
+
+ previous_boot_id = SD_ID128_NULL;
+ for (;;) {
+ _cleanup_free_ BootId *current = NULL;
+
+ r = discover_next_boot(j, previous_boot_id, advance_older, &current);
+ if (r < 0) {
+ boot_id_free_all(head);
+ return r;
+ }
+
+ if (!current)
+ break;
+
+ previous_boot_id = current->id;
+
+ if (query_ref_boot) {
+ if (!skip_once)
+ ref_boot_offset += advance_older ? 1 : -1;
+ skip_once = false;
+
+ if (ref_boot_offset == 0) {
+ count = 1;
+ *query_ref_boot = current->id;
+ break;
+ }
+ } else {
+ LIST_INSERT_AFTER(boot_list, head, tail, current);
+ tail = current;
+ current = NULL;
+ count++;
+ }
+ }
+
+finish:
+ if (boots)
+ *boots = head;
+
+ sd_journal_flush_matches(j);
+
+ return count;
+}
+
+static int list_boots(sd_journal *j) {
+ int w, i, count;
+ BootId *id, *all_ids;
+
+ assert(j);
+
+ count = get_boots(j, &all_ids, NULL, 0);
+ if (count < 0)
+ return log_error_errno(count, "Failed to determine boots: %m");
+ if (count == 0)
+ return count;
+
+ pager_open(arg_no_pager, arg_pager_end);
+
+ /* numbers are one less, but we need an extra char for the sign */
+ w = DECIMAL_STR_WIDTH(count - 1) + 1;
+
+ i = 0;
+ LIST_FOREACH(boot_list, id, all_ids) {
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
+
+ printf("% *i " SD_ID128_FORMAT_STR " %s—%s\n",
+ w, i - count + 1,
+ SD_ID128_FORMAT_VAL(id->id),
+ format_timestamp_maybe_utc(a, sizeof(a), id->first),
+ format_timestamp_maybe_utc(b, sizeof(b), id->last));
+ i++;
+ }
+
+ boot_id_free_all(all_ids);
+
+ return 0;
+}
+
+static int add_boot(sd_journal *j) {
+ char match[9+32+1] = "_BOOT_ID=";
+ sd_id128_t ref_boot_id;
+ int r;
+
+ assert(j);
+
+ if (!arg_boot)
+ return 0;
+
+ if (arg_boot_offset == 0 && sd_id128_equal(arg_boot_id, SD_ID128_NULL))
+ return add_match_this_boot(j, arg_machine);
+
+ ref_boot_id = arg_boot_id;
+ r = get_boots(j, NULL, &ref_boot_id, arg_boot_offset);
+ assert(r <= 1);
+ if (r <= 0) {
+ const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r);
+
+ if (sd_id128_is_null(arg_boot_id))
+ log_error("Data from the specified boot (%+i) is not available: %s",
+ arg_boot_offset, reason);
+ else
+ log_error("Data from the specified boot ("SD_ID128_FORMAT_STR") is not available: %s",
+ SD_ID128_FORMAT_VAL(arg_boot_id), reason);
+
+ return r == 0 ? -ENODATA : r;
+ }
+
+ sd_id128_to_string(ref_boot_id, match + 9);
+
+ r = sd_journal_add_match(j, match, sizeof(match) - 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add conjunction: %m");
+
+ return 0;
+}
+
+static int add_dmesg(sd_journal *j) {
+ int r;
+ assert(j);
+
+ if (!arg_dmesg)
+ return 0;
+
+ r = sd_journal_add_match(j, "_TRANSPORT=kernel", strlen("_TRANSPORT=kernel"));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add conjunction: %m");
+
+ return 0;
+}
+
+static int get_possible_units(
+ sd_journal *j,
+ const char *fields,
+ char **patterns,
+ Set **units) {
+
+ _cleanup_set_free_free_ Set *found;
+ const char *field;
+ int r;
+
+ found = set_new(&string_hash_ops);
+ if (!found)
+ return -ENOMEM;
+
+ NULSTR_FOREACH(field, fields) {
+ const void *data;
+ size_t size;
+
+ r = sd_journal_query_unique(j, field);
+ if (r < 0)
+ return r;
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
+ char **pattern, *eq;
+ size_t prefix;
+ _cleanup_free_ char *u = NULL;
+
+ eq = memchr(data, '=', size);
+ if (eq)
+ prefix = eq - (char*) data + 1;
+ else
+ prefix = 0;
+
+ u = strndup((char*) data + prefix, size - prefix);
+ if (!u)
+ return -ENOMEM;
+
+ STRV_FOREACH(pattern, patterns)
+ if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) {
+ log_debug("Matched %s with pattern %s=%s", u, field, *pattern);
+
+ r = set_consume(found, u);
+ u = NULL;
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ break;
+ }
+ }
+ }
+
+ *units = found;
+ found = NULL;
+ return 0;
+}
+
+/* This list is supposed to return the superset of unit names
+ * possibly matched by rules added with add_matches_for_unit... */
+#define SYSTEM_UNITS \
+ "_SYSTEMD_UNIT\0" \
+ "COREDUMP_UNIT\0" \
+ "UNIT\0" \
+ "OBJECT_SYSTEMD_UNIT\0" \
+ "_SYSTEMD_SLICE\0"
+
+/* ... and add_matches_for_user_unit */
+#define USER_UNITS \
+ "_SYSTEMD_USER_UNIT\0" \
+ "USER_UNIT\0" \
+ "COREDUMP_USER_UNIT\0" \
+ "OBJECT_SYSTEMD_USER_UNIT\0"
+
+static int add_units(sd_journal *j) {
+ _cleanup_strv_free_ char **patterns = NULL;
+ int r, count = 0;
+ char **i;
+
+ assert(j);
+
+ STRV_FOREACH(i, arg_system_units) {
+ _cleanup_free_ char *u = NULL;
+
+ r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u);
+ if (r < 0)
+ return r;
+
+ if (string_is_glob(u)) {
+ r = strv_push(&patterns, u);
+ if (r < 0)
+ return r;
+ u = NULL;
+ } else {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count++;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_free_ Set *units = NULL;
+ Iterator it;
+ char *u;
+
+ r = get_possible_units(j, SYSTEM_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units, it) {
+ r = add_matches_for_unit(j, u);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count++;
+ }
+ }
+
+ patterns = strv_free(patterns);
+
+ STRV_FOREACH(i, arg_user_units) {
+ _cleanup_free_ char *u = NULL;
+
+ r = unit_name_mangle(*i, UNIT_NAME_GLOB, &u);
+ if (r < 0)
+ return r;
+
+ if (string_is_glob(u)) {
+ r = strv_push(&patterns, u);
+ if (r < 0)
+ return r;
+ u = NULL;
+ } else {
+ r = add_matches_for_user_unit(j, u, getuid());
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count++;
+ }
+ }
+
+ if (!strv_isempty(patterns)) {
+ _cleanup_set_free_free_ Set *units = NULL;
+ Iterator it;
+ char *u;
+
+ r = get_possible_units(j, USER_UNITS, patterns, &units);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(u, units, it) {
+ r = add_matches_for_user_unit(j, u, getuid());
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ count++;
+ }
+ }
+
+ /* Complain if the user request matches but nothing whatsoever was
+ * found, since otherwise everything would be matched. */
+ if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0)
+ return -ENODATA;
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int add_priorities(sd_journal *j) {
+ char match[] = "PRIORITY=0";
+ int i, r;
+ assert(j);
+
+ if (arg_priorities == 0xFF)
+ return 0;
+
+ for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
+ if (arg_priorities & (1 << i)) {
+ match[sizeof(match)-2] = '0' + i;
+
+ r = sd_journal_add_match(j, match, strlen(match));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add match: %m");
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add conjunction: %m");
+
+ return 0;
+}
+
+
+static int add_syslog_identifier(sd_journal *j) {
+ int r;
+ char **i;
+
+ assert(j);
+
+ STRV_FOREACH(i, arg_syslog_identifier) {
+ char *u;
+
+ u = strjoina("SYSLOG_IDENTIFIER=", *i);
+ r = sd_journal_add_match(j, u, 0);
+ if (r < 0)
+ return r;
+ r = sd_journal_add_disjunction(j);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_journal_add_conjunction(j);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int setup_keys(void) {
+#ifdef HAVE_GCRYPT
+ size_t mpk_size, seed_size, state_size, i;
+ uint8_t *mpk, *seed, *state;
+ int fd = -1, r;
+ sd_id128_t machine, boot;
+ char *p = NULL, *k = NULL;
+ struct FSSHeader h;
+ uint64_t n;
+ struct stat st;
+
+ r = stat("/var/log/journal", &st);
+ if (r < 0 && errno != ENOENT && errno != ENOTDIR)
+ return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal");
+
+ if (r < 0 || !S_ISDIR(st.st_mode)) {
+ log_error("%s is not a directory, must be using persistent logging for FSS.",
+ "/var/log/journal");
+ return r < 0 ? -errno : -ENOTDIR;
+ }
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get machine ID: %m");
+
+ r = sd_id128_get_boot(&boot);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get boot ID: %m");
+
+ if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss",
+ SD_ID128_FORMAT_VAL(machine)) < 0)
+ return log_oom();
+
+ if (arg_force) {
+ r = unlink(p);
+ if (r < 0 && errno != ENOENT) {
+ r = log_error_errno(errno, "unlink(\"%s\") failed: %m", p);
+ goto finish;
+ }
+ } else if (access(p, F_OK) >= 0) {
+ log_error("Sealing key file %s exists already. Use --force to recreate.", p);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX",
+ SD_ID128_FORMAT_VAL(machine)) < 0) {
+ r = log_oom();
+ goto finish;
+ }
+
+ mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR);
+ mpk = alloca(mpk_size);
+
+ seed_size = FSPRG_RECOMMENDED_SEEDLEN;
+ seed = alloca(seed_size);
+
+ state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR);
+ state = alloca(state_size);
+
+ fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (fd < 0) {
+ r = log_error_errno(errno, "Failed to open /dev/random: %m");
+ goto finish;
+ }
+
+ log_info("Generating seed...");
+ r = loop_read_exact(fd, seed, seed_size, true);
+ if (r < 0) {
+ log_error_errno(r, "Failed to read random seed: %m");
+ goto finish;
+ }
+
+ log_info("Generating key pair...");
+ FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR);
+
+ log_info("Generating sealing key...");
+ FSPRG_GenState0(state, mpk, seed, seed_size);
+
+ assert(arg_interval > 0);
+
+ n = now(CLOCK_REALTIME);
+ n /= arg_interval;
+
+ safe_close(fd);
+ fd = mkostemp_safe(k, O_WRONLY|O_CLOEXEC);
+ if (fd < 0) {
+ r = log_error_errno(fd, "Failed to open %s: %m", k);
+ goto finish;
+ }
+
+ /* Enable secure remove, exclusion from dump, synchronous
+ * writing and in-place updating */
+ r = chattr_fd(fd, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL, FS_SECRM_FL|FS_NODUMP_FL|FS_SYNC_FL|FS_NOCOW_FL);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set file attributes: %m");
+
+ zero(h);
+ memcpy(h.signature, "KSHHRHLP", 8);
+ h.machine_id = machine;
+ h.boot_id = boot;
+ h.header_size = htole64(sizeof(h));
+ h.start_usec = htole64(n * arg_interval);
+ h.interval_usec = htole64(arg_interval);
+ h.fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR);
+ h.fsprg_state_size = htole64(state_size);
+
+ r = loop_write(fd, &h, sizeof(h), false);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write header: %m");
+ goto finish;
+ }
+
+ r = loop_write(fd, state, state_size, false);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write state: %m");
+ goto finish;
+ }
+
+ if (link(k, p) < 0) {
+ r = log_error_errno(errno, "Failed to link file: %m");
+ goto finish;
+ }
+
+ if (on_tty()) {
+ fprintf(stderr,
+ "\n"
+ "The new key pair has been generated. The " ANSI_HIGHLIGHT "secret sealing key" ANSI_NORMAL " has been written to\n"
+ "the following local file. This key file is automatically updated when the\n"
+ "sealing key is advanced. It should not be used on multiple hosts.\n"
+ "\n"
+ "\t%s\n"
+ "\n"
+ "Please write down the following " ANSI_HIGHLIGHT "secret verification key" ANSI_NORMAL ". It should be stored\n"
+ "at a safe location and should not be saved locally on disk.\n"
+ "\n\t" ANSI_HIGHLIGHT_RED, p);
+ fflush(stderr);
+ }
+ for (i = 0; i < seed_size; i++) {
+ if (i > 0 && i % 3 == 0)
+ putchar('-');
+ printf("%02x", ((uint8_t*) seed)[i]);
+ }
+
+ printf("/%llx-%llx\n", (unsigned long long) n, (unsigned long long) arg_interval);
+
+ if (on_tty()) {
+ char tsb[FORMAT_TIMESPAN_MAX], *hn;
+
+ fprintf(stderr,
+ ANSI_NORMAL "\n"
+ "The sealing key is automatically changed every %s.\n",
+ format_timespan(tsb, sizeof(tsb), arg_interval, 0));
+
+ hn = gethostname_malloc();
+
+ if (hn) {
+ hostname_cleanup(hn);
+ fprintf(stderr, "\nThe keys have been generated for host %s/" SD_ID128_FORMAT_STR ".\n", hn, SD_ID128_FORMAT_VAL(machine));
+ } else
+ fprintf(stderr, "\nThe keys have been generated for host " SD_ID128_FORMAT_STR ".\n", SD_ID128_FORMAT_VAL(machine));
+
+#ifdef HAVE_QRENCODE
+ /* If this is not an UTF-8 system don't print any QR codes */
+ if (is_locale_utf8()) {
+ fputs("\nTo transfer the verification key to your phone please scan the QR code below:\n\n", stderr);
+ print_qr_code(stderr, seed, seed_size, n, arg_interval, hn, machine);
+ }
+#endif
+ free(hn);
+ }
+
+ r = 0;
+
+finish:
+ safe_close(fd);
+
+ if (k) {
+ unlink(k);
+ free(k);
+ }
+
+ free(p);
+
+ return r;
+#else
+ log_error("Forward-secure sealing not available.");
+ return -EOPNOTSUPP;
+#endif
+}
+
+static int verify(sd_journal *j) {
+ int r = 0;
+ Iterator i;
+ JournalFile *f;
+
+ assert(j);
+
+ log_show_color(true);
+
+ ORDERED_HASHMAP_FOREACH(f, j->files, i) {
+ int k;
+ usec_t first = 0, validated = 0, last = 0;
+
+#ifdef HAVE_GCRYPT
+ if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header))
+ log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path);
+#endif
+
+ k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, true);
+ if (k == -EINVAL) {
+ /* If the key was invalid give up right-away. */
+ return k;
+ } else if (k < 0) {
+ log_warning_errno(k, "FAIL: %s (%m)", f->path);
+ r = k;
+ } else {
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX], c[FORMAT_TIMESPAN_MAX];
+ log_info("PASS: %s", f->path);
+
+ if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) {
+ if (validated > 0) {
+ log_info("=> Validated from %s to %s, final %s entries not sealed.",
+ format_timestamp_maybe_utc(a, sizeof(a), first),
+ format_timestamp_maybe_utc(b, sizeof(b), validated),
+ format_timespan(c, sizeof(c), last > validated ? last - validated : 0, 0));
+ } else if (last > 0)
+ log_info("=> No sealing yet, %s of entries not sealed.",
+ format_timespan(c, sizeof(c), last - first, 0));
+ else
+ log_info("=> No sealing yet, no entries in file.");
+ }
+ }
+ }
+
+ return r;
+}
+
+static int access_check_var_log_journal(sd_journal *j) {
+#ifdef HAVE_ACL
+ _cleanup_strv_free_ char **g = NULL;
+ const char* dir;
+#endif
+ int r;
+
+ assert(j);
+
+ if (arg_quiet)
+ return 0;
+
+ /* If we are root, we should have access, don't warn. */
+ if (getuid() == 0)
+ return 0;
+
+ /* If we are in the 'systemd-journal' group, we should have
+ * access too. */
+ r = in_group("systemd-journal");
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if we are in the 'systemd-journal' group: %m");
+ if (r > 0)
+ return 0;
+
+#ifdef HAVE_ACL
+ if (laccess("/run/log/journal", F_OK) >= 0)
+ dir = "/run/log/journal";
+ else
+ dir = "/var/log/journal";
+
+ /* If we are in any of the groups listed in the journal ACLs,
+ * then all is good, too. Let's enumerate all groups from the
+ * default ACL of the directory, which generally should allow
+ * access to most journal files too. */
+ r = acl_search_groups(dir, &g);
+ if (r < 0)
+ return log_error_errno(r, "Failed to search journal ACL: %m");
+ if (r > 0)
+ return 0;
+
+ /* Print a pretty list, if there were ACLs set. */
+ if (!strv_isempty(g)) {
+ _cleanup_free_ char *s = NULL;
+
+ /* Thre are groups in the ACL, let's list them */
+ r = strv_extend(&g, "systemd-journal");
+ if (r < 0)
+ return log_oom();
+
+ strv_sort(g);
+ strv_uniq(g);
+
+ s = strv_join(g, "', '");
+ if (!s)
+ return log_oom();
+
+ log_notice("Hint: You are currently not seeing messages from other users and the system.\n"
+ " Users in groups '%s' can see all messages.\n"
+ " Pass -q to turn off this notice.", s);
+ return 1;
+ }
+#endif
+
+ /* If no ACLs were found, print a short version of the message. */
+ log_notice("Hint: You are currently not seeing messages from other users and the system.\n"
+ " Users in the 'systemd-journal' group can see all messages. Pass -q to\n"
+ " turn off this notice.");
+
+ return 1;
+}
+
+static int access_check(sd_journal *j) {
+ Iterator it;
+ void *code;
+ char *path;
+ int r = 0;
+
+ assert(j);
+
+ if (hashmap_isempty(j->errors)) {
+ if (ordered_hashmap_isempty(j->files))
+ log_notice("No journal files were found.");
+
+ return 0;
+ }
+
+ if (hashmap_contains(j->errors, INT_TO_PTR(-EACCES))) {
+ (void) access_check_var_log_journal(j);
+
+ if (ordered_hashmap_isempty(j->files))
+ r = log_error_errno(EACCES, "No journal files were opened due to insufficient permissions.");
+ }
+
+ HASHMAP_FOREACH_KEY(path, code, j->errors, it) {
+ int err;
+
+ err = abs(PTR_TO_INT(code));
+
+ switch (err) {
+ case EACCES:
+ continue;
+
+ case ENODATA:
+ log_warning_errno(err, "Journal file %s is truncated, ignoring file.", path);
+ break;
+
+ case EPROTONOSUPPORT:
+ log_warning_errno(err, "Journal file %s uses an unsupported feature, ignoring file.", path);
+ break;
+
+ case EBADMSG:
+ log_warning_errno(err, "Journal file %s corrupted, ignoring file.", path);
+ break;
+
+ default:
+ log_warning_errno(err, "An error was encountered while opening journal file or directory %s, ignoring file: %m", path);
+ break;
+ }
+ }
+
+ return r;
+}
+
+static int flush_to_var(void) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_close_ int watch_fd = -1;
+ int r;
+
+ if (arg_machine) {
+ log_error("--flush is not supported in conjunction with --machine=.");
+ return -EOPNOTSUPP;
+ }
+
+ /* Quick exit */
+ if (access("/run/systemd/journal/flushed", F_OK) >= 0)
+ return 0;
+
+ /* OK, let's actually do the full logic, send SIGUSR1 to the
+ * daemon and set up inotify to wait for the flushed file to appear */
+ r = bus_connect_system_systemd(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get D-Bus connection: %m");
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KillUnit",
+ &error,
+ NULL,
+ "ssi", "systemd-journald.service", "main", SIGUSR1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
+
+ mkdir_p("/run/systemd/journal", 0755);
+
+ watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+ if (watch_fd < 0)
+ return log_error_errno(errno, "Failed to create inotify watch: %m");
+
+ r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_CREATE|IN_DONT_FOLLOW|IN_ONLYDIR);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to watch journal directory: %m");
+
+ for (;;) {
+ if (access("/run/systemd/journal/flushed", F_OK) >= 0)
+ break;
+
+ if (errno != ENOENT)
+ return log_error_errno(errno, "Failed to check for existence of /run/systemd/journal/flushed: %m");
+
+ r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for event: %m");
+
+ r = flush_fd(watch_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to flush inotify events: %m");
+ }
+
+ return 0;
+}
+
+static int send_signal_and_wait(int sig, const char *watch_path) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_close_ int watch_fd = -1;
+ usec_t start;
+ int r;
+
+ if (arg_machine) {
+ log_error("--sync and --rotate are not supported in conjunction with --machine=.");
+ return -EOPNOTSUPP;
+ }
+
+ start = now(CLOCK_MONOTONIC);
+
+ /* This call sends the specified signal to journald, and waits
+ * for acknowledgment by watching the mtime of the specified
+ * flag file. This is used to trigger syncing or rotation and
+ * then wait for the operation to complete. */
+
+ for (;;) {
+ usec_t tstamp;
+
+ /* See if a sync happened by now. */
+ r = read_timestamp_file(watch_path, &tstamp);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(errno, "Failed to read %s: %m", watch_path);
+ if (r >= 0 && tstamp >= start)
+ return 0;
+
+ /* Let's ask for a sync, but only once. */
+ if (!bus) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+ r = bus_connect_system_systemd(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get D-Bus connection: %m");
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KillUnit",
+ &error,
+ NULL,
+ "ssi", "systemd-journald.service", "main", sig);
+ if (r < 0)
+ return log_error_errno(r, "Failed to kill journal service: %s", bus_error_message(&error, r));
+
+ continue;
+ }
+
+ /* Let's install the inotify watch, if we didn't do that yet. */
+ if (watch_fd < 0) {
+
+ mkdir_p("/run/systemd/journal", 0755);
+
+ watch_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+ if (watch_fd < 0)
+ return log_error_errno(errno, "Failed to create inotify watch: %m");
+
+ r = inotify_add_watch(watch_fd, "/run/systemd/journal", IN_MOVED_TO|IN_DONT_FOLLOW|IN_ONLYDIR);
+ if (r < 0)
+ return log_error_errno(errno, "Failed to watch journal directory: %m");
+
+ /* Recheck the flag file immediately, so that we don't miss any event since the last check. */
+ continue;
+ }
+
+ /* OK, all preparatory steps done, let's wait until
+ * inotify reports an event. */
+
+ r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for event: %m");
+
+ r = flush_fd(watch_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to flush inotify events: %m");
+ }
+
+ return 0;
+}
+
+static int rotate(void) {
+ return send_signal_and_wait(SIGUSR2, "/run/systemd/journal/rotated");
+}
+
+static int sync_journal(void) {
+ return send_signal_and_wait(SIGRTMIN+1, "/run/systemd/journal/synced");
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ bool need_seek = false;
+ sd_id128_t previous_boot_id;
+ bool previous_boot_id_valid = false, first_line = true;
+ int n_shown = 0;
+ bool ellipsized = false;
+
+ setlocale(LC_ALL, "");
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ signal(SIGWINCH, columns_lines_cache_reset);
+ sigbus_install();
+
+ /* Increase max number of open files to 16K if we can, we
+ * might needs this when browsing journal files, which might
+ * be split up into many files. */
+ setrlimit_closest(RLIMIT_NOFILE, &RLIMIT_MAKE_CONST(16384));
+
+ switch (arg_action) {
+
+ case ACTION_NEW_ID128:
+ r = generate_new_id128();
+ goto finish;
+
+ case ACTION_SETUP_KEYS:
+ r = setup_keys();
+ goto finish;
+
+ case ACTION_LIST_CATALOG:
+ case ACTION_DUMP_CATALOG:
+ case ACTION_UPDATE_CATALOG: {
+ _cleanup_free_ char *database;
+
+ database = path_join(arg_root, CATALOG_DATABASE, NULL);
+ if (!database) {
+ r = log_oom();
+ goto finish;
+ }
+
+ if (arg_action == ACTION_UPDATE_CATALOG) {
+ r = catalog_update(database, arg_root, catalog_file_dirs);
+ if (r < 0)
+ log_error_errno(r, "Failed to list catalog: %m");
+ } else {
+ bool oneline = arg_action == ACTION_LIST_CATALOG;
+
+ pager_open(arg_no_pager, arg_pager_end);
+
+ if (optind < argc)
+ r = catalog_list_items(stdout, database, oneline, argv + optind);
+ else
+ r = catalog_list(stdout, database, oneline);
+ if (r < 0)
+ log_error_errno(r, "Failed to list catalog: %m");
+ }
+
+ goto finish;
+ }
+
+ case ACTION_FLUSH:
+ r = flush_to_var();
+ goto finish;
+
+ case ACTION_SYNC:
+ r = sync_journal();
+ goto finish;
+
+ case ACTION_ROTATE:
+ r = rotate();
+ goto finish;
+
+ case ACTION_SHOW:
+ case ACTION_PRINT_HEADER:
+ case ACTION_VERIFY:
+ case ACTION_DISK_USAGE:
+ case ACTION_LIST_BOOTS:
+ case ACTION_VACUUM:
+ case ACTION_LIST_FIELDS:
+ case ACTION_LIST_FIELD_NAMES:
+ /* These ones require access to the journal files, continue below. */
+ break;
+
+ default:
+ assert_not_reached("Unknown action");
+ }
+
+ if (arg_directory)
+ r = sd_journal_open_directory(&j, arg_directory, arg_journal_type);
+ else if (arg_file_stdin) {
+ int ifd = STDIN_FILENO;
+ r = sd_journal_open_files_fd(&j, &ifd, 1, 0);
+ } else if (arg_file)
+ r = sd_journal_open_files(&j, (const char**) arg_file, 0);
+ else if (arg_machine) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int fd;
+
+ if (geteuid() != 0) {
+ /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of
+ * the container, thus we need root privileges to override them. */
+ log_error("Using the --machine= switch requires root privileges.");
+ r = -EPERM;
+ goto finish;
+ }
+
+ r = sd_bus_open_system(&bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open system bus: %m");
+ goto finish;
+ }
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.machine1",
+ "/org/freedesktop/machine1",
+ "org.freedesktop.machine1.Manager",
+ "OpenMachineRootDirectory",
+ &error,
+ &reply,
+ "s", arg_machine);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open root directory: %s", bus_error_message(&error, r));
+ goto finish;
+ }
+
+ r = sd_bus_message_read(reply, "h", &fd);
+ if (r < 0) {
+ bus_log_parse_error(r);
+ goto finish;
+ }
+
+ fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0) {
+ r = log_error_errno(errno, "Failed to duplicate file descriptor: %m");
+ goto finish;
+ }
+
+ r = sd_journal_open_directory_fd(&j, fd, SD_JOURNAL_OS_ROOT);
+ if (r < 0)
+ safe_close(fd);
+ } else
+ r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal");
+ goto finish;
+ }
+
+ r = access_check(j);
+ if (r < 0)
+ goto finish;
+
+ switch (arg_action) {
+
+ case ACTION_NEW_ID128:
+ case ACTION_SETUP_KEYS:
+ case ACTION_LIST_CATALOG:
+ case ACTION_DUMP_CATALOG:
+ case ACTION_UPDATE_CATALOG:
+ case ACTION_FLUSH:
+ case ACTION_SYNC:
+ case ACTION_ROTATE:
+ assert_not_reached("Unexpected action.");
+
+ case ACTION_PRINT_HEADER:
+ journal_print_header(j);
+ r = 0;
+ goto finish;
+
+ case ACTION_VERIFY:
+ r = verify(j);
+ goto finish;
+
+ case ACTION_DISK_USAGE: {
+ uint64_t bytes = 0;
+ char sbytes[FORMAT_BYTES_MAX];
+
+ r = sd_journal_get_usage(j, &bytes);
+ if (r < 0)
+ goto finish;
+
+ printf("Archived and active journals take up %s on disk.\n",
+ format_bytes(sbytes, sizeof(sbytes), bytes));
+ goto finish;
+ }
+
+ case ACTION_LIST_BOOTS:
+ r = list_boots(j);
+ goto finish;
+
+ case ACTION_VACUUM: {
+ Directory *d;
+ Iterator i;
+
+ HASHMAP_FOREACH(d, j->directories_by_path, i) {
+ int q;
+
+ if (d->is_root)
+ continue;
+
+ q = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, true);
+ if (q < 0) {
+ log_error_errno(q, "Failed to vacuum %s: %m", d->path);
+ r = q;
+ }
+ }
+
+ goto finish;
+ }
+
+ case ACTION_LIST_FIELD_NAMES: {
+ const char *field;
+
+ SD_JOURNAL_FOREACH_FIELD(j, field) {
+ printf("%s\n", field);
+ n_shown++;
+ }
+
+ r = 0;
+ goto finish;
+ }
+
+ case ACTION_SHOW:
+ case ACTION_LIST_FIELDS:
+ break;
+
+ default:
+ assert_not_reached("Unknown action");
+ }
+
+ if (arg_boot_offset != 0 &&
+ sd_journal_has_runtime_files(j) > 0 &&
+ sd_journal_has_persistent_files(j) == 0) {
+ log_info("Specifying boot ID has no effect, no persistent journal was found");
+ r = 0;
+ goto finish;
+ }
+ /* add_boot() must be called first!
+ * It may need to seek the journal to find parent boot IDs. */
+ r = add_boot(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_dmesg(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_units(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add filter for units: %m");
+ goto finish;
+ }
+
+ r = add_syslog_identifier(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add filter for syslog identifiers: %m");
+ goto finish;
+ }
+
+ r = add_priorities(j);
+ if (r < 0)
+ goto finish;
+
+ r = add_matches(j, argv + optind);
+ if (r < 0)
+ goto finish;
+
+ if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) {
+ _cleanup_free_ char *filter;
+
+ filter = journal_make_match_string(j);
+ if (!filter)
+ return log_oom();
+
+ log_debug("Journal filter: %s", filter);
+ }
+
+ if (arg_action == ACTION_LIST_FIELDS) {
+ const void *data;
+ size_t size;
+
+ assert(arg_field);
+
+ r = sd_journal_set_data_threshold(j, 0);
+ if (r < 0) {
+ log_error_errno(r, "Failed to unset data size threshold: %m");
+ goto finish;
+ }
+
+ r = sd_journal_query_unique(j, arg_field);
+ if (r < 0) {
+ log_error_errno(r, "Failed to query unique data objects: %m");
+ goto finish;
+ }
+
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, size) {
+ const void *eq;
+
+ if (arg_lines >= 0 && n_shown >= arg_lines)
+ break;
+
+ eq = memchr(data, '=', size);
+ if (eq)
+ printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1);
+ else
+ printf("%.*s\n", (int) size, (const char*) data);
+
+ n_shown++;
+ }
+
+ r = 0;
+ goto finish;
+ }
+
+ /* Opening the fd now means the first sd_journal_wait() will actually wait */
+ if (arg_follow) {
+ r = sd_journal_get_fd(j);
+ if (r == -EMEDIUMTYPE) {
+ log_error_errno(r, "The --follow switch is not supported in conjunction with reading from STDIN.");
+ goto finish;
+ }
+ if (r < 0) {
+ log_error_errno(r, "Failed to get journal fd: %m");
+ goto finish;
+ }
+ }
+
+ if (arg_cursor || arg_after_cursor) {
+ r = sd_journal_seek_cursor(j, arg_cursor ?: arg_after_cursor);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to cursor: %m");
+ goto finish;
+ }
+
+ if (!arg_reverse)
+ r = sd_journal_next_skip(j, 1 + !!arg_after_cursor);
+ else
+ r = sd_journal_previous_skip(j, 1 + !!arg_after_cursor);
+
+ if (arg_after_cursor && r < 2) {
+ /* We couldn't find the next entry after the cursor. */
+ if (arg_follow)
+ need_seek = true;
+ else
+ arg_lines = 0;
+ }
+
+ } else if (arg_since_set && !arg_reverse) {
+ r = sd_journal_seek_realtime_usec(j, arg_since);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to date: %m");
+ goto finish;
+ }
+ r = sd_journal_next(j);
+
+ } else if (arg_until_set && arg_reverse) {
+ r = sd_journal_seek_realtime_usec(j, arg_until);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to date: %m");
+ goto finish;
+ }
+ r = sd_journal_previous(j);
+
+ } else if (arg_lines >= 0) {
+ r = sd_journal_seek_tail(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to tail: %m");
+ goto finish;
+ }
+
+ r = sd_journal_previous_skip(j, arg_lines);
+
+ } else if (arg_reverse) {
+ r = sd_journal_seek_tail(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to tail: %m");
+ goto finish;
+ }
+
+ r = sd_journal_previous(j);
+
+ } else {
+ r = sd_journal_seek_head(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to seek to head: %m");
+ goto finish;
+ }
+
+ r = sd_journal_next(j);
+ }
+
+ if (r < 0) {
+ log_error_errno(r, "Failed to iterate through journal: %m");
+ goto finish;
+ }
+ if (r == 0) {
+ if (arg_follow)
+ need_seek = true;
+ else {
+ if (!arg_quiet)
+ printf("-- No entries --\n");
+ goto finish;
+ }
+ }
+
+ if (!arg_follow)
+ pager_open(arg_no_pager, arg_pager_end);
+
+ if (!arg_quiet) {
+ usec_t start, end;
+ char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
+
+ r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get cutoff: %m");
+ goto finish;
+ }
+
+ if (r > 0) {
+ if (arg_follow)
+ printf("-- Logs begin at %s. --\n",
+ format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start));
+ else
+ printf("-- Logs begin at %s, end at %s. --\n",
+ format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start),
+ format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end));
+ }
+ }
+
+ for (;;) {
+ while (arg_lines < 0 || n_shown < arg_lines || (arg_follow && !first_line)) {
+ int flags;
+
+ if (need_seek) {
+ if (!arg_reverse)
+ r = sd_journal_next(j);
+ else
+ r = sd_journal_previous(j);
+ if (r < 0) {
+ log_error_errno(r, "Failed to iterate through journal: %m");
+ goto finish;
+ }
+ if (r == 0)
+ break;
+ }
+
+ if (arg_until_set && !arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to determine timestamp: %m");
+ goto finish;
+ }
+ if (usec > arg_until)
+ goto finish;
+ }
+
+ if (arg_since_set && arg_reverse) {
+ usec_t usec;
+
+ r = sd_journal_get_realtime_usec(j, &usec);
+ if (r < 0) {
+ log_error_errno(r, "Failed to determine timestamp: %m");
+ goto finish;
+ }
+ if (usec < arg_since)
+ goto finish;
+ }
+
+ if (!arg_merge && !arg_quiet) {
+ sd_id128_t boot_id;
+
+ r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
+ if (r >= 0) {
+ if (previous_boot_id_valid &&
+ !sd_id128_equal(boot_id, previous_boot_id))
+ printf("%s-- Reboot --%s\n",
+ ansi_highlight(), ansi_normal());
+
+ previous_boot_id = boot_id;
+ previous_boot_id_valid = true;
+ }
+ }
+
+ flags =
+ arg_all * OUTPUT_SHOW_ALL |
+ arg_full * OUTPUT_FULL_WIDTH |
+ colors_enabled() * OUTPUT_COLOR |
+ arg_catalog * OUTPUT_CATALOG |
+ arg_utc * OUTPUT_UTC |
+ arg_no_hostname * OUTPUT_NO_HOSTNAME;
+
+ r = output_journal(stdout, j, arg_output, 0, flags, &ellipsized);
+ need_seek = true;
+ if (r == -EADDRNOTAVAIL)
+ break;
+ else if (r < 0 || ferror(stdout))
+ goto finish;
+
+ n_shown++;
+ }
+
+ if (!arg_follow) {
+ if (arg_show_cursor) {
+ _cleanup_free_ char *cursor = NULL;
+
+ r = sd_journal_get_cursor(j, &cursor);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ log_error_errno(r, "Failed to get cursor: %m");
+ else if (r >= 0)
+ printf("-- cursor: %s\n", cursor);
+ }
+
+ break;
+ }
+
+ r = sd_journal_wait(j, (uint64_t) -1);
+ if (r < 0) {
+ log_error_errno(r, "Couldn't wait for journal event: %m");
+ goto finish;
+ }
+
+ first_line = false;
+ }
+
+finish:
+ pager_close();
+
+ strv_free(arg_file);
+
+ strv_free(arg_syslog_identifier);
+ strv_free(arg_system_units);
+ strv_free(arg_user_units);
+
+ free(arg_root);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-journal/journalctl/journalctl.completion.bash b/src/grp-journal/journalctl/journalctl.completion.bash
new file mode 100644
index 0000000000..7c8a9ce361
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.completion.bash
@@ -0,0 +1,133 @@
+# journalctl(1) completion -*- shell-script -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010 Ran Benita
+#
+# 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
+# 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/>.
+
+__contains_word () {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+__get_machines() {
+ local a b
+ (machinectl list-images --no-legend --no-pager; machinectl list --no-legend --no-pager; echo ".host") | \
+ { while read a b; do echo " $a"; done; } | sort -u;
+}
+
+__journal_fields=(MESSAGE{,_ID} PRIORITY CODE_{FILE,LINE,FUNC}
+ ERRNO SYSLOG_{FACILITY,IDENTIFIER,PID} COREDUMP_EXE
+ _{P,U,G}ID _COMM _EXE _CMDLINE
+ _CAP_EFFECTIVE _AUDIT_{SESSION,LOGINUID}
+ _SYSTEMD_{CGROUP,SESSION,{,USER_}UNIT,OWNER_UID,SLICE}
+ _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP
+ _{BOOT,MACHINE}_ID _HOSTNAME _TRANSPORT
+ _KERNEL_{DEVICE,SUBSYSTEM}
+ _UDEV_{SYSNAME,DEVNODE,DEVLINK}
+ __CURSOR __{REALTIME,MONOTONIC}_TIMESTAMP)
+
+__syslog_priorities=(emerg alert crit err warning notice info debug)
+
+_journalctl() {
+ local field_vals= cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+ local -A OPTS=(
+ [STANDALONE]='-a --all --full --system --user
+ --disk-usage -f --follow --header
+ -h --help -l --local --new-id128 -m --merge --no-pager
+ --no-tail -q --quiet --setup-keys --this-boot --verify
+ --version --list-catalog --update-catalog --list-boots
+ --show-cursor --dmesg -k --pager-end -e -r --reverse
+ --utc -x --catalog --no-full --force --dump-catalog
+ --flush --rotate --sync'
+ [ARG]='-b --boot --this-boot -D --directory --file -F --field
+ -M --machine -o --output -u --unit --user-unit -p --priority
+ --vacuum-size --vacuum-time'
+ [ARGUNKNOWN]='-c --cursor --interval -n --lines -S --since -U --until
+ --after-cursor --verify-key -t --identifier
+ --root'
+ )
+
+ if __contains_word "$prev" ${OPTS[ARG]} ${OPTS[ARGUNKNOWN]}; then
+ case $prev in
+ --boot|--this-boot|-b)
+ comps=$(journalctl -F '_BOOT_ID' 2>/dev/null)
+ ;;
+ --directory|-D)
+ comps=$(compgen -d -- "$cur")
+ compopt -o filenames
+ ;;
+ --file)
+ comps=$(compgen -f -- "$cur")
+ compopt -o filenames
+ ;;
+ --output|-o)
+ comps='short short-iso short-precise short-monotonic verbose export json json-pretty json-sse cat'
+ ;;
+ --field|-F)
+ comps=${__journal_fields[*]}
+ ;;
+ --machine|-M)
+ comps=$( __get_machines )
+ ;;
+ --priority|-p)
+ comps=${__syslog_priorities[*]}
+ ;;
+ --unit|-u)
+ comps=$(journalctl -F '_SYSTEMD_UNIT' 2>/dev/null)
+ ;;
+ --user-unit)
+ comps=$(journalctl -F '_SYSTEMD_USER_UNIT' 2>/dev/null)
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
+ if [[ $cur = -* ]]; then
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+ return 0
+ elif [[ $cur = *=* ]]; then
+ mapfile -t field_vals < <(journalctl -F "${prev%=}" 2>/dev/null)
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "${cur#=}") )
+ elif [[ $cur = /dev* ]]; then
+ compopt -o filenames
+ COMPREPLY=( $(compgen -f -- "${cur}") )
+ elif [[ $cur = /* ]]; then
+ # Append /dev/ to the list of completions, so that
+ # after typing /<TAB><TAB> the user sees /dev/ as one
+ # of the alternatives. Later on the rule above will
+ # take care of showing device files in /dev/.
+ mapfile -t field_vals < <(journalctl -F "_EXE" 2>/dev/null; echo '/dev/')
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "${cur}") )
+ if [[ "${COMPREPLY[@]}" = '/dev/' ]]; then
+ compopt -o filenames
+ COMPREPLY=( $(compgen -f -- "${cur}") )
+ fi
+ elif [[ $prev = '=' ]]; then
+ mapfile -t field_vals < <(journalctl -F "${COMP_WORDS[COMP_CWORD-2]}" 2>/dev/null)
+ COMPREPLY=( $(compgen -W '${field_vals[*]}' -- "$cur") )
+ else
+ compopt -o nospace
+ COMPREPLY=( $(compgen -W '${__journal_fields[*]}' -S= -- "$cur") )
+ fi
+}
+
+complete -F _journalctl journalctl
diff --git a/src/grp-journal/journalctl/journalctl.completion.zsh b/src/grp-journal/journalctl/journalctl.completion.zsh
new file mode 100644
index 0000000000..2bee23b6d3
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.completion.zsh
@@ -0,0 +1,98 @@
+#compdef journalctl
+
+_list_fields() {
+ local -a journal_fields
+ journal_fields=(MESSAGE{,_ID} PRIORITY CODE_{FILE,LINE,FUNC}
+ ERRNO SYSLOG_{FACILITY,IDENTIFIER,PID}
+ _{P,U,G}ID _COMM _EXE _CMDLINE
+ _AUDIT_{SESSION,LOGINUID}
+ _SYSTEMD_{CGROUP,SESSION,UNIT,OWNER_UID}
+ _SYSTEMD_USER_UNIT USER_UNIT
+ _SELINUX_CONTEXT _SOURCE_REALTIME_TIMESTAMP
+ _{BOOT,MACHINE}_ID _HOSTNAME _TRANSPORT
+ _KERNEL_{DEVICE,SUBSYSTEM}
+ _UDEV_{SYSNAME,DEVNODE,DEVLINK}
+ __CURSOR __{REALTIME,MONOTONIC}_TIMESTAMP)
+ case $_jrnl_none in
+ yes) _values -s '=' 'possible fields' \
+ "${journal_fields[@]}:value:_journal_fields ${words[CURRENT]%%=*}" ;;
+ *) _describe 'possible fields' journal_fields ;;
+ esac
+}
+
+_journal_none() {
+ local -a _commands _files _jrnl_none
+ # Setting use-cache will slow this down considerably
+ _commands=( ${"$(_call_program commands "$service" -F _EXE 2>/dev/null)"} )
+ _jrnl_none='yes'
+ _alternative : \
+ 'files:/dev files:_files -W /dev -P /dev/' \
+ "commands:commands:($_commands[@])" \
+ 'fields:fields:_list_fields'
+}
+
+_journal_fields() {
+ local -a _fields cmd
+ cmd=("journalctl" "-F ${@[-1]}" "2>/dev/null" )
+ _fields=$(_call_program fields $cmd[@])
+ _fields=${_fields//'\'/'\\'}
+ _fields=${_fields//':'/'\:'}
+ _fields=( ${(f)_fields} )
+ typeset -U _fields
+ _describe 'possible values' _fields
+}
+
+_journal_boots() {
+ local -a _bootid _previousboots
+ _bootid=( ${(f)"$(_call_program bootid "$service -F _BOOT_ID")"} )
+ _previousboots=( -{1..${#_bootid}} )
+ _alternative : \
+ "offsets:boot offsets:compadd -a '_previousboots[1,-2]'" \
+ "bootid:boot ids:compadd -a _bootid"
+}
+
+_arguments -s \
+ {-h,--help}'[Show this help]' \
+ '--version[Show package version]' \
+ '--no-pager[Do not pipe output into a pager]' \
+ {-l,--full}'[Show long fields in full]' \
+ {-a,--all}'[Show all fields, including long and unprintable]' \
+ {-f,--follow}'[Follow journal]' \
+ {-e,--pager-end}'[Jump to the end of the journal in the pager]' \
+ {-n+,--lines=}'[Number of journal entries to show]:integer' \
+ '--no-tail[Show all lines, even in follow mode]' \
+ {-r,--reverse}'[Reverse output]' \
+ {-o+,--output=}'[Change journal output mode]:output modes:_sd_outputmodes' \
+ {-x,--catalog}'[Show explanatory texts with each log line]' \
+ {-q,--quiet}"[Don't show privilege warning]" \
+ {-m,--merge}'[Show entries from all available journals]' \
+ {-b+,--boot=}'[Show data only from the specified boot or offset]::boot id or offset:_journal_boots' \
+ '--list-boots[List boots ordered by time]' \
+ {-k,--dmesg}'[Show only kernel messages from the current boot]' \
+ {-u+,--unit=}'[Show data only from the specified unit]:units:_journal_fields _SYSTEMD_UNIT' \
+ '--user-unit=[Show data only from the specified user session unit]:units:_journal_fields USER_UNIT' \
+ {-p+,--priority=}'[Show only messages within the specified priority range]:priority:_journal_fields PRIORITY' \
+ {-t+,--identifier=}'[Show only messages with the specified syslog identifier]:identifier:_journal_fields SYSLOG_IDENTIFIER' \
+ {-c+,--cursor=}'[Start showing entries from the specified cursor]:cursors:_journal_fields __CURSORS' \
+ '--after-cursor=[Start showing entries from after the specified cursor]:cursors:_journal_fields __CURSORS' \
+ '--since=[Start showing entries on or newer than the specified date]:YYYY-MM-DD HH\:MM\:SS' \
+ '--until=[Stop showing entries on or older than the specified date]:YYYY-MM-DD HH\:MM\:SS' \
+ {-F,--field=}'[List all values a certain field takes]:Fields:_list_fields' \
+ '--system[Show system and kernel messages]' \
+ '--user[Show messages from user services]' \
+ {-M+,--machine=}'[Operate on local container]:machines:_sd_machines' \
+ {-D+,--directory=}'[Show journal files from directory]:directories:_directories' \
+ '--file=[Operate on specified journal files]:file:_files' \
+ '--root=[Operate on catalog hierarchy under specified directory]:directories:_directories' \
+ '--new-id128[Generate a new 128 Bit ID]' \
+ '--header[Show journal header information]' \
+ '--disk-usage[Show total disk usage]' \
+ '--list-catalog[List messages in catalog]' \
+ '--dump-catalog[Dump messages in catalog]' \
+ '--update-catalog[Update binary catalog database]' \
+ '--setup-keys[Generate a new FSS key pair]' \
+ '--force[Force recreation of the FSS keys]' \
+ '--interval=[Time interval for changing the FSS sealing key]:time interval' \
+ '--verify[Verify journal file consistency]' \
+ '--verify-key=[Specify FSS verification key]:FSS key' \
+ '*::default: _journal_none'
diff --git a/src/grp-journal/journalctl/journalctl.xml b/src/grp-journal/journalctl/journalctl.xml
new file mode 100644
index 0000000000..3efe6ef62a
--- /dev/null
+++ b/src/grp-journal/journalctl/journalctl.xml
@@ -0,0 +1,914 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!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 2012 Lennart Poettering
+
+ 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="journalctl"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>journalctl</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart@poettering.net</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>journalctl</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>journalctl</refname>
+ <refpurpose>Query the systemd journal</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>journalctl</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="repeat">MATCHES</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>journalctl</command> may be used to query the
+ contents of the
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ journal as written by
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+
+ <para>If called without parameters, it will show the full
+ contents of the journal, starting with the oldest entry
+ collected.</para>
+
+ <para>If one or more match arguments are passed, the output is
+ filtered accordingly. A match is in the format
+ <literal>FIELD=VALUE</literal>,
+ e.g. <literal>_SYSTEMD_UNIT=httpd.service</literal>, referring
+ to the components of a structured journal entry. See
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ for a list of well-known fields. If multiple matches are
+ specified matching different fields, the log entries are
+ filtered by both, i.e. the resulting output will show only
+ entries matching all the specified matches of this kind. If two
+ matches apply to the same field, then they are automatically
+ matched as alternatives, i.e. the resulting output will show
+ entries matching any of the specified matches for the same
+ field. Finally, the character <literal>+</literal> may appear
+ as a separate word between other terms on the command line. This
+ causes all matches before and after to be combined in a
+ disjunction (i.e. logical OR).</para>
+
+ <para>As shortcuts for a few types of field/value matches, file
+ paths may be specified. If a file path refers to an executable
+ file, this is equivalent to an <literal>_EXE=</literal> match
+ for the canonicalized binary path. Similarly, if a path refers
+ to a device node then match is added for the kernel name of the
+ device (<literal>_KERNEL_DEVICE=</literal>). Also, matches for the
+ kernel names of all the parent devices are added automatically.
+ Device node paths are not stable across reboots, therefore match
+ for the current boot id (<literal>_BOOT_ID=</literal>) is
+ always added as well. Note that only the log entries for
+ the existing device nodes maybe queried by providing path to
+ the device node.</para>
+
+ <para>Additional constraints may be added using options
+ <option>--boot</option>, <option>--unit=</option>, etc., to
+ further limit what entries will be shown (logical AND).</para>
+
+ <para>Output is interleaved from all accessible journal files,
+ whether they are rotated or currently being written, and
+ regardless of whether they belong to the system itself or are
+ accessible user journals.</para>
+
+ <para>The set of journal files which will be used can be
+ modified using the <option>--user</option>,
+ <option>--system</option>, <option>--directory</option>, and
+ <option>--file</option> options, see below.</para>
+
+ <para>All users are granted access to their private per-user
+ journals. However, by default, only root and users who are
+ members of a few special groups are granted access to the system
+ journal and the journals of other users. Members of the groups
+ <literal>systemd-journal</literal>, <literal>adm</literal>, and
+ <literal>wheel</literal> can read all journal files. Note
+ that the two latter groups traditionally have additional
+ privileges specified by the distribution. Members of the
+ <literal>wheel</literal> group can often perform administrative
+ tasks.</para>
+
+ <para>The output is paged through <command>less</command> by
+ default, and long lines are "truncated" to screen width. The
+ hidden part can be viewed by using the left-arrow and
+ right-arrow keys. Paging can be disabled; see the
+ <option>--no-pager</option> option and the "Environment" section
+ below.</para>
+
+ <para>When outputting to a tty, lines are colored according to
+ priority: lines of level ERROR and higher are colored red; lines
+ of level NOTICE and higher are highlighted; other lines are
+ displayed normally.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--no-full</option></term>
+ <term><option>--full</option></term>
+ <term><option>-l</option></term>
+
+ <listitem><para>Ellipsize fields when they do not fit in
+ available columns. The default is to show full fields,
+ allowing them to wrap or be truncated by the pager, if one
+ is used.</para>
+
+ <para>The old options
+ <option>-l</option>/<option>--full</option> are not useful
+ anymore, except to undo <option>--no-full</option>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--all</option></term>
+
+ <listitem><para>Show all fields in full, even if they
+ include unprintable characters or are very
+ long.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-f</option></term>
+ <term><option>--follow</option></term>
+
+ <listitem><para>Show only the most recent journal entries,
+ and continuously print new entries as they are appended to
+ the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-e</option></term>
+ <term><option>--pager-end</option></term>
+
+ <listitem><para>Immediately jump to the end of the journal
+ inside the implied pager tool. This implies
+ <option>-n1000</option> to guarantee that the pager will not
+ buffer logs of unbounded size. This may be overridden with
+ an explicit <option>-n</option> with some other numeric
+ value, while <option>-nall</option> will disable this cap.
+ Note that this option is only supported for the
+ <citerefentry project='man-pages'><refentrytitle>less</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ pager.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--lines=</option></term>
+
+ <listitem><para>Show the most recent journal events and
+ limit the number of events shown. If
+ <option>--follow</option> is used, this option is
+ implied. The argument is a positive integer or
+ <literal>all</literal> to disable line limiting. The default
+ value is 10 if no argument is given.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-tail</option></term>
+
+ <listitem><para>Show all stored output lines, even in follow
+ mode. Undoes the effect of <option>--lines=</option>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--reverse</option></term>
+
+ <listitem><para>Reverse output so that the newest entries
+ are displayed first.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-o</option></term>
+ <term><option>--output=</option></term>
+
+ <listitem><para>Controls the formatting of the journal
+ entries that are shown. Takes one of the following
+ options:</para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>short</option>
+ </term>
+ <listitem>
+ <para>is the default and generates an output that is
+ mostly identical to the formatting of classic syslog
+ files, showing one line per journal entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-iso</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows ISO 8601 wallclock
+ timestamps.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-precise</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows timestamps with full
+ microsecond precision.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-monotonic</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows monotonic timestamps
+ instead of wallclock timestamps.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>short-unix</option>
+ </term>
+ <listitem>
+ <para>is very similar, but shows seconds passed since January 1st 1970 UTC instead of wallclock
+ timestamps ("UNIX time"). The time is shown with microsecond accuracy.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>verbose</option>
+ </term>
+ <listitem>
+ <para>shows the full-structured entry items with all
+ fields.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>export</option>
+ </term>
+ <listitem>
+ <para>serializes the journal into a binary (but mostly
+ text-based) stream suitable for backups and network
+ transfer (see
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/export">Journal Export Format</ulink>
+ for more information).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>json</option>
+ </term>
+ <listitem>
+ <para>formats entries as JSON data structures, one per
+ line (see
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/json">Journal JSON Format</ulink>
+ for more information).</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>json-pretty</option>
+ </term>
+ <listitem>
+ <para>formats entries as JSON data structures, but
+ formats them in multiple lines in order to make them
+ more readable by humans.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>json-sse</option>
+ </term>
+ <listitem>
+ <para>formats entries as JSON data structures, but wraps
+ them in a format suitable for
+ <ulink url="https://developer.mozilla.org/en-US/docs/Server-sent_events/Using_server-sent_events">Server-Sent Events</ulink>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>cat</option>
+ </term>
+ <listitem>
+ <para>generates a very terse output, only showing the
+ actual message of each journal entry with no metadata,
+ not even a timestamp.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--utc</option></term>
+
+ <listitem><para>Express time in Coordinated Universal Time
+ (UTC).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-hostname</option></term>
+
+ <listitem><para>Don't show the hostname field of log messages originating from the local host. This switch only
+ has an effect on the <option>short</option> family of output modes (see above).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-x</option></term>
+ <term><option>--catalog</option></term>
+
+ <listitem><para>Augment log lines with explanation texts from
+ the message catalog. This will add explanatory help texts to
+ log messages in the output where this is available. These
+ short help texts will explain the context of an error or log
+ event, possible solutions, as well as pointers to support
+ forums, developer documentation, and any other relevant
+ manuals. Note that help texts are not available for all
+ messages, but only for selected ones. For more information on
+ the message catalog, please refer to the
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/catalog">Message Catalog Developer Documentation</ulink>.</para>
+
+ <para>Note: when attaching <command>journalctl</command>
+ output to bug reports, please do <emphasis>not</emphasis> use
+ <option>-x</option>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--quiet</option></term>
+
+ <listitem><para>Suppresses all info messages
+ (i.e. "-- Logs begin at ...", "-- Reboot --"),
+ any warning messages regarding
+ inaccessible system journals when run as a normal
+ user.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-m</option></term>
+ <term><option>--merge</option></term>
+
+ <listitem><para>Show entries interleaved from all available
+ journals, including remote ones.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-b <optional><replaceable>ID</replaceable></optional><optional><replaceable>±offset</replaceable></optional></option></term>
+ <term><option>--boot=<optional><replaceable>ID</replaceable></optional><optional><replaceable>±offset</replaceable></optional></option></term>
+
+ <listitem><para>Show messages from a specific boot. This will
+ add a match for <literal>_BOOT_ID=</literal>.</para>
+
+ <para>The argument may be empty, in which case logs for the
+ current boot will be shown.</para>
+
+ <para>If the boot ID is omitted, a positive
+ <replaceable>offset</replaceable> will look up the boots
+ starting from the beginning of the journal, and an
+ equal-or-less-than zero <replaceable>offset</replaceable> will
+ look up boots starting from the end of the journal. Thus,
+ <constant>1</constant> means the first boot found in the
+ journal in chronological order, <constant>2</constant> the
+ second and so on; while <constant>-0</constant> is the last
+ boot, <constant>-1</constant> the boot before last, and so
+ on. An empty <replaceable>offset</replaceable> is equivalent
+ to specifying <constant>-0</constant>, except when the current
+ boot is not the last boot (e.g. because
+ <option>--directory</option> was specified to look at logs
+ from a different machine).</para>
+
+ <para>If the 32-character <replaceable>ID</replaceable> is
+ specified, it may optionally be followed by
+ <replaceable>offset</replaceable> which identifies the boot
+ relative to the one given by boot
+ <replaceable>ID</replaceable>. Negative values mean earlier
+ boots and positive values mean later boots. If
+ <replaceable>offset</replaceable> is not specified, a value of
+ zero is assumed, and the logs for the boot given by
+ <replaceable>ID</replaceable> are shown.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-boots</option></term>
+
+ <listitem><para>Show a tabular list of boot numbers (relative to
+ the current boot), their IDs, and the timestamps of the first
+ and last message pertaining to the boot.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-k</option></term>
+ <term><option>--dmesg</option></term>
+
+ <listitem><para>Show only kernel messages. This implies
+ <option>-b</option> and adds the match
+ <literal>_TRANSPORT=kernel</literal>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--identifier=<replaceable>SYSLOG_IDENTIFIER</replaceable></option></term>
+
+ <listitem><para>Show messages for the specified syslog
+ identifier
+ <replaceable>SYSLOG_IDENTIFIER</replaceable>.</para>
+
+ <para>This parameter can be specified multiple
+ times.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-u</option></term>
+ <term><option>--unit=<replaceable>UNIT</replaceable>|<replaceable>PATTERN</replaceable></option></term>
+
+ <listitem><para>Show messages for the specified systemd unit
+ <replaceable>UNIT</replaceable> (such as a service unit), or
+ for any of the units matched by
+ <replaceable>PATTERN</replaceable>. If a pattern is
+ specified, a list of unit names found in the journal is
+ compared with the specified pattern and all that match are
+ used. For each unit name, a match is added for messages from
+ the unit
+ (<literal>_SYSTEMD_UNIT=<replaceable>UNIT</replaceable></literal>),
+ along with additional matches for messages from systemd and
+ messages about coredumps for the specified unit.</para>
+
+ <para>This parameter can be specified multiple times.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--user-unit=</option></term>
+
+ <listitem><para>Show messages for the specified user session
+ unit. This will add a match for messages from the unit
+ (<literal>_SYSTEMD_USER_UNIT=</literal> and
+ <literal>_UID=</literal>) and additional matches for messages
+ from session systemd and messages about coredumps for the
+ specified unit.</para>
+
+ <para>This parameter can be specified multiple times.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--priority=</option></term>
+
+ <listitem><para>Filter output by message priorities or
+ priority ranges. Takes either a single numeric or textual log
+ level (i.e. between 0/<literal>emerg</literal> and
+ 7/<literal>debug</literal>), or a range of numeric/text log
+ levels in the form FROM..TO. The log levels are the usual
+ syslog log levels as documented in
+ <citerefentry project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+ i.e. <literal>emerg</literal> (0),
+ <literal>alert</literal> (1), <literal>crit</literal> (2),
+ <literal>err</literal> (3), <literal>warning</literal> (4),
+ <literal>notice</literal> (5), <literal>info</literal> (6),
+ <literal>debug</literal> (7). If a single log level is
+ specified, all messages with this log level or a lower (hence
+ more important) log level are shown. If a range is specified,
+ all messages within the range are shown, including both the
+ start and the end value of the range. This will add
+ <literal>PRIORITY=</literal> matches for the specified
+ priorities.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--cursor=</option></term>
+
+ <listitem><para>Start showing entries from the location in the
+ journal specified by the passed cursor.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--after-cursor=</option></term>
+
+ <listitem><para>Start showing entries from the location in the
+ journal <emphasis>after</emphasis> the location specified by
+ the passed cursor. The cursor is shown when the
+ <option>--show-cursor</option> option is used.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--show-cursor</option></term>
+
+ <listitem><para>The cursor is shown after the last entry after
+ two dashes:</para>
+ <programlisting>-- cursor: s=0639...</programlisting>
+ <para>The format of the cursor is private
+ and subject to change.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--since=</option></term>
+ <term><option>-U</option></term>
+ <term><option>--until=</option></term>
+
+ <listitem><para>Start showing entries on or newer than the
+ specified date, or on or older than the specified date,
+ respectively. Date specifications should be of the format
+ <literal>2012-10-30 18:17:16</literal>. If the time part is
+ omitted, <literal>00:00:00</literal> is assumed. If only the
+ seconds component is omitted, <literal>:00</literal> is
+ assumed. If the date component is omitted, the current day is
+ assumed. Alternatively the strings
+ <literal>yesterday</literal>, <literal>today</literal>,
+ <literal>tomorrow</literal> are understood, which refer to
+ 00:00:00 of the day before the current day, the current day,
+ or the day after the current day,
+ respectively. <literal>now</literal> refers to the current
+ time. Finally, relative times may be specified, prefixed with
+ <literal>-</literal> or <literal>+</literal>, referring to
+ times before or after the current time, respectively. For complete
+ time and date specification, see
+ <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-F</option></term>
+ <term><option>--field=</option></term>
+
+ <listitem><para>Print all possible data values the specified
+ field can take in all entries of the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-N</option></term>
+ <term><option>--fields</option></term>
+
+ <listitem><para>Print all field names currently used in all entries of the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--system</option></term>
+ <term><option>--user</option></term>
+
+ <listitem><para>Show messages from system services and the
+ kernel (with <option>--system</option>). Show messages from
+ service of current user (with <option>--user</option>). If
+ neither is specified, show all messages that the user can see.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-M</option></term>
+ <term><option>--machine=</option></term>
+
+ <listitem><para>Show messages from a running, local
+ container. Specify a container name to connect to.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-D <replaceable>DIR</replaceable></option></term>
+ <term><option>--directory=<replaceable>DIR</replaceable></option></term>
+
+ <listitem><para>Takes a directory path as argument. If
+ specified, journalctl will operate on the specified journal
+ directory <replaceable>DIR</replaceable> instead of the
+ default runtime and system journal paths.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--file=<replaceable>GLOB</replaceable></option></term>
+
+ <listitem><para>Takes a file glob as an argument. If
+ specified, journalctl will operate on the specified journal
+ files matching <replaceable>GLOB</replaceable> instead of the
+ default runtime and system journal paths. May be specified
+ multiple times, in which case files will be suitably
+ interleaved.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--root=<replaceable>ROOT</replaceable></option></term>
+
+ <listitem><para>Takes a directory path as an argument. If
+ specified, journalctl will operate on catalog file hierarchy
+ underneath the specified directory instead of the root
+ directory (e.g. <option>--update-catalog</option> will create
+ <filename><replaceable>ROOT</replaceable>/var/lib/systemd/catalog/database</filename>).
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--new-id128</option></term>
+
+ <listitem><para>Instead of showing journal contents, generate
+ a new 128-bit ID suitable for identifying messages. This is
+ intended for usage by developers who need a new identifier for
+ a new message they introduce and want to make
+ recognizable. This will print the new ID in three different
+ formats which can be copied into source code or similar.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--header</option></term>
+
+ <listitem><para>Instead of showing journal contents, show
+ internal header information of the journal fields
+ accessed.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--disk-usage</option></term>
+
+ <listitem><para>Shows the current disk usage of all journal
+ files. This shows the sum of the disk usage of all archived
+ and active journal files.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--vacuum-size=</option></term>
+ <term><option>--vacuum-time=</option></term>
+ <term><option>--vacuum-files=</option></term>
+
+ <listitem><para>Removes archived journal files until the disk
+ space they use falls below the specified size (specified with
+ the usual <literal>K</literal>, <literal>M</literal>,
+ <literal>G</literal> and <literal>T</literal> suffixes), or all
+ archived journal files contain no data older than the specified
+ timespan (specified with the usual <literal>s</literal>,
+ <literal>m</literal>, <literal>h</literal>,
+ <literal>days</literal>, <literal>months</literal>,
+ <literal>weeks</literal> and <literal>years</literal> suffixes),
+ or no more than the specified number of separate journal files
+ remain. Note that running <option>--vacuum-size=</option> has
+ only an indirect effect on the output shown by
+ <option>--disk-usage</option>, as the latter includes active
+ journal files, while the vacuuming operation only operates
+ on archived journal files. Similarly,
+ <option>--vacuum-files=</option> might not actually reduce the
+ number of journal files to below the specified number, as it
+ will not remove active journal
+ files. <option>--vacuum-size=</option>,
+ <option>--vacuum-time=</option> and
+ <option>--vacuum-files=</option> may be combined in a single
+ invocation to enforce any combination of a size, a time and a
+ number of files limit on the archived journal
+ files. Specifying any of these three parameters as zero is
+ equivalent to not enforcing the specific limit, and is thus
+ redundant.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--list-catalog
+ <optional><replaceable>128-bit-ID...</replaceable></optional>
+ </option></term>
+
+ <listitem><para>List the contents of the message catalog as a
+ table of message IDs, plus their short description strings.
+ </para>
+
+ <para>If any <replaceable>128-bit-ID</replaceable>s are
+ specified, only those entries are shown.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--dump-catalog
+ <optional><replaceable>128-bit-ID...</replaceable></optional>
+ </option></term>
+
+ <listitem><para>Show the contents of the message catalog, with
+ entries separated by a line consisting of two dashes and the
+ ID (the format is the same as <filename>.catalog</filename>
+ files).</para>
+
+ <para>If any <replaceable>128-bit-ID</replaceable>s are
+ specified, only those entries are shown.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--update-catalog</option></term>
+
+ <listitem><para>Update the message catalog index. This command
+ needs to be executed each time new catalog files are
+ installed, removed, or updated to rebuild the binary catalog
+ index.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--setup-keys</option></term>
+
+ <listitem><para>Instead of showing journal contents, generate
+ a new key pair for Forward Secure Sealing (FSS). This will
+ generate a sealing key and a verification key. The sealing key
+ is stored in the journal data directory and shall remain on
+ the host. The verification key should be stored
+ externally. Refer to the <option>Seal=</option> option in
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for information on Forward Secure Sealing and for a link to a
+ refereed scholarly paper detailing the cryptographic theory it
+ is based on.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--force</option></term>
+
+ <listitem><para>When <option>--setup-keys</option> is passed
+ and Forward Secure Sealing (FSS) has already been configured,
+ recreate FSS keys.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--interval=</option></term>
+
+ <listitem><para>Specifies the change interval for the sealing
+ key when generating an FSS key pair with
+ <option>--setup-keys</option>. Shorter intervals increase CPU
+ consumption but shorten the time range of undetectable journal
+ alterations. Defaults to 15min.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--verify</option></term>
+
+ <listitem><para>Check the journal file for internal
+ consistency. If the file has been generated with FSS enabled and
+ the FSS verification key has been specified with
+ <option>--verify-key=</option>, authenticity of the journal file
+ is verified.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--verify-key=</option></term>
+
+ <listitem><para>Specifies the FSS verification key to use for
+ the <option>--verify</option> operation.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--sync</option></term>
+
+ <listitem><para>Asks the journal daemon to write all yet
+ unwritten journal data to the backing file system and
+ synchronize all journals. This call does not return until the
+ synchronization operation is complete. This command guarantees
+ that any log messages written before its invocation are safely
+ stored on disk at the time it returns.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--flush</option></term>
+
+ <listitem><para>Asks the journal daemon to flush any log data
+ stored in <filename>/run/log/journal</filename> into
+ <filename>/var/log/journal</filename>, if persistent storage
+ is enabled. This call does not return until the operation is
+ complete. Note that this call is idempotent: the data is only
+ flushed from <filename>/run/log/journal</filename> into
+ <filename>/var/log/journal</filename> once during system
+ runtime, and this command exits cleanly without executing any
+ operation if this has already has happened. This command
+ effectively guarantees that all data is flushed to
+ <filename>/var/log/journal</filename> at the time it
+ returns.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--rotate</option></term>
+
+ <listitem><para>Asks the journal daemon to rotate journal
+ files. This call does not return until the rotation operation
+ is complete.</para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ <xi:include href="standard-options.xml" xpointer="no-pager" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned; otherwise, a non-zero failure
+ code is returned.</para>
+ </refsect1>
+
+ <xi:include href="less-variables.xml" />
+
+ <refsect1>
+ <title>Examples</title>
+
+ <para>Without arguments, all collected logs are shown
+ unfiltered:</para>
+
+ <programlisting>journalctl</programlisting>
+
+ <para>With one match specified, all entries with a field matching
+ the expression are shown:</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service</programlisting>
+
+ <para>If two different fields are matched, only entries matching
+ both expressions at the same time are shown:</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097</programlisting>
+
+ <para>If two matches refer to the same field, all entries matching
+ either expression are shown:</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _SYSTEMD_UNIT=dbus.service</programlisting>
+
+ <para>If the separator <literal>+</literal> is used, two
+ expressions may be combined in a logical OR. The following will
+ show all messages from the Avahi service process with the PID
+ 28097 plus all messages from the D-Bus service (from any of its
+ processes):</para>
+
+ <programlisting>journalctl _SYSTEMD_UNIT=avahi-daemon.service _PID=28097 + _SYSTEMD_UNIT=dbus.service</programlisting>
+
+ <para>Show all logs generated by the D-Bus executable:</para>
+
+ <programlisting>journalctl /usr/bin/dbus-daemon</programlisting>
+
+ <para>Show all kernel logs from previous boot:</para>
+
+ <programlisting>journalctl -k -b -1</programlisting>
+
+ <para>Show a live log display from a system service
+ <filename>apache.service</filename>:</para>
+
+ <programlisting>journalctl -f -u apache</programlisting>
+
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>coredumpctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/grp-journal/journalctl/systemd-journal-catalog-update.service.in b/src/grp-journal/journalctl/systemd-journal-catalog-update.service.in
new file mode 100644
index 0000000000..6370dd478f
--- /dev/null
+++ b/src/grp-journal/journalctl/systemd-journal-catalog-update.service.in
@@ -0,0 +1,21 @@
+# 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=Rebuild Journal Catalog
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=local-fs.target
+Before=sysinit.target shutdown.target systemd-update-done.service
+ConditionNeedsUpdate=/etc
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/journalctl --update-catalog
+TimeoutSec=90s
diff --git a/src/grp-journal/journalctl/systemd-journal-flush.service.in b/src/grp-journal/journalctl/systemd-journal-flush.service.in
new file mode 100644
index 0000000000..a0a2e3fdb4
--- /dev/null
+++ b/src/grp-journal/journalctl/systemd-journal-flush.service.in
@@ -0,0 +1,22 @@
+# 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=Flush Journal to Persistent Storage
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Requires=systemd-journald.service
+After=systemd-journald.service
+After=systemd-remount-fs.service
+Before=systemd-user-sessions.service systemd-tmpfiles-setup.service
+RequiresMountsFor=/var/log/journal
+
+[Service]
+ExecStart=@rootbindir@/journalctl --flush
+Type=oneshot
+RemainAfterExit=yes
+TimeoutSec=90s
diff --git a/src/grp-journal/libjournal-core/.gitignore b/src/grp-journal/libjournal-core/.gitignore
new file mode 100644
index 0000000000..04d5852547
--- /dev/null
+++ b/src/grp-journal/libjournal-core/.gitignore
@@ -0,0 +1,4 @@
+/journald-gperf.c
+/libsystemd-journal.pc
+/audit_type-list.txt
+/audit_type-*-name.*
diff --git a/src/grp-journal/libjournal-core/Makefile b/src/grp-journal/libjournal-core/Makefile
new file mode 100644
index 0000000000..d55aebfb49
--- /dev/null
+++ b/src/grp-journal/libjournal-core/Makefile
@@ -0,0 +1,56 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+libjournal_core_la_SOURCES = \
+ src/journal/journald-kmsg.c \
+ src/journal/journald-kmsg.h \
+ src/journal/journald-syslog.c \
+ src/journal/journald-syslog.h \
+ src/journal/journald-stream.c \
+ src/journal/journald-stream.h \
+ src/journal/journald-server.c \
+ src/journal/journald-server.h \
+ src/journal/journald-console.c \
+ src/journal/journald-console.h \
+ src/journal/journald-wall.c \
+ src/journal/journald-wall.h \
+ src/journal/journald-native.c \
+ src/journal/journald-native.h \
+ src/journal/journald-audit.c \
+ src/journal/journald-audit.h \
+ src/journal/journald-rate-limit.c \
+ src/journal/journald-rate-limit.h \
+ src/journal/journal-internal.h
+
+nodist_libjournal_core_la_SOURCES = \
+ src/journal/journald-gperf.c
+
+libjournal_core_la_LIBADD = \
+ libshared.la
+
+noinst_LTLIBRARIES += \
+ libjournal-core.la
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/libjournal-core/journald-audit.c b/src/grp-journal/libjournal-core/journald-audit.c
new file mode 100644
index 0000000000..74ad0c017e
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-audit.c
@@ -0,0 +1,565 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ 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 "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/hexdecoct.h"
+#include "basic/io-util.h"
+#include "basic/missing.h"
+#include "basic/string-util.h"
+#include "sd-journal/audit-type.h"
+
+#include "journald-audit.h"
+
+typedef struct MapField {
+ const char *audit_field;
+ const char *journal_field;
+ int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov);
+} MapField;
+
+static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+ _cleanup_free_ char *c = NULL;
+ size_t l = 0, allocated = 0;
+ const char *e;
+
+ assert(field);
+ assert(p);
+ assert(iov);
+ assert(n_iov);
+
+ l = strlen(field);
+ allocated = l + 1;
+ c = malloc(allocated);
+ if (!c)
+ return -ENOMEM;
+
+ memcpy(c, field, l);
+ for (e = *p; *e != ' ' && *e != 0; e++) {
+ if (!GREEDY_REALLOC(c, allocated, l+2))
+ return -ENOMEM;
+
+ c[l++] = *e;
+ }
+
+ c[l] = 0;
+
+ if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
+ return -ENOMEM;
+
+ (*iov)[*n_iov].iov_base = c;
+ (*iov)[*n_iov].iov_len = l;
+ (*n_iov)++;
+
+ *p = e;
+ c = NULL;
+
+ return 1;
+}
+
+static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov, bool filter_printable) {
+ _cleanup_free_ char *c = NULL;
+ const char *s, *e;
+ size_t l;
+
+ assert(field);
+ assert(p);
+ assert(iov);
+ assert(n_iov);
+
+ /* The kernel formats string fields in one of two formats. */
+
+ if (**p == '"') {
+ /* Normal quoted syntax */
+ s = *p + 1;
+ e = strchr(s, '"');
+ if (!e)
+ return 0;
+
+ l = strlen(field) + (e - s);
+ c = malloc(l+1);
+ if (!c)
+ return -ENOMEM;
+
+ *((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0;
+
+ e += 1;
+
+ } else if (unhexchar(**p) >= 0) {
+ /* Hexadecimal escaping */
+ size_t allocated = 0;
+
+ l = strlen(field);
+ allocated = l + 2;
+ c = malloc(allocated);
+ if (!c)
+ return -ENOMEM;
+
+ memcpy(c, field, l);
+ for (e = *p; *e != ' ' && *e != 0; e += 2) {
+ int a, b;
+ uint8_t x;
+
+ a = unhexchar(e[0]);
+ if (a < 0)
+ return 0;
+
+ b = unhexchar(e[1]);
+ if (b < 0)
+ return 0;
+
+ x = ((uint8_t) a << 4 | (uint8_t) b);
+
+ if (filter_printable && x < (uint8_t) ' ')
+ x = (uint8_t) ' ';
+
+ if (!GREEDY_REALLOC(c, allocated, l+2))
+ return -ENOMEM;
+
+ c[l++] = (char) x;
+ }
+
+ c[l] = 0;
+ } else
+ return 0;
+
+ if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
+ return -ENOMEM;
+
+ (*iov)[*n_iov].iov_base = c;
+ (*iov)[*n_iov].iov_len = l;
+ (*n_iov)++;
+
+ *p = e;
+ c = NULL;
+
+ return 1;
+}
+
+static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+ return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false);
+}
+
+static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+ return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true);
+}
+
+static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+ const char *e, *f;
+ char *c, *t;
+ int r;
+
+ /* Implements fallback mappings for all fields we don't know */
+
+ for (e = *p; e < *p + 16; e++) {
+
+ if (*e == 0 || *e == ' ')
+ return 0;
+
+ if (*e == '=')
+ break;
+
+ if (!((*e >= 'a' && *e <= 'z') ||
+ (*e >= 'A' && *e <= 'Z') ||
+ (*e >= '0' && *e <= '9') ||
+ *e == '_' || *e == '-'))
+ return 0;
+ }
+
+ if (e <= *p || e >= *p + 16)
+ return 0;
+
+ c = alloca(strlen(prefix) + (e - *p) + 2);
+
+ t = stpcpy(c, prefix);
+ for (f = *p; f < e; f++) {
+ char x;
+
+ if (*f >= 'a' && *f <= 'z')
+ x = (*f - 'a') + 'A'; /* uppercase */
+ else if (*f == '-')
+ x = '_'; /* dashes → underscores */
+ else
+ x = *f;
+
+ *(t++) = x;
+ }
+ strcpy(t, "=");
+
+ e++;
+
+ r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
+ if (r < 0)
+ return r;
+
+ *p = e;
+ return r;
+}
+
+/* Kernel fields are those occurring in the audit string before
+ * msg='. All of these fields are trusted, hence carry the "_" prefix.
+ * We try to translate the fields we know into our native names. The
+ * other's are generically mapped to _AUDIT_FIELD_XYZ= */
+static const MapField map_fields_kernel[] = {
+
+ /* First, we map certain well-known audit fields into native
+ * well-known fields */
+ { "pid=", "_PID=", map_simple_field },
+ { "ppid=", "_PPID=", map_simple_field },
+ { "uid=", "_UID=", map_simple_field },
+ { "euid=", "_EUID=", map_simple_field },
+ { "fsuid=", "_FSUID=", map_simple_field },
+ { "gid=", "_GID=", map_simple_field },
+ { "egid=", "_EGID=", map_simple_field },
+ { "fsgid=", "_FSGID=", map_simple_field },
+ { "tty=", "_TTY=", map_simple_field },
+ { "ses=", "_AUDIT_SESSION=", map_simple_field },
+ { "auid=", "_AUDIT_LOGINUID=", map_simple_field },
+ { "subj=", "_SELINUX_CONTEXT=", map_simple_field },
+ { "comm=", "_COMM=", map_string_field },
+ { "exe=", "_EXE=", map_string_field },
+ { "proctitle=", "_CMDLINE=", map_string_field_printable },
+
+ /* Some fields don't map to native well-known fields. However,
+ * we know that they are string fields, hence let's undo
+ * string field escaping for them, though we stick to the
+ * generic field names. */
+ { "path=", "_AUDIT_FIELD_PATH=", map_string_field },
+ { "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
+ { "name=", "_AUDIT_FIELD_NAME=", map_string_field },
+ {}
+};
+
+/* Userspace fields are those occurring in the audit string after
+ * msg='. All of these fields are untrusted, hence carry no "_"
+ * prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
+static const MapField map_fields_userspace[] = {
+ { "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
+ { "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
+ { "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
+ { "exe=", "AUDIT_FIELD_EXE=", map_string_field },
+ { "comm=", "AUDIT_FIELD_COMM=", map_string_field },
+ {}
+};
+
+static int map_all_fields(
+ const char *p,
+ const MapField map_fields[],
+ const char *prefix,
+ bool handle_msg,
+ struct iovec **iov,
+ size_t *n_iov_allocated,
+ unsigned *n_iov) {
+
+ int r;
+
+ assert(p);
+ assert(iov);
+ assert(n_iov_allocated);
+ assert(n_iov);
+
+ for (;;) {
+ bool mapped = false;
+ const MapField *m;
+ const char *v;
+
+ p += strspn(p, WHITESPACE);
+
+ if (*p == 0)
+ return 0;
+
+ if (handle_msg) {
+ v = startswith(p, "msg='");
+ if (v) {
+ const char *e;
+ char *c;
+
+ /* Userspace message. It's enclosed in
+ simple quotation marks, is not
+ escaped, but the last field in the
+ line, hence let's remove the
+ quotation mark, and apply the
+ userspace mapping instead of the
+ kernel mapping. */
+
+ e = endswith(v, "'");
+ if (!e)
+ return 0; /* don't continue splitting up if the final quotation mark is missing */
+
+ c = strndupa(v, e - v);
+ return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov);
+ }
+ }
+
+ /* Try to map the kernel fields to our own names */
+ for (m = map_fields; m->audit_field; m++) {
+ v = startswith(p, m->audit_field);
+ if (!v)
+ continue;
+
+ r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse audit array: %m");
+
+ if (r > 0) {
+ mapped = true;
+ p = v;
+ break;
+ }
+ }
+
+ if (!mapped) {
+ r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse audit array: %m");
+
+ if (r == 0)
+ /* Couldn't process as generic field, let's just skip over it */
+ p += strcspn(p, WHITESPACE);
+ }
+ }
+}
+
+static void process_audit_string(Server *s, int type, const char *data, size_t size) {
+ _cleanup_free_ struct iovec *iov = NULL;
+ size_t n_iov_allocated = 0;
+ unsigned n_iov = 0, k;
+ uint64_t seconds, msec, id;
+ const char *p, *type_name;
+ unsigned z;
+ char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
+ type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
+ source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
+ char *m;
+
+ assert(s);
+
+ if (size <= 0)
+ return;
+
+ if (!data)
+ return;
+
+ /* Note that the input buffer is NUL terminated, but let's
+ * check whether there is a spurious NUL byte */
+ if (memchr(data, 0, size))
+ return;
+
+ p = startswith(data, "audit");
+ if (!p)
+ return;
+
+ if (sscanf(p, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n",
+ &seconds,
+ &msec,
+ &id,
+ &k) != 3)
+ return;
+
+ p += k;
+ p += strspn(p, WHITESPACE);
+
+ if (isempty(p))
+ return;
+
+ n_iov_allocated = N_IOVEC_META_FIELDS + 7;
+ iov = new(struct iovec, n_iov_allocated);
+ if (!iov) {
+ log_oom();
+ return;
+ }
+
+ IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit");
+
+ sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64,
+ (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC);
+ IOVEC_SET_STRING(iov[n_iov++], source_time_field);
+
+ sprintf(type_field, "_AUDIT_TYPE=%i", type);
+ IOVEC_SET_STRING(iov[n_iov++], type_field);
+
+ sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
+ IOVEC_SET_STRING(iov[n_iov++], id_field);
+
+ assert_cc(4 == LOG_FAC(LOG_AUTH));
+ IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_FACILITY=4");
+ IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_IDENTIFIER=audit");
+
+ type_name = audit_type_name_alloca(type);
+
+ m = strjoina("MESSAGE=", type_name, " ", p);
+ IOVEC_SET_STRING(iov[n_iov++], m);
+
+ z = n_iov;
+
+ map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov);
+
+ if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) {
+ log_oom();
+ goto finish;
+ }
+
+ server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, NULL, 0, NULL, LOG_NOTICE, 0);
+
+finish:
+ /* free() all entries that map_all_fields() added. All others
+ * are allocated on the stack or are constant. */
+
+ for (; z < n_iov; z++)
+ free(iov[z].iov_base);
+}
+
+void server_process_audit_message(
+ Server *s,
+ const void *buffer,
+ size_t buffer_size,
+ const struct ucred *ucred,
+ const union sockaddr_union *sa,
+ socklen_t salen) {
+
+ const struct nlmsghdr *nl = buffer;
+
+ assert(s);
+
+ if (buffer_size < ALIGN(sizeof(struct nlmsghdr)))
+ return;
+
+ assert(buffer);
+
+ /* Filter out fake data */
+ if (!sa ||
+ salen != sizeof(struct sockaddr_nl) ||
+ sa->nl.nl_family != AF_NETLINK ||
+ sa->nl.nl_pid != 0) {
+ log_debug("Audit netlink message from invalid sender.");
+ return;
+ }
+
+ if (!ucred || ucred->pid != 0) {
+ log_debug("Audit netlink message with invalid credentials.");
+ return;
+ }
+
+ if (!NLMSG_OK(nl, buffer_size)) {
+ log_error("Audit netlink message truncated.");
+ return;
+ }
+
+ /* Ignore special Netlink messages */
+ if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR))
+ return;
+
+ /* Below AUDIT_FIRST_USER_MSG theer are only control messages, let's ignore those */
+ if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG)
+ return;
+
+ process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)));
+}
+
+static int enable_audit(int fd, bool b) {
+ struct {
+ union {
+ struct nlmsghdr header;
+ uint8_t header_space[NLMSG_HDRLEN];
+ };
+ struct audit_status body;
+ } _packed_ request = {
+ .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)),
+ .header.nlmsg_type = AUDIT_SET,
+ .header.nlmsg_flags = NLM_F_REQUEST,
+ .header.nlmsg_seq = 1,
+ .header.nlmsg_pid = 0,
+ .body.mask = AUDIT_STATUS_ENABLED,
+ .body.enabled = b,
+ };
+ union sockaddr_union sa = {
+ .nl.nl_family = AF_NETLINK,
+ .nl.nl_pid = 0,
+ };
+ struct iovec iovec = {
+ .iov_base = &request,
+ .iov_len = NLMSG_LENGTH(sizeof(struct audit_status)),
+ };
+ struct msghdr mh = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ .msg_name = &sa.sa,
+ .msg_namelen = sizeof(sa.nl),
+ };
+
+ ssize_t n;
+
+ n = sendmsg(fd, &mh, MSG_NOSIGNAL);
+ if (n < 0)
+ return -errno;
+ if (n != NLMSG_LENGTH(sizeof(struct audit_status)))
+ return -EIO;
+
+ /* We don't wait for the result here, we can't do anything
+ * about it anyway */
+
+ return 0;
+}
+
+int server_open_audit(Server *s) {
+ static const int one = 1;
+ int r;
+
+ if (s->audit_fd < 0) {
+ static const union sockaddr_union sa = {
+ .nl.nl_family = AF_NETLINK,
+ .nl.nl_pid = 0,
+ .nl.nl_groups = AUDIT_NLGRP_READLOG,
+ };
+
+ s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
+ if (s->audit_fd < 0) {
+ if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
+ log_debug("Audit not supported in the kernel.");
+ else
+ log_warning_errno(errno, "Failed to create audit socket, ignoring: %m");
+
+ return 0;
+ }
+
+ if (bind(s->audit_fd, &sa.sa, sizeof(sa.nl)) < 0) {
+ log_warning_errno(errno,
+ "Failed to join audit multicast group. "
+ "The kernel is probably too old or multicast reading is not supported. "
+ "Ignoring: %m");
+ s->audit_fd = safe_close(s->audit_fd);
+ return 0;
+ }
+ } else
+ fd_nonblock(s->audit_fd, 1);
+
+ r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0)
+ return log_error_errno(errno, "Failed to set SO_PASSCRED on audit socket: %m");
+
+ r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add audit fd to event loop: %m");
+
+ /* We are listening now, try to enable audit */
+ r = enable_audit(s->audit_fd, true);
+ if (r < 0)
+ log_warning_errno(r, "Failed to issue audit enable call: %m");
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/journald-audit.h b/src/grp-journal/libjournal-core/journald-audit.h
new file mode 100644
index 0000000000..98c07e95e4
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-audit.h
@@ -0,0 +1,28 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Lennart Poettering
+
+ 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 "basic/socket-util.h"
+
+#include "journald-server.h"
+
+void server_process_audit_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const union sockaddr_union *sa, socklen_t salen);
+
+int server_open_audit(Server*s);
diff --git a/src/grp-journal/libjournal-core/journald-console.c b/src/grp-journal/libjournal-core/journald-console.c
new file mode 100644
index 0000000000..b1e6828dc2
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-console.c
@@ -0,0 +1,116 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <sys/socket.h>
+#include <time.h>
+
+#include "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/formats-util.h"
+#include "basic/io-util.h"
+#include "basic/parse-util.h"
+#include "basic/process-util.h"
+#include "basic/stdio-util.h"
+#include "basic/terminal-util.h"
+
+#include "journald-console.h"
+#include "journald-server.h"
+
+static bool prefix_timestamp(void) {
+
+ static int cached_printk_time = -1;
+
+ if (_unlikely_(cached_printk_time < 0)) {
+ _cleanup_free_ char *p = NULL;
+
+ cached_printk_time =
+ read_one_line_file("/sys/module/printk/parameters/time", &p) >= 0
+ && parse_boolean(p) > 0;
+ }
+
+ return cached_printk_time;
+}
+
+void server_forward_console(
+ Server *s,
+ int priority,
+ const char *identifier,
+ const char *message,
+ const struct ucred *ucred) {
+
+ struct iovec iovec[5];
+ struct timespec ts;
+ char tbuf[sizeof("[] ")-1 + DECIMAL_STR_MAX(ts.tv_sec) + DECIMAL_STR_MAX(ts.tv_nsec)-3 + 1];
+ char header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t)];
+ int n = 0, fd;
+ _cleanup_free_ char *ident_buf = NULL;
+ const char *tty;
+
+ assert(s);
+ assert(message);
+
+ if (LOG_PRI(priority) > s->max_level_console)
+ return;
+
+ /* First: timestamp */
+ if (prefix_timestamp()) {
+ assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0);
+ xsprintf(tbuf, "[%5"PRI_TIME".%06ld] ",
+ ts.tv_sec,
+ ts.tv_nsec / 1000);
+ IOVEC_SET_STRING(iovec[n++], tbuf);
+ }
+
+ /* Second: identifier and PID */
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid);
+
+ if (identifier)
+ IOVEC_SET_STRING(iovec[n++], identifier);
+
+ IOVEC_SET_STRING(iovec[n++], header_pid);
+ } else if (identifier) {
+ IOVEC_SET_STRING(iovec[n++], identifier);
+ IOVEC_SET_STRING(iovec[n++], ": ");
+ }
+
+ /* Fourth: message */
+ IOVEC_SET_STRING(iovec[n++], message);
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ tty = s->tty_path ? s->tty_path : "/dev/console";
+
+ fd = open_terminal(tty, O_WRONLY|O_NOCTTY|O_CLOEXEC);
+ if (fd < 0) {
+ log_debug_errno(fd, "Failed to open %s for logging: %m", tty);
+ return;
+ }
+
+ if (writev(fd, iovec, n) < 0)
+ log_debug_errno(errno, "Failed to write to %s for logging: %m", tty);
+
+ safe_close(fd);
+}
diff --git a/src/grp-journal/libjournal-core/journald-console.h b/src/grp-journal/libjournal-core/journald-console.h
new file mode 100644
index 0000000000..dda07e2c28
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-console.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "journald-server.h"
+
+void server_forward_console(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred);
diff --git a/src/grp-journal/libjournal-core/journald-gperf.gperf b/src/grp-journal/libjournal-core/journald-gperf.gperf
new file mode 100644
index 0000000000..7c5c68862a
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-gperf.gperf
@@ -0,0 +1,48 @@
+%{
+#include <stddef.h>
+#include <sys/socket.h>
+
+#include "shared/conf-parser.h"
+
+#include "journald-server.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name journald_gperf_hash
+%define lookup-function-name journald_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Journal.Storage, config_parse_storage, 0, offsetof(Server, storage)
+Journal.Compress, config_parse_bool, 0, offsetof(Server, compress)
+Journal.Seal, config_parse_bool, 0, offsetof(Server, seal)
+Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec)
+# The following is a legacy name for compatibility
+Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, rate_limit_interval)
+Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, rate_limit_interval)
+Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, rate_limit_burst)
+Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_use)
+Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.max_size)
+Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_metrics.keep_free)
+Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_metrics.n_max_files)
+Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_use)
+Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.max_size)
+Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_metrics.keep_free)
+Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_metrics.n_max_files)
+Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec)
+Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec)
+Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog)
+Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg)
+Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console)
+Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall)
+Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path)
+Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store)
+Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog)
+Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg)
+Journal.MaxLevelConsole, config_parse_log_level, 0, offsetof(Server, max_level_console)
+Journal.MaxLevelWall, config_parse_log_level, 0, offsetof(Server, max_level_wall)
+Journal.SplitMode, config_parse_split_mode, 0, offsetof(Server, split_mode)
diff --git a/src/grp-journal/libjournal-core/journald-kmsg.c b/src/grp-journal/libjournal-core/journald-kmsg.c
new file mode 100644
index 0000000000..946318c184
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-kmsg.c
@@ -0,0 +1,474 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <sys/epoll.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <libudev.h>
+#include <systemd/sd-messages.h>
+
+#include "basic/escape.h"
+#include "basic/fd-util.h"
+#include "basic/formats-util.h"
+#include "basic/io-util.h"
+#include "basic/parse-util.h"
+#include "basic/process-util.h"
+#include "basic/stdio-util.h"
+#include "basic/string-util.h"
+
+#include "journald-kmsg.h"
+#include "journald-server.h"
+#include "journald-syslog.h"
+
+void server_forward_kmsg(
+ Server *s,
+ int priority,
+ const char *identifier,
+ const char *message,
+ const struct ucred *ucred) {
+
+ struct iovec iovec[5];
+ char header_priority[DECIMAL_STR_MAX(priority) + 3],
+ header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t) + 1];
+ int n = 0;
+ char *ident_buf = NULL;
+
+ assert(s);
+ assert(priority >= 0);
+ assert(priority <= 999);
+ assert(message);
+
+ if (_unlikely_(LOG_PRI(priority) > s->max_level_kmsg))
+ return;
+
+ if (_unlikely_(s->dev_kmsg_fd < 0))
+ return;
+
+ /* Never allow messages with kernel facility to be written to
+ * kmsg, regardless where the data comes from. */
+ priority = syslog_fixup_facility(priority);
+
+ /* First: priority field */
+ xsprintf(header_priority, "<%i>", priority);
+ IOVEC_SET_STRING(iovec[n++], header_priority);
+
+ /* Second: identifier and PID */
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid);
+
+ if (identifier)
+ IOVEC_SET_STRING(iovec[n++], identifier);
+
+ IOVEC_SET_STRING(iovec[n++], header_pid);
+ } else if (identifier) {
+ IOVEC_SET_STRING(iovec[n++], identifier);
+ IOVEC_SET_STRING(iovec[n++], ": ");
+ }
+
+ /* Fourth: message */
+ IOVEC_SET_STRING(iovec[n++], message);
+ IOVEC_SET_STRING(iovec[n++], "\n");
+
+ if (writev(s->dev_kmsg_fd, iovec, n) < 0)
+ log_debug_errno(errno, "Failed to write to /dev/kmsg for logging: %m");
+
+ free(ident_buf);
+}
+
+static bool is_us(const char *pid) {
+ pid_t t;
+
+ assert(pid);
+
+ if (parse_pid(pid, &t) < 0)
+ return false;
+
+ return t == getpid();
+}
+
+static void dev_kmsg_record(Server *s, const char *p, size_t l) {
+ struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS];
+ char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL;
+ int priority, r;
+ unsigned n = 0, z = 0, j;
+ unsigned long long usec;
+ char *identifier = NULL, *pid = NULL, *e, *f, *k;
+ uint64_t serial;
+ size_t pl;
+ char *kernel_device = NULL;
+
+ assert(s);
+ assert(p);
+
+ if (l <= 0)
+ return;
+
+ e = memchr(p, ',', l);
+ if (!e)
+ return;
+ *e = 0;
+
+ r = safe_atoi(p, &priority);
+ if (r < 0 || priority < 0 || priority > 999)
+ return;
+
+ if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN)
+ return;
+
+ l -= (e - p) + 1;
+ p = e + 1;
+ e = memchr(p, ',', l);
+ if (!e)
+ return;
+ *e = 0;
+
+ r = safe_atou64(p, &serial);
+ if (r < 0)
+ return;
+
+ if (s->kernel_seqnum) {
+ /* We already read this one? */
+ if (serial < *s->kernel_seqnum)
+ return;
+
+ /* Did we lose any? */
+ if (serial > *s->kernel_seqnum)
+ server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED,
+ LOG_MESSAGE("Missed %"PRIu64" kernel messages",
+ serial - *s->kernel_seqnum),
+ NULL);
+
+ /* Make sure we never read this one again. Note that
+ * we always store the next message serial we expect
+ * here, simply because this makes handling the first
+ * message with serial 0 easy. */
+ *s->kernel_seqnum = serial + 1;
+ }
+
+ l -= (e - p) + 1;
+ p = e + 1;
+ f = memchr(p, ';', l);
+ if (!f)
+ return;
+ /* Kernel 3.6 has the flags field, kernel 3.5 lacks that */
+ e = memchr(p, ',', l);
+ if (!e || f < e)
+ e = f;
+ *e = 0;
+
+ r = safe_atollu(p, &usec);
+ if (r < 0)
+ return;
+
+ l -= (f - p) + 1;
+ p = f + 1;
+ e = memchr(p, '\n', l);
+ if (!e)
+ return;
+ *e = 0;
+
+ pl = e - p;
+ l -= (e - p) + 1;
+ k = e + 1;
+
+ for (j = 0; l > 0 && j < N_IOVEC_KERNEL_FIELDS; j++) {
+ char *m;
+ /* Metadata fields attached */
+
+ if (*k != ' ')
+ break;
+
+ k++, l--;
+
+ e = memchr(k, '\n', l);
+ if (!e)
+ return;
+
+ *e = 0;
+
+ if (cunescape_length_with_prefix(k, e - k, "_KERNEL_", UNESCAPE_RELAX, &m) < 0)
+ break;
+
+ if (startswith(m, "_KERNEL_DEVICE="))
+ kernel_device = m + 15;
+
+ IOVEC_SET_STRING(iovec[n++], m);
+ z++;
+
+ l -= (e - k) + 1;
+ k = e + 1;
+ }
+
+ if (kernel_device) {
+ struct udev_device *ud;
+
+ ud = udev_device_new_from_device_id(s->udev, kernel_device);
+ if (ud) {
+ const char *g;
+ struct udev_list_entry *ll;
+ char *b;
+
+ g = udev_device_get_devnode(ud);
+ if (g) {
+ b = strappend("_UDEV_DEVNODE=", g);
+ if (b) {
+ IOVEC_SET_STRING(iovec[n++], b);
+ z++;
+ }
+ }
+
+ g = udev_device_get_sysname(ud);
+ if (g) {
+ b = strappend("_UDEV_SYSNAME=", g);
+ if (b) {
+ IOVEC_SET_STRING(iovec[n++], b);
+ z++;
+ }
+ }
+
+ j = 0;
+ ll = udev_device_get_devlinks_list_entry(ud);
+ udev_list_entry_foreach(ll, ll) {
+
+ if (j > N_IOVEC_UDEV_FIELDS)
+ break;
+
+ g = udev_list_entry_get_name(ll);
+ if (g) {
+ b = strappend("_UDEV_DEVLINK=", g);
+ if (b) {
+ IOVEC_SET_STRING(iovec[n++], b);
+ z++;
+ }
+ }
+
+ j++;
+ }
+
+ udev_device_unref(ud);
+ }
+ }
+
+ if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec) >= 0)
+ IOVEC_SET_STRING(iovec[n++], source_time);
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel");
+
+ if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+ if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0)
+ IOVEC_SET_STRING(iovec[n++], syslog_facility);
+
+ if ((priority & LOG_FACMASK) == LOG_KERN)
+ IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel");
+ else {
+ pl -= syslog_parse_identifier((const char**) &p, &identifier, &pid);
+
+ /* Avoid any messages we generated ourselves via
+ * log_info() and friends. */
+ if (pid && is_us(pid))
+ goto finish;
+
+ if (identifier) {
+ syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier);
+ if (syslog_identifier)
+ IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+ }
+
+ if (pid) {
+ syslog_pid = strappend("SYSLOG_PID=", pid);
+ if (syslog_pid)
+ IOVEC_SET_STRING(iovec[n++], syslog_pid);
+ }
+ }
+
+ if (cunescape_length_with_prefix(p, pl, "MESSAGE=", UNESCAPE_RELAX, &message) >= 0)
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority, 0);
+
+finish:
+ for (j = 0; j < z; j++)
+ free(iovec[j].iov_base);
+
+ free(message);
+ free(syslog_priority);
+ free(syslog_identifier);
+ free(syslog_pid);
+ free(syslog_facility);
+ free(source_time);
+ free(identifier);
+ free(pid);
+}
+
+static int server_read_dev_kmsg(Server *s) {
+ char buffer[8192+1]; /* the kernel-side limit per record is 8K currently */
+ ssize_t l;
+
+ assert(s);
+ assert(s->dev_kmsg_fd >= 0);
+
+ l = read(s->dev_kmsg_fd, buffer, sizeof(buffer) - 1);
+ if (l == 0)
+ return 0;
+ if (l < 0) {
+ /* Old kernels who don't allow reading from /dev/kmsg
+ * return EINVAL when we try. So handle this cleanly,
+ * but don' try to ever read from it again. */
+ if (errno == EINVAL) {
+ s->dev_kmsg_event_source = sd_event_source_unref(s->dev_kmsg_event_source);
+ return 0;
+ }
+
+ if (errno == EAGAIN || errno == EINTR || errno == EPIPE)
+ return 0;
+
+ return log_error_errno(errno, "Failed to read from kernel: %m");
+ }
+
+ dev_kmsg_record(s, buffer, l);
+ return 1;
+}
+
+int server_flush_dev_kmsg(Server *s) {
+ int r;
+
+ assert(s);
+
+ if (s->dev_kmsg_fd < 0)
+ return 0;
+
+ if (!s->dev_kmsg_readable)
+ return 0;
+
+ log_debug("Flushing /dev/kmsg...");
+
+ for (;;) {
+ r = server_read_dev_kmsg(s);
+ if (r < 0)
+ return r;
+
+ if (r == 0)
+ break;
+ }
+
+ return 0;
+}
+
+static int dispatch_dev_kmsg(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Server *s = userdata;
+
+ assert(es);
+ assert(fd == s->dev_kmsg_fd);
+ assert(s);
+
+ if (revents & EPOLLERR)
+ log_warning("/dev/kmsg buffer overrun, some messages lost.");
+
+ if (!(revents & EPOLLIN))
+ log_error("Got invalid event from epoll for /dev/kmsg: %"PRIx32, revents);
+
+ return server_read_dev_kmsg(s);
+}
+
+int server_open_dev_kmsg(Server *s) {
+ int r;
+
+ assert(s);
+
+ s->dev_kmsg_fd = open("/dev/kmsg", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+ if (s->dev_kmsg_fd < 0) {
+ log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
+ "Failed to open /dev/kmsg, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_event_add_io(s->event, &s->dev_kmsg_event_source, s->dev_kmsg_fd, EPOLLIN, dispatch_dev_kmsg, s);
+ if (r < 0) {
+
+ /* This will fail with EPERM on older kernels where
+ * /dev/kmsg is not readable. */
+ if (r == -EPERM) {
+ r = 0;
+ goto fail;
+ }
+
+ log_error_errno(r, "Failed to add /dev/kmsg fd to event loop: %m");
+ goto fail;
+ }
+
+ r = sd_event_source_set_priority(s->dev_kmsg_event_source, SD_EVENT_PRIORITY_IMPORTANT+10);
+ if (r < 0) {
+ log_error_errno(r, "Failed to adjust priority of kmsg event source: %m");
+ goto fail;
+ }
+
+ s->dev_kmsg_readable = true;
+
+ return 0;
+
+fail:
+ s->dev_kmsg_event_source = sd_event_source_unref(s->dev_kmsg_event_source);
+ s->dev_kmsg_fd = safe_close(s->dev_kmsg_fd);
+
+ return r;
+}
+
+int server_open_kernel_seqnum(Server *s) {
+ _cleanup_close_ int fd;
+ uint64_t *p;
+ int r;
+
+ assert(s);
+
+ /* We store the seqnum we last read in an mmaped file. That
+ * way we can just use it like a variable, but it is
+ * persistent and automatically flushed at reboot. */
+
+ fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644);
+ if (fd < 0) {
+ log_error_errno(errno, "Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m");
+ return 0;
+ }
+
+ r = posix_fallocate(fd, 0, sizeof(uint64_t));
+ if (r != 0) {
+ log_error_errno(r, "Failed to allocate sequential number file, ignoring: %m");
+ return 0;
+ }
+
+ p = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ if (p == MAP_FAILED) {
+ log_error_errno(errno, "Failed to map sequential number file, ignoring: %m");
+ return 0;
+ }
+
+ s->kernel_seqnum = p;
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/journald-kmsg.h b/src/grp-journal/libjournal-core/journald-kmsg.h
new file mode 100644
index 0000000000..dab49f1e8c
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-kmsg.h
@@ -0,0 +1,29 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "journald-server.h"
+
+int server_open_dev_kmsg(Server *s);
+int server_flush_dev_kmsg(Server *s);
+
+void server_forward_kmsg(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred);
+
+int server_open_kernel_seqnum(Server *s);
diff --git a/src/grp-journal/libjournal-core/journald-native.c b/src/grp-journal/libjournal-core/journald-native.c
new file mode 100644
index 0000000000..9bfea935ad
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-native.c
@@ -0,0 +1,502 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <stddef.h>
+#include <sys/epoll.h>
+#include <sys/mman.h>
+#include <sys/statvfs.h>
+#include <unistd.h>
+
+#include "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/fs-util.h"
+#include "basic/io-util.h"
+#include "basic/memfd-util.h"
+#include "basic/parse-util.h"
+#include "basic/path-util.h"
+#include "basic/selinux-util.h"
+#include "basic/socket-util.h"
+#include "basic/string-util.h"
+
+#include "journald-console.h"
+#include "journald-kmsg.h"
+#include "journald-native.h"
+#include "journald-server.h"
+#include "journald-syslog.h"
+#include "journald-wall.h"
+
+bool valid_user_field(const char *p, size_t l, bool allow_protected) {
+ const char *a;
+
+ /* We kinda enforce POSIX syntax recommendations for
+ environment variables here, but make a couple of additional
+ requirements.
+
+ http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */
+
+ /* No empty field names */
+ if (l <= 0)
+ return false;
+
+ /* Don't allow names longer than 64 chars */
+ if (l > 64)
+ return false;
+
+ /* Variables starting with an underscore are protected */
+ if (!allow_protected && p[0] == '_')
+ return false;
+
+ /* Don't allow digits as first character */
+ if (p[0] >= '0' && p[0] <= '9')
+ return false;
+
+ /* Only allow A-Z0-9 and '_' */
+ for (a = p; a < p + l; a++)
+ if ((*a < 'A' || *a > 'Z') &&
+ (*a < '0' || *a > '9') &&
+ *a != '_')
+ return false;
+
+ return true;
+}
+
+static bool allow_object_pid(const struct ucred *ucred) {
+ return ucred && ucred->uid == 0;
+}
+
+void server_process_native_message(
+ Server *s,
+ const void *buffer, size_t buffer_size,
+ const struct ucred *ucred,
+ const struct timeval *tv,
+ const char *label, size_t label_len) {
+
+ struct iovec *iovec = NULL;
+ unsigned n = 0, j, tn = (unsigned) -1;
+ const char *p;
+ size_t remaining, m = 0, entry_size = 0;
+ int priority = LOG_INFO;
+ char *identifier = NULL, *message = NULL;
+ pid_t object_pid = 0;
+
+ assert(s);
+ assert(buffer || buffer_size == 0);
+
+ p = buffer;
+ remaining = buffer_size;
+
+ while (remaining > 0) {
+ const char *e, *q;
+
+ e = memchr(p, '\n', remaining);
+
+ if (!e) {
+ /* Trailing noise, let's ignore it, and flush what we collected */
+ log_debug("Received message with trailing noise, ignoring.");
+ break;
+ }
+
+ if (e == p) {
+ /* Entry separator */
+
+ if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */
+ log_debug("Entry is too big with %u properties and %zu bytes, ignoring.", n, entry_size);
+ continue;
+ }
+
+ server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid);
+ n = 0;
+ priority = LOG_INFO;
+ entry_size = 0;
+
+ p++;
+ remaining--;
+ continue;
+ }
+
+ if (*p == '.' || *p == '#') {
+ /* Ignore control commands for now, and
+ * comments too. */
+ remaining -= (e - p) + 1;
+ p = e + 1;
+ continue;
+ }
+
+ /* A property follows */
+
+ /* n existing properties, 1 new, +1 for _TRANSPORT */
+ if (!GREEDY_REALLOC(iovec, m, n + 2 + N_IOVEC_META_FIELDS + N_IOVEC_OBJECT_FIELDS)) {
+ log_oom();
+ break;
+ }
+
+ q = memchr(p, '=', e - p);
+ if (q) {
+ if (valid_user_field(p, q - p, false)) {
+ size_t l;
+
+ l = e - p;
+
+ /* If the field name starts with an
+ * underscore, skip the variable,
+ * since that indidates a trusted
+ * field */
+ iovec[n].iov_base = (char*) p;
+ iovec[n].iov_len = l;
+ entry_size += iovec[n].iov_len;
+ n++;
+
+ /* We need to determine the priority
+ * of this entry for the rate limiting
+ * logic */
+ if (l == 10 &&
+ startswith(p, "PRIORITY=") &&
+ p[9] >= '0' && p[9] <= '9')
+ priority = (priority & LOG_FACMASK) | (p[9] - '0');
+
+ else if (l == 17 &&
+ startswith(p, "SYSLOG_FACILITY=") &&
+ p[16] >= '0' && p[16] <= '9')
+ priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3);
+
+ else if (l == 18 &&
+ startswith(p, "SYSLOG_FACILITY=") &&
+ p[16] >= '0' && p[16] <= '9' &&
+ p[17] >= '0' && p[17] <= '9')
+ priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3);
+
+ else if (l >= 19 &&
+ startswith(p, "SYSLOG_IDENTIFIER=")) {
+ char *t;
+
+ t = strndup(p + 18, l - 18);
+ if (t) {
+ free(identifier);
+ identifier = t;
+ }
+
+ } else if (l >= 8 &&
+ startswith(p, "MESSAGE=")) {
+ char *t;
+
+ t = strndup(p + 8, l - 8);
+ if (t) {
+ free(message);
+ message = t;
+ }
+
+ } else if (l > strlen("OBJECT_PID=") &&
+ l < strlen("OBJECT_PID=") + DECIMAL_STR_MAX(pid_t) &&
+ startswith(p, "OBJECT_PID=") &&
+ allow_object_pid(ucred)) {
+ char buf[DECIMAL_STR_MAX(pid_t)];
+ memcpy(buf, p + strlen("OBJECT_PID="), l - strlen("OBJECT_PID="));
+ buf[l-strlen("OBJECT_PID=")] = '\0';
+
+ /* ignore error */
+ parse_pid(buf, &object_pid);
+ }
+ }
+
+ remaining -= (e - p) + 1;
+ p = e + 1;
+ continue;
+ } else {
+ le64_t l_le;
+ uint64_t l;
+ char *k;
+
+ if (remaining < e - p + 1 + sizeof(uint64_t) + 1) {
+ log_debug("Failed to parse message, ignoring.");
+ break;
+ }
+
+ memcpy(&l_le, e + 1, sizeof(uint64_t));
+ l = le64toh(l_le);
+
+ if (l > DATA_SIZE_MAX) {
+ log_debug("Received binary data block of %"PRIu64" bytes is too large, ignoring.", l);
+ break;
+ }
+
+ if ((uint64_t) remaining < e - p + 1 + sizeof(uint64_t) + l + 1 ||
+ e[1+sizeof(uint64_t)+l] != '\n') {
+ log_debug("Failed to parse message, ignoring.");
+ break;
+ }
+
+ k = malloc((e - p) + 1 + l);
+ if (!k) {
+ log_oom();
+ break;
+ }
+
+ memcpy(k, p, e - p);
+ k[e - p] = '=';
+ memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l);
+
+ if (valid_user_field(p, e - p, false)) {
+ iovec[n].iov_base = k;
+ iovec[n].iov_len = (e - p) + 1 + l;
+ entry_size += iovec[n].iov_len;
+ n++;
+ } else
+ free(k);
+
+ remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1;
+ p = e + 1 + sizeof(uint64_t) + l + 1;
+ }
+ }
+
+ if (n <= 0)
+ goto finish;
+
+ tn = n++;
+ IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal");
+ entry_size += strlen("_TRANSPORT=journal");
+
+ if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */
+ log_debug("Entry is too big with %u properties and %zu bytes, ignoring.",
+ n, entry_size);
+ goto finish;
+ }
+
+ if (message) {
+ if (s->forward_to_syslog)
+ server_forward_syslog(s, priority, identifier, message, ucred, tv);
+
+ if (s->forward_to_kmsg)
+ server_forward_kmsg(s, priority, identifier, message, ucred);
+
+ if (s->forward_to_console)
+ server_forward_console(s, priority, identifier, message, ucred);
+
+ if (s->forward_to_wall)
+ server_forward_wall(s, priority, identifier, message, ucred);
+ }
+
+ server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid);
+
+finish:
+ for (j = 0; j < n; j++) {
+ if (j == tn)
+ continue;
+
+ if (iovec[j].iov_base < buffer ||
+ (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size)
+ free(iovec[j].iov_base);
+ }
+
+ free(iovec);
+ free(identifier);
+ free(message);
+}
+
+void server_process_native_file(
+ Server *s,
+ int fd,
+ const struct ucred *ucred,
+ const struct timeval *tv,
+ const char *label, size_t label_len) {
+
+ struct stat st;
+ bool sealed;
+ int r;
+
+ /* Data is in the passed fd, since it didn't fit in a
+ * datagram. */
+
+ assert(s);
+ assert(fd >= 0);
+
+ /* If it's a memfd, check if it is sealed. If so, we can just
+ * use map it and use it, and do not need to copy the data
+ * out. */
+ sealed = memfd_get_sealed(fd) > 0;
+
+ if (!sealed && (!ucred || ucred->uid != 0)) {
+ _cleanup_free_ char *sl = NULL, *k = NULL;
+ const char *e;
+
+ /* If this is not a sealed memfd, and the peer is unknown or
+ * unprivileged, then verify the path. */
+
+ if (asprintf(&sl, "/proc/self/fd/%i", fd) < 0) {
+ log_oom();
+ return;
+ }
+
+ r = readlink_malloc(sl, &k);
+ if (r < 0) {
+ log_error_errno(r, "readlink(%s) failed: %m", sl);
+ return;
+ }
+
+ e = path_startswith(k, "/dev/shm/");
+ if (!e)
+ e = path_startswith(k, "/tmp/");
+ if (!e)
+ e = path_startswith(k, "/var/tmp/");
+ if (!e) {
+ log_error("Received file outside of allowed directories. Refusing.");
+ return;
+ }
+
+ if (!filename_is_valid(e)) {
+ log_error("Received file in subdirectory of allowed directories. Refusing.");
+ return;
+ }
+ }
+
+ if (fstat(fd, &st) < 0) {
+ log_error_errno(errno, "Failed to stat passed file, ignoring: %m");
+ return;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ log_error("File passed is not regular. Ignoring.");
+ return;
+ }
+
+ if (st.st_size <= 0)
+ return;
+
+ if (st.st_size > ENTRY_SIZE_MAX) {
+ log_error("File passed too large. Ignoring.");
+ return;
+ }
+
+ if (sealed) {
+ void *p;
+ size_t ps;
+
+ /* The file is sealed, we can just map it and use it. */
+
+ ps = PAGE_ALIGN(st.st_size);
+ p = mmap(NULL, ps, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (p == MAP_FAILED) {
+ log_error_errno(errno, "Failed to map memfd, ignoring: %m");
+ return;
+ }
+
+ server_process_native_message(s, p, st.st_size, ucred, tv, label, label_len);
+ assert_se(munmap(p, ps) >= 0);
+ } else {
+ _cleanup_free_ void *p = NULL;
+ struct statvfs vfs;
+ ssize_t n;
+
+ if (fstatvfs(fd, &vfs) < 0) {
+ log_error_errno(errno, "Failed to stat file system of passed file, ignoring: %m");
+ return;
+ }
+
+ /* Refuse operating on file systems that have
+ * mandatory locking enabled, see:
+ *
+ * https://github.com/systemd/systemd/issues/1822
+ */
+ if (vfs.f_flag & ST_MANDLOCK) {
+ log_error("Received file descriptor from file system with mandatory locking enable, refusing.");
+ return;
+ }
+
+ /* Make the fd non-blocking. On regular files this has
+ * the effect of bypassing mandatory locking. Of
+ * course, this should normally not be necessary given
+ * the check above, but let's better be safe than
+ * sorry, after all NFS is pretty confusing regarding
+ * file system flags, and we better don't trust it,
+ * and so is SMB. */
+ r = fd_nonblock(fd, true);
+ if (r < 0) {
+ log_error_errno(r, "Failed to make fd non-blocking, ignoring: %m");
+ return;
+ }
+
+ /* The file is not sealed, we can't map the file here, since
+ * clients might then truncate it and trigger a SIGBUS for
+ * us. So let's stupidly read it */
+
+ p = malloc(st.st_size);
+ if (!p) {
+ log_oom();
+ return;
+ }
+
+ n = pread(fd, p, st.st_size, 0);
+ if (n < 0)
+ log_error_errno(errno, "Failed to read file, ignoring: %m");
+ else if (n > 0)
+ server_process_native_message(s, p, n, ucred, tv, label, label_len);
+ }
+}
+
+int server_open_native_socket(Server*s) {
+
+ static const union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/journal/socket",
+ };
+ static const int one = 1;
+ int r;
+
+ assert(s);
+
+ if (s->native_fd < 0) {
+ s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->native_fd < 0)
+ return log_error_errno(errno, "socket() failed: %m");
+
+ (void) unlink(sa.un.sun_path);
+
+ r = bind(s->native_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
+
+ (void) chmod(sa.un.sun_path, 0666);
+ } else
+ fd_nonblock(s->native_fd, 1);
+
+ r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0)
+ return log_error_errno(errno, "SO_PASSCRED failed: %m");
+
+#ifdef HAVE_SELINUX
+ if (mac_selinux_have()) {
+ r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+ if (r < 0)
+ log_warning_errno(errno, "SO_PASSSEC failed: %m");
+ }
+#endif
+
+ r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+ if (r < 0)
+ return log_error_errno(errno, "SO_TIMESTAMP failed: %m");
+
+ r = sd_event_add_io(s->event, &s->native_event_source, s->native_fd, EPOLLIN, server_process_datagram, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add native server fd to event loop: %m");
+
+ r = sd_event_source_set_priority(s->native_event_source, SD_EVENT_PRIORITY_NORMAL+5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust native event source priority: %m");
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/journald-native.h b/src/grp-journal/libjournal-core/journald-native.h
new file mode 100644
index 0000000000..c13b80aa4f
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-native.h
@@ -0,0 +1,35 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "journald-server.h"
+
+/* Make sure not to make this smaller than the maximum coredump
+ * size. See COREDUMP_MAX in coredump.c */
+#define ENTRY_SIZE_MAX (1024*1024*770u)
+#define DATA_SIZE_MAX (1024*1024*768u)
+
+bool valid_user_field(const char *p, size_t l, bool allow_protected);
+
+void server_process_native_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len);
+
+void server_process_native_file(Server *s, int fd, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len);
+
+int server_open_native_socket(Server*s);
diff --git a/src/grp-journal/libjournal-core/journald-rate-limit.c b/src/grp-journal/libjournal-core/journald-rate-limit.c
new file mode 100644
index 0000000000..22603bf8e6
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-rate-limit.c
@@ -0,0 +1,272 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <errno.h>
+#include <string.h>
+
+#include "basic/alloc-util.h"
+#include "basic/hashmap.h"
+#include "basic/list.h"
+#include "basic/random-util.h"
+#include "basic/string-util.h"
+#include "basic/util.h"
+
+#include "journald-rate-limit.h"
+
+#define POOLS_MAX 5
+#define BUCKETS_MAX 127
+#define GROUPS_MAX 2047
+
+static const int priority_map[] = {
+ [LOG_EMERG] = 0,
+ [LOG_ALERT] = 0,
+ [LOG_CRIT] = 0,
+ [LOG_ERR] = 1,
+ [LOG_WARNING] = 2,
+ [LOG_NOTICE] = 3,
+ [LOG_INFO] = 3,
+ [LOG_DEBUG] = 4
+};
+
+typedef struct JournalRateLimitPool JournalRateLimitPool;
+typedef struct JournalRateLimitGroup JournalRateLimitGroup;
+
+struct JournalRateLimitPool {
+ usec_t begin;
+ unsigned num;
+ unsigned suppressed;
+};
+
+struct JournalRateLimitGroup {
+ JournalRateLimit *parent;
+
+ char *id;
+ JournalRateLimitPool pools[POOLS_MAX];
+ uint64_t hash;
+
+ LIST_FIELDS(JournalRateLimitGroup, bucket);
+ LIST_FIELDS(JournalRateLimitGroup, lru);
+};
+
+struct JournalRateLimit {
+ usec_t interval;
+ unsigned burst;
+
+ JournalRateLimitGroup* buckets[BUCKETS_MAX];
+ JournalRateLimitGroup *lru, *lru_tail;
+
+ unsigned n_groups;
+
+ uint8_t hash_key[16];
+};
+
+JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
+ JournalRateLimit *r;
+
+ assert(interval > 0 || burst == 0);
+
+ r = new0(JournalRateLimit, 1);
+ if (!r)
+ return NULL;
+
+ r->interval = interval;
+ r->burst = burst;
+
+ random_bytes(r->hash_key, sizeof(r->hash_key));
+
+ return r;
+}
+
+static void journal_rate_limit_group_free(JournalRateLimitGroup *g) {
+ assert(g);
+
+ if (g->parent) {
+ assert(g->parent->n_groups > 0);
+
+ if (g->parent->lru_tail == g)
+ g->parent->lru_tail = g->lru_prev;
+
+ LIST_REMOVE(lru, g->parent->lru, g);
+ LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g);
+
+ g->parent->n_groups--;
+ }
+
+ free(g->id);
+ free(g);
+}
+
+void journal_rate_limit_free(JournalRateLimit *r) {
+ assert(r);
+
+ while (r->lru)
+ journal_rate_limit_group_free(r->lru);
+
+ free(r);
+}
+
+_pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
+ unsigned i;
+
+ assert(g);
+
+ for (i = 0; i < POOLS_MAX; i++)
+ if (g->pools[i].begin + g->parent->interval >= ts)
+ return false;
+
+ return true;
+}
+
+static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
+ assert(r);
+
+ /* Makes room for at least one new item, but drop all
+ * expored items too. */
+
+ while (r->n_groups >= GROUPS_MAX ||
+ (r->lru_tail && journal_rate_limit_group_expired(r->lru_tail, ts)))
+ journal_rate_limit_group_free(r->lru_tail);
+}
+
+static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
+ JournalRateLimitGroup *g;
+ struct siphash state;
+
+ assert(r);
+ assert(id);
+
+ g = new0(JournalRateLimitGroup, 1);
+ if (!g)
+ return NULL;
+
+ g->id = strdup(id);
+ if (!g->id)
+ goto fail;
+
+ siphash24_init(&state, r->hash_key);
+ string_hash_func(g->id, &state);
+ g->hash = siphash24_finalize(&state);
+
+ journal_rate_limit_vacuum(r, ts);
+
+ LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g);
+ LIST_PREPEND(lru, r->lru, g);
+ if (!g->lru_next)
+ r->lru_tail = g;
+ r->n_groups++;
+
+ g->parent = r;
+ return g;
+
+fail:
+ journal_rate_limit_group_free(g);
+ return NULL;
+}
+
+static unsigned burst_modulate(unsigned burst, uint64_t available) {
+ unsigned k;
+
+ /* Modulates the burst rate a bit with the amount of available
+ * disk space */
+
+ k = u64log2(available);
+
+ /* 1MB */
+ if (k <= 20)
+ return burst;
+
+ burst = (burst * (k-20)) / 4;
+
+ /*
+ * Example:
+ *
+ * <= 1MB = rate * 1
+ * 16MB = rate * 2
+ * 256MB = rate * 3
+ * 4GB = rate * 4
+ * 64GB = rate * 5
+ * 1TB = rate * 6
+ */
+
+ return burst;
+}
+
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
+ uint64_t h;
+ JournalRateLimitGroup *g;
+ JournalRateLimitPool *p;
+ struct siphash state;
+ unsigned burst;
+ usec_t ts;
+
+ assert(id);
+
+ if (!r)
+ return 1;
+
+ if (r->interval == 0 || r->burst == 0)
+ return 1;
+
+ burst = burst_modulate(r->burst, available);
+
+ ts = now(CLOCK_MONOTONIC);
+
+ siphash24_init(&state, r->hash_key);
+ string_hash_func(id, &state);
+ h = siphash24_finalize(&state);
+ g = r->buckets[h % BUCKETS_MAX];
+
+ LIST_FOREACH(bucket, g, g)
+ if (streq(g->id, id))
+ break;
+
+ if (!g) {
+ g = journal_rate_limit_group_new(r, id, ts);
+ if (!g)
+ return -ENOMEM;
+ }
+
+ p = &g->pools[priority_map[priority]];
+
+ if (p->begin <= 0) {
+ p->suppressed = 0;
+ p->num = 1;
+ p->begin = ts;
+ return 1;
+ }
+
+ if (p->begin + r->interval < ts) {
+ unsigned s;
+
+ s = p->suppressed;
+ p->suppressed = 0;
+ p->num = 1;
+ p->begin = ts;
+
+ return 1 + s;
+ }
+
+ if (p->num <= burst) {
+ p->num++;
+ return 1;
+ }
+
+ p->suppressed++;
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/journald-rate-limit.h b/src/grp-journal/libjournal-core/journald-rate-limit.h
new file mode 100644
index 0000000000..533dd0f013
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-rate-limit.h
@@ -0,0 +1,28 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "basic/util.h"
+
+typedef struct JournalRateLimit JournalRateLimit;
+
+JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst);
+void journal_rate_limit_free(JournalRateLimit *r);
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available);
diff --git a/src/grp-journal/libjournal-core/journald-server.c b/src/grp-journal/libjournal-core/journald-server.c
new file mode 100644
index 0000000000..8bc2e55709
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-server.c
@@ -0,0 +1,2009 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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/>.
+***/
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/signalfd.h>
+#include <sys/statvfs.h>
+
+#include <linux/sockios.h>
+
+#include <libudev.h>
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-journal.h>
+#include <systemd/sd-messages.h>
+
+#include "basic/alloc-util.h"
+#include "basic/audit-util.h"
+#include "basic/cgroup-util.h"
+#include "basic/dirent-util.h"
+#include "basic/extract-word.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/formats-util.h"
+#include "basic/fs-util.h"
+#include "basic/hashmap.h"
+#include "basic/hostname-util.h"
+#include "basic/io-util.h"
+#include "basic/log.h"
+#include "basic/missing.h"
+#include "basic/mkdir.h"
+#include "basic/parse-util.h"
+#include "basic/proc-cmdline.h"
+#include "basic/process-util.h"
+#include "basic/rm-rf.h"
+#include "basic/selinux-util.h"
+#include "basic/signal-util.h"
+#include "basic/socket-util.h"
+#include "basic/stdio-util.h"
+#include "basic/string-table.h"
+#include "basic/string-util.h"
+#include "basic/user-util.h"
+#include "sd-journal/journal-authenticate.h"
+#include "sd-journal/journal-file.h"
+#include "sd-journal/journal-internal.h"
+#include "sd-journal/journal-vacuum.h"
+#include "shared/acl-util.h"
+#include "shared/conf-parser.h"
+
+#include "journald-audit.h"
+#include "journald-kmsg.h"
+#include "journald-native.h"
+#include "journald-rate-limit.h"
+#include "journald-server.h"
+#include "journald-stream.h"
+#include "journald-syslog.h"
+
+#define USER_JOURNALS_MAX 1024
+
+#define DEFAULT_SYNC_INTERVAL_USEC (5*USEC_PER_MINUTE)
+#define DEFAULT_RATE_LIMIT_INTERVAL (30*USEC_PER_SEC)
+#define DEFAULT_RATE_LIMIT_BURST 1000
+#define DEFAULT_MAX_FILE_USEC USEC_PER_MONTH
+
+#define RECHECK_SPACE_USEC (30*USEC_PER_SEC)
+
+#define NOTIFY_SNDBUF_SIZE (8*1024*1024)
+
+/* The period to insert between posting changes for coalescing */
+#define POST_CHANGE_TIMER_INTERVAL_USEC (250*USEC_PER_MSEC)
+
+static int determine_space_for(
+ Server *s,
+ JournalMetrics *metrics,
+ const char *path,
+ const char *name,
+ bool verbose,
+ bool patch_min_use,
+ uint64_t *available,
+ uint64_t *limit) {
+
+ uint64_t sum = 0, ss_avail, avail;
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+ struct statvfs ss;
+ const char *p;
+ usec_t ts;
+
+ assert(s);
+ assert(metrics);
+ assert(path);
+ assert(name);
+
+ ts = now(CLOCK_MONOTONIC);
+
+ if (!verbose && s->cached_space_timestamp + RECHECK_SPACE_USEC > ts) {
+
+ if (available)
+ *available = s->cached_space_available;
+ if (limit)
+ *limit = s->cached_space_limit;
+
+ return 0;
+ }
+
+ p = strjoina(path, SERVER_MACHINE_ID(s));
+ d = opendir(p);
+ if (!d)
+ return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open %s: %m", p);
+
+ if (fstatvfs(dirfd(d), &ss) < 0)
+ return log_error_errno(errno, "Failed to fstatvfs(%s): %m", p);
+
+ FOREACH_DIRENT_ALL(de, d, break) {
+ struct stat st;
+
+ if (!endswith(de->d_name, ".journal") &&
+ !endswith(de->d_name, ".journal~"))
+ continue;
+
+ if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+ log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", p, de->d_name);
+ continue;
+ }
+
+ if (!S_ISREG(st.st_mode))
+ continue;
+
+ sum += (uint64_t) st.st_blocks * 512UL;
+ }
+
+ /* If requested, then let's bump the min_use limit to the
+ * current usage on disk. We do this when starting up and
+ * first opening the journal files. This way sudden spikes in
+ * disk usage will not cause journald to vacuum files without
+ * bounds. Note that this means that only a restart of
+ * journald will make it reset this value. */
+
+ if (patch_min_use)
+ metrics->min_use = MAX(metrics->min_use, sum);
+
+ ss_avail = ss.f_bsize * ss.f_bavail;
+ avail = LESS_BY(ss_avail, metrics->keep_free);
+
+ s->cached_space_limit = MIN(MAX(sum + avail, metrics->min_use), metrics->max_use);
+ s->cached_space_available = LESS_BY(s->cached_space_limit, sum);
+ s->cached_space_timestamp = ts;
+
+ if (verbose) {
+ char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX],
+ fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX], fb6[FORMAT_BYTES_MAX];
+ format_bytes(fb1, sizeof(fb1), sum);
+ format_bytes(fb2, sizeof(fb2), metrics->max_use);
+ format_bytes(fb3, sizeof(fb3), metrics->keep_free);
+ format_bytes(fb4, sizeof(fb4), ss_avail);
+ format_bytes(fb5, sizeof(fb5), s->cached_space_limit);
+ format_bytes(fb6, sizeof(fb6), s->cached_space_available);
+
+ server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE,
+ LOG_MESSAGE("%s (%s) is %s, max %s, %s free.",
+ name, path, fb1, fb5, fb6),
+ "JOURNAL_NAME=%s", name,
+ "JOURNAL_PATH=%s", path,
+ "CURRENT_USE=%"PRIu64, sum,
+ "CURRENT_USE_PRETTY=%s", fb1,
+ "MAX_USE=%"PRIu64, metrics->max_use,
+ "MAX_USE_PRETTY=%s", fb2,
+ "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free,
+ "DISK_KEEP_FREE_PRETTY=%s", fb3,
+ "DISK_AVAILABLE=%"PRIu64, ss_avail,
+ "DISK_AVAILABLE_PRETTY=%s", fb4,
+ "LIMIT=%"PRIu64, s->cached_space_limit,
+ "LIMIT_PRETTY=%s", fb5,
+ "AVAILABLE=%"PRIu64, s->cached_space_available,
+ "AVAILABLE_PRETTY=%s", fb6,
+ NULL);
+ }
+
+ if (available)
+ *available = s->cached_space_available;
+ if (limit)
+ *limit = s->cached_space_limit;
+
+ return 1;
+}
+
+static int determine_space(Server *s, bool verbose, bool patch_min_use, uint64_t *available, uint64_t *limit) {
+ JournalMetrics *metrics;
+ const char *path, *name;
+
+ assert(s);
+
+ if (s->system_journal) {
+ path = "/var/log/journal/";
+ metrics = &s->system_metrics;
+ name = "System journal";
+ } else {
+ path = "/run/log/journal/";
+ metrics = &s->runtime_metrics;
+ name = "Runtime journal";
+ }
+
+ return determine_space_for(s, metrics, path, name, verbose, patch_min_use, available, limit);
+}
+
+static void server_add_acls(JournalFile *f, uid_t uid) {
+#ifdef HAVE_ACL
+ int r;
+#endif
+ assert(f);
+
+#ifdef HAVE_ACL
+ if (uid <= SYSTEM_UID_MAX)
+ return;
+
+ r = add_acls_for_user(f->fd, uid);
+ if (r < 0)
+ log_warning_errno(r, "Failed to set ACL on %s, ignoring: %m", f->path);
+#endif
+}
+
+static int open_journal(
+ Server *s,
+ bool reliably,
+ const char *fname,
+ int flags,
+ bool seal,
+ JournalMetrics *metrics,
+ JournalFile **ret) {
+ int r;
+ JournalFile *f;
+
+ assert(s);
+ assert(fname);
+ assert(ret);
+
+ if (reliably)
+ r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f);
+ else
+ r = journal_file_open(-1, fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f);
+ if (r < 0)
+ return r;
+
+ r = journal_file_enable_post_change_timer(f, s->event, POST_CHANGE_TIMER_INTERVAL_USEC);
+ if (r < 0) {
+ (void) journal_file_close(f);
+ return r;
+ }
+
+ *ret = f;
+ return r;
+}
+
+static JournalFile* find_journal(Server *s, uid_t uid) {
+ _cleanup_free_ char *p = NULL;
+ int r;
+ JournalFile *f;
+ sd_id128_t machine;
+
+ assert(s);
+
+ /* We split up user logs only on /var, not on /run. If the
+ * runtime file is open, we write to it exclusively, in order
+ * to guarantee proper order as soon as we flush /run to
+ * /var and close the runtime file. */
+
+ if (s->runtime_journal)
+ return s->runtime_journal;
+
+ if (uid <= SYSTEM_UID_MAX)
+ return s->system_journal;
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return s->system_journal;
+
+ f = ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid));
+ if (f)
+ return f;
+
+ if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-"UID_FMT".journal",
+ SD_ID128_FORMAT_VAL(machine), uid) < 0)
+ return s->system_journal;
+
+ while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
+ /* Too many open? Then let's close one */
+ f = ordered_hashmap_steal_first(s->user_journals);
+ assert(f);
+ (void) journal_file_close(f);
+ }
+
+ r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &f);
+ if (r < 0)
+ return s->system_journal;
+
+ server_add_acls(f, uid);
+
+ r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f);
+ if (r < 0) {
+ (void) journal_file_close(f);
+ return s->system_journal;
+ }
+
+ return f;
+}
+
+static int do_rotate(
+ Server *s,
+ JournalFile **f,
+ const char* name,
+ bool seal,
+ uint32_t uid) {
+
+ int r;
+ assert(s);
+
+ if (!*f)
+ return -EINVAL;
+
+ r = journal_file_rotate(f, s->compress, seal, s->deferred_closes);
+ if (r < 0)
+ if (*f)
+ log_error_errno(r, "Failed to rotate %s: %m", (*f)->path);
+ else
+ log_error_errno(r, "Failed to create new %s journal: %m", name);
+ else
+ server_add_acls(*f, uid);
+
+ return r;
+}
+
+void server_rotate(Server *s) {
+ JournalFile *f;
+ void *k;
+ Iterator i;
+ int r;
+
+ log_debug("Rotating...");
+
+ (void) do_rotate(s, &s->runtime_journal, "runtime", false, 0);
+ (void) do_rotate(s, &s->system_journal, "system", s->seal, 0);
+
+ ORDERED_HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) {
+ r = do_rotate(s, &f, "user", s->seal, PTR_TO_UID(k));
+ if (r >= 0)
+ ordered_hashmap_replace(s->user_journals, k, f);
+ else if (!f)
+ /* Old file has been closed and deallocated */
+ ordered_hashmap_remove(s->user_journals, k);
+ }
+
+ /* Perform any deferred closes which aren't still offlining. */
+ SET_FOREACH(f, s->deferred_closes, i)
+ if (!journal_file_is_offlining(f)) {
+ (void) set_remove(s->deferred_closes, f);
+ (void) journal_file_close(f);
+ }
+}
+
+void server_sync(Server *s) {
+ JournalFile *f;
+ Iterator i;
+ int r;
+
+ if (s->system_journal) {
+ r = journal_file_set_offline(s->system_journal, false);
+ if (r < 0)
+ log_warning_errno(r, "Failed to sync system journal, ignoring: %m");
+ }
+
+ ORDERED_HASHMAP_FOREACH(f, s->user_journals, i) {
+ r = journal_file_set_offline(f, false);
+ if (r < 0)
+ log_warning_errno(r, "Failed to sync user journal, ignoring: %m");
+ }
+
+ if (s->sync_event_source) {
+ r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_OFF);
+ if (r < 0)
+ log_error_errno(r, "Failed to disable sync timer source: %m");
+ }
+
+ s->sync_scheduled = false;
+}
+
+static void do_vacuum(
+ Server *s,
+ JournalFile *f,
+ JournalMetrics *metrics,
+ const char *path,
+ const char *name,
+ bool verbose,
+ bool patch_min_use) {
+
+ const char *p;
+ uint64_t limit;
+ int r;
+
+ assert(s);
+ assert(metrics);
+ assert(path);
+ assert(name);
+
+ if (!f)
+ return;
+
+ p = strjoina(path, SERVER_MACHINE_ID(s));
+
+ limit = metrics->max_use;
+ (void) determine_space_for(s, metrics, path, name, verbose, patch_min_use, NULL, &limit);
+
+ r = journal_directory_vacuum(p, limit, metrics->n_max_files, s->max_retention_usec, &s->oldest_file_usec, verbose);
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to vacuum %s, ignoring: %m", p);
+}
+
+int server_vacuum(Server *s, bool verbose, bool patch_min_use) {
+ assert(s);
+
+ log_debug("Vacuuming...");
+
+ s->oldest_file_usec = 0;
+
+ do_vacuum(s, s->system_journal, &s->system_metrics, "/var/log/journal/", "System journal", verbose, patch_min_use);
+ do_vacuum(s, s->runtime_journal, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", verbose, patch_min_use);
+
+ s->cached_space_limit = 0;
+ s->cached_space_available = 0;
+ s->cached_space_timestamp = 0;
+
+ return 0;
+}
+
+static void server_cache_machine_id(Server *s) {
+ sd_id128_t id;
+ int r;
+
+ assert(s);
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return;
+
+ sd_id128_to_string(id, stpcpy(s->machine_id_field, "_MACHINE_ID="));
+}
+
+static void server_cache_boot_id(Server *s) {
+ sd_id128_t id;
+ int r;
+
+ assert(s);
+
+ r = sd_id128_get_boot(&id);
+ if (r < 0)
+ return;
+
+ sd_id128_to_string(id, stpcpy(s->boot_id_field, "_BOOT_ID="));
+}
+
+static void server_cache_hostname(Server *s) {
+ _cleanup_free_ char *t = NULL;
+ char *x;
+
+ assert(s);
+
+ t = gethostname_malloc();
+ if (!t)
+ return;
+
+ x = strappend("_HOSTNAME=", t);
+ if (!x)
+ return;
+
+ free(s->hostname_field);
+ s->hostname_field = x;
+}
+
+static bool shall_try_append_again(JournalFile *f, int r) {
+ switch(r) {
+ case -E2BIG: /* Hit configured limit */
+ case -EFBIG: /* Hit fs limit */
+ case -EDQUOT: /* Quota limit hit */
+ case -ENOSPC: /* Disk full */
+ log_debug("%s: Allocation limit reached, rotating.", f->path);
+ return true;
+ case -EIO: /* I/O error of some kind (mmap) */
+ log_warning("%s: IO error, rotating.", f->path);
+ return true;
+ case -EHOSTDOWN: /* Other machine */
+ log_info("%s: Journal file from other machine, rotating.", f->path);
+ return true;
+ case -EBUSY: /* Unclean shutdown */
+ log_info("%s: Unclean shutdown, rotating.", f->path);
+ return true;
+ case -EPROTONOSUPPORT: /* Unsupported feature */
+ log_info("%s: Unsupported feature, rotating.", f->path);
+ return true;
+ case -EBADMSG: /* Corrupted */
+ case -ENODATA: /* Truncated */
+ case -ESHUTDOWN: /* Already archived */
+ log_warning("%s: Journal file corrupted, rotating.", f->path);
+ return true;
+ case -EIDRM: /* Journal file has been deleted */
+ log_warning("%s: Journal file has been deleted, rotating.", f->path);
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n, int priority) {
+ JournalFile *f;
+ bool vacuumed = false;
+ int r;
+
+ assert(s);
+ assert(iovec);
+ assert(n > 0);
+
+ f = find_journal(s, uid);
+ if (!f)
+ return;
+
+ if (journal_file_rotate_suggested(f, s->max_file_usec)) {
+ log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path);
+ server_rotate(s);
+ server_vacuum(s, false, false);
+ vacuumed = true;
+
+ f = find_journal(s, uid);
+ if (!f)
+ return;
+ }
+
+ r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL);
+ if (r >= 0) {
+ server_schedule_sync(s, priority);
+ return;
+ }
+
+ if (vacuumed || !shall_try_append_again(f, r)) {
+ log_error_errno(r, "Failed to write entry (%d items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
+ return;
+ }
+
+ server_rotate(s);
+ server_vacuum(s, false, false);
+
+ f = find_journal(s, uid);
+ if (!f)
+ return;
+
+ log_debug("Retrying write.");
+ r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL);
+ if (r < 0)
+ log_error_errno(r, "Failed to write entry (%d items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
+ else
+ server_schedule_sync(s, priority);
+}
+
+static void dispatch_message_real(
+ Server *s,
+ struct iovec *iovec, unsigned n, unsigned m,
+ const struct ucred *ucred,
+ const struct timeval *tv,
+ const char *label, size_t label_len,
+ const char *unit_id,
+ int priority,
+ pid_t object_pid) {
+
+ char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)],
+ uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)],
+ gid[sizeof("_GID=") + DECIMAL_STR_MAX(gid_t)],
+ owner_uid[sizeof("_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)],
+ source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)],
+ o_uid[sizeof("OBJECT_UID=") + DECIMAL_STR_MAX(uid_t)],
+ o_gid[sizeof("OBJECT_GID=") + DECIMAL_STR_MAX(gid_t)],
+ o_owner_uid[sizeof("OBJECT_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)];
+ uid_t object_uid;
+ gid_t object_gid;
+ char *x;
+ int r;
+ char *t, *c;
+ uid_t realuid = 0, owner = 0, journal_uid;
+ bool owner_valid = false;
+#ifdef HAVE_AUDIT
+ char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)],
+ audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)],
+ o_audit_session[sizeof("OBJECT_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)],
+ o_audit_loginuid[sizeof("OBJECT_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)];
+
+ uint32_t audit;
+ uid_t loginuid;
+#endif
+
+ assert(s);
+ assert(iovec);
+ assert(n > 0);
+ assert(n + N_IOVEC_META_FIELDS + (object_pid ? N_IOVEC_OBJECT_FIELDS : 0) <= m);
+
+ if (ucred) {
+ realuid = ucred->uid;
+
+ sprintf(pid, "_PID="PID_FMT, ucred->pid);
+ IOVEC_SET_STRING(iovec[n++], pid);
+
+ sprintf(uid, "_UID="UID_FMT, ucred->uid);
+ IOVEC_SET_STRING(iovec[n++], uid);
+
+ sprintf(gid, "_GID="GID_FMT, ucred->gid);
+ IOVEC_SET_STRING(iovec[n++], gid);
+
+ r = get_process_comm(ucred->pid, &t);
+ if (r >= 0) {
+ x = strjoina("_COMM=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ r = get_process_exe(ucred->pid, &t);
+ if (r >= 0) {
+ x = strjoina("_EXE=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ r = get_process_cmdline(ucred->pid, 0, false, &t);
+ if (r >= 0) {
+ x = strjoina("_CMDLINE=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ r = get_process_capeff(ucred->pid, &t);
+ if (r >= 0) {
+ x = strjoina("_CAP_EFFECTIVE=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+#ifdef HAVE_AUDIT
+ r = audit_session_from_pid(ucred->pid, &audit);
+ if (r >= 0) {
+ sprintf(audit_session, "_AUDIT_SESSION=%"PRIu32, audit);
+ IOVEC_SET_STRING(iovec[n++], audit_session);
+ }
+
+ r = audit_loginuid_from_pid(ucred->pid, &loginuid);
+ if (r >= 0) {
+ sprintf(audit_loginuid, "_AUDIT_LOGINUID="UID_FMT, loginuid);
+ IOVEC_SET_STRING(iovec[n++], audit_loginuid);
+ }
+#endif
+
+ r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c);
+ if (r >= 0) {
+ char *session = NULL;
+
+ x = strjoina("_SYSTEMD_CGROUP=", c);
+ IOVEC_SET_STRING(iovec[n++], x);
+
+ r = cg_path_get_session(c, &t);
+ if (r >= 0) {
+ session = strjoina("_SYSTEMD_SESSION=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], session);
+ }
+
+ if (cg_path_get_owner_uid(c, &owner) >= 0) {
+ owner_valid = true;
+
+ sprintf(owner_uid, "_SYSTEMD_OWNER_UID="UID_FMT, owner);
+ IOVEC_SET_STRING(iovec[n++], owner_uid);
+ }
+
+ if (cg_path_get_unit(c, &t) >= 0) {
+ x = strjoina("_SYSTEMD_UNIT=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ } else if (unit_id && !session) {
+ x = strjoina("_SYSTEMD_UNIT=", unit_id);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ if (cg_path_get_user_unit(c, &t) >= 0) {
+ x = strjoina("_SYSTEMD_USER_UNIT=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ } else if (unit_id && session) {
+ x = strjoina("_SYSTEMD_USER_UNIT=", unit_id);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ if (cg_path_get_slice(c, &t) >= 0) {
+ x = strjoina("_SYSTEMD_SLICE=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ free(c);
+ } else if (unit_id) {
+ x = strjoina("_SYSTEMD_UNIT=", unit_id);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+#ifdef HAVE_SELINUX
+ if (mac_selinux_have()) {
+ if (label) {
+ x = alloca(strlen("_SELINUX_CONTEXT=") + label_len + 1);
+
+ *((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0;
+ IOVEC_SET_STRING(iovec[n++], x);
+ } else {
+ security_context_t con;
+
+ if (getpidcon(ucred->pid, &con) >= 0) {
+ x = strjoina("_SELINUX_CONTEXT=", con);
+
+ freecon(con);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+ }
+ }
+#endif
+ }
+ assert(n <= m);
+
+ if (object_pid) {
+ r = get_process_uid(object_pid, &object_uid);
+ if (r >= 0) {
+ sprintf(o_uid, "OBJECT_UID="UID_FMT, object_uid);
+ IOVEC_SET_STRING(iovec[n++], o_uid);
+ }
+
+ r = get_process_gid(object_pid, &object_gid);
+ if (r >= 0) {
+ sprintf(o_gid, "OBJECT_GID="GID_FMT, object_gid);
+ IOVEC_SET_STRING(iovec[n++], o_gid);
+ }
+
+ r = get_process_comm(object_pid, &t);
+ if (r >= 0) {
+ x = strjoina("OBJECT_COMM=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ r = get_process_exe(object_pid, &t);
+ if (r >= 0) {
+ x = strjoina("OBJECT_EXE=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ r = get_process_cmdline(object_pid, 0, false, &t);
+ if (r >= 0) {
+ x = strjoina("OBJECT_CMDLINE=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+#ifdef HAVE_AUDIT
+ r = audit_session_from_pid(object_pid, &audit);
+ if (r >= 0) {
+ sprintf(o_audit_session, "OBJECT_AUDIT_SESSION=%"PRIu32, audit);
+ IOVEC_SET_STRING(iovec[n++], o_audit_session);
+ }
+
+ r = audit_loginuid_from_pid(object_pid, &loginuid);
+ if (r >= 0) {
+ sprintf(o_audit_loginuid, "OBJECT_AUDIT_LOGINUID="UID_FMT, loginuid);
+ IOVEC_SET_STRING(iovec[n++], o_audit_loginuid);
+ }
+#endif
+
+ r = cg_pid_get_path_shifted(object_pid, s->cgroup_root, &c);
+ if (r >= 0) {
+ x = strjoina("OBJECT_SYSTEMD_CGROUP=", c);
+ IOVEC_SET_STRING(iovec[n++], x);
+
+ r = cg_path_get_session(c, &t);
+ if (r >= 0) {
+ x = strjoina("OBJECT_SYSTEMD_SESSION=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ if (cg_path_get_owner_uid(c, &owner) >= 0) {
+ sprintf(o_owner_uid, "OBJECT_SYSTEMD_OWNER_UID="UID_FMT, owner);
+ IOVEC_SET_STRING(iovec[n++], o_owner_uid);
+ }
+
+ if (cg_path_get_unit(c, &t) >= 0) {
+ x = strjoina("OBJECT_SYSTEMD_UNIT=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ if (cg_path_get_user_unit(c, &t) >= 0) {
+ x = strjoina("OBJECT_SYSTEMD_USER_UNIT=", t);
+ free(t);
+ IOVEC_SET_STRING(iovec[n++], x);
+ }
+
+ free(c);
+ }
+ }
+ assert(n <= m);
+
+ if (tv) {
+ sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=%llu", (unsigned long long) timeval_load(tv));
+ IOVEC_SET_STRING(iovec[n++], source_time);
+ }
+
+ /* Note that strictly speaking storing the boot id here is
+ * redundant since the entry includes this in-line
+ * anyway. However, we need this indexed, too. */
+ if (!isempty(s->boot_id_field))
+ IOVEC_SET_STRING(iovec[n++], s->boot_id_field);
+
+ if (!isempty(s->machine_id_field))
+ IOVEC_SET_STRING(iovec[n++], s->machine_id_field);
+
+ if (!isempty(s->hostname_field))
+ IOVEC_SET_STRING(iovec[n++], s->hostname_field);
+
+ assert(n <= m);
+
+ if (s->split_mode == SPLIT_UID && realuid > 0)
+ /* Split up strictly by any UID */
+ journal_uid = realuid;
+ else if (s->split_mode == SPLIT_LOGIN && realuid > 0 && owner_valid && owner > 0)
+ /* Split up by login UIDs. We do this only if the
+ * realuid is not root, in order not to accidentally
+ * leak privileged information to the user that is
+ * logged by a privileged process that is part of an
+ * unprivileged session. */
+ journal_uid = owner;
+ else
+ journal_uid = 0;
+
+ write_to_journal(s, journal_uid, iovec, n, priority);
+}
+
+void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) {
+ char mid[11 + 32 + 1];
+ struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS];
+ unsigned n = 0, m;
+ int r;
+ va_list ap;
+ struct ucred ucred = {};
+
+ assert(s);
+ assert(format);
+
+ assert_cc(3 == LOG_FAC(LOG_DAEMON));
+ IOVEC_SET_STRING(iovec[n++], "SYSLOG_FACILITY=3");
+ IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=systemd-journald");
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver");
+ assert_cc(6 == LOG_INFO);
+ IOVEC_SET_STRING(iovec[n++], "PRIORITY=6");
+
+ if (!sd_id128_equal(message_id, SD_ID128_NULL)) {
+ snprintf(mid, sizeof(mid), LOG_MESSAGE_ID(message_id));
+ IOVEC_SET_STRING(iovec[n++], mid);
+ }
+
+ m = n;
+
+ va_start(ap, format);
+ r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, false, 0, format, ap);
+ /* Error handling below */
+ va_end(ap);
+
+ ucred.pid = getpid();
+ ucred.uid = getuid();
+ ucred.gid = getgid();
+
+ if (r >= 0)
+ dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0);
+
+ while (m < n)
+ free(iovec[m++].iov_base);
+
+ if (r < 0) {
+ /* We failed to format the message. Emit a warning instead. */
+ char buf[LINE_MAX];
+
+ xsprintf(buf, "MESSAGE=Entry printing failed: %s", strerror(-r));
+
+ n = 3;
+ IOVEC_SET_STRING(iovec[n++], "PRIORITY=4");
+ IOVEC_SET_STRING(iovec[n++], buf);
+ dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0);
+ }
+}
+
+void server_dispatch_message(
+ Server *s,
+ struct iovec *iovec, unsigned n, unsigned m,
+ const struct ucred *ucred,
+ const struct timeval *tv,
+ const char *label, size_t label_len,
+ const char *unit_id,
+ int priority,
+ pid_t object_pid) {
+
+ int rl, r;
+ _cleanup_free_ char *path = NULL;
+ uint64_t available = 0;
+ char *c;
+
+ assert(s);
+ assert(iovec || n == 0);
+
+ if (n == 0)
+ return;
+
+ if (LOG_PRI(priority) > s->max_level_store)
+ return;
+
+ /* Stop early in case the information will not be stored
+ * in a journal. */
+ if (s->storage == STORAGE_NONE)
+ return;
+
+ if (!ucred)
+ goto finish;
+
+ r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &path);
+ if (r < 0)
+ goto finish;
+
+ /* example: /user/lennart/3/foobar
+ * /system/dbus.service/foobar
+ *
+ * So let's cut of everything past the third /, since that is
+ * where user directories start */
+
+ c = strchr(path, '/');
+ if (c) {
+ c = strchr(c+1, '/');
+ if (c) {
+ c = strchr(c+1, '/');
+ if (c)
+ *c = 0;
+ }
+ }
+
+ (void) determine_space(s, false, false, &available, NULL);
+ rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available);
+ if (rl == 0)
+ return;
+
+ /* Write a suppression message if we suppressed something */
+ if (rl > 1)
+ server_driver_message(s, SD_MESSAGE_JOURNAL_DROPPED,
+ LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, path),
+ NULL);
+
+finish:
+ dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid);
+}
+
+
+static int system_journal_open(Server *s, bool flush_requested) {
+ const char *fn;
+ int r = 0;
+
+ if (!s->system_journal &&
+ (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) &&
+ (flush_requested
+ || access("/run/systemd/journal/flushed", F_OK) >= 0)) {
+
+ /* If in auto mode: first try to create the machine
+ * path, but not the prefix.
+ *
+ * If in persistent mode: create /var/log/journal and
+ * the machine path */
+
+ if (s->storage == STORAGE_PERSISTENT)
+ (void) mkdir_p("/var/log/journal/", 0755);
+
+ fn = strjoina("/var/log/journal/", SERVER_MACHINE_ID(s));
+ (void) mkdir(fn, 0755);
+
+ fn = strjoina(fn, "/system.journal");
+ r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_metrics, &s->system_journal);
+ if (r >= 0) {
+ server_add_acls(s->system_journal, 0);
+ (void) determine_space_for(s, &s->system_metrics, "/var/log/journal/", "System journal", true, true, NULL, NULL);
+ } else if (r < 0) {
+ if (r != -ENOENT && r != -EROFS)
+ log_warning_errno(r, "Failed to open system journal: %m");
+
+ r = 0;
+ }
+ }
+
+ if (!s->runtime_journal &&
+ (s->storage != STORAGE_NONE)) {
+
+ fn = strjoina("/run/log/journal/", SERVER_MACHINE_ID(s), "/system.journal");
+
+ if (s->system_journal) {
+
+ /* Try to open the runtime journal, but only
+ * if it already exists, so that we can flush
+ * it into the system journal */
+
+ r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_metrics, &s->runtime_journal);
+ if (r < 0) {
+ if (r != -ENOENT)
+ log_warning_errno(r, "Failed to open runtime journal: %m");
+
+ r = 0;
+ }
+
+ } else {
+
+ /* OK, we really need the runtime journal, so create
+ * it if necessary. */
+
+ (void) mkdir("/run/log", 0755);
+ (void) mkdir("/run/log/journal", 0755);
+ (void) mkdir_parents(fn, 0750);
+
+ r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_metrics, &s->runtime_journal);
+ if (r < 0)
+ return log_error_errno(r, "Failed to open runtime journal: %m");
+ }
+
+ if (s->runtime_journal) {
+ server_add_acls(s->runtime_journal, 0);
+ (void) determine_space_for(s, &s->runtime_metrics, "/run/log/journal/", "Runtime journal", true, true, NULL, NULL);
+ }
+ }
+
+ return r;
+}
+
+int server_flush_to_var(Server *s) {
+ sd_id128_t machine;
+ sd_journal *j = NULL;
+ char ts[FORMAT_TIMESPAN_MAX];
+ usec_t start;
+ unsigned n = 0;
+ int r;
+
+ assert(s);
+
+ if (s->storage != STORAGE_AUTO &&
+ s->storage != STORAGE_PERSISTENT)
+ return 0;
+
+ if (!s->runtime_journal)
+ return 0;
+
+ (void) system_journal_open(s, true);
+
+ if (!s->system_journal)
+ return 0;
+
+ log_debug("Flushing to /var...");
+
+ start = now(CLOCK_MONOTONIC);
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return r;
+
+ r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read runtime journal: %m");
+
+ sd_journal_set_data_threshold(j, 0);
+
+ SD_JOURNAL_FOREACH(j) {
+ Object *o = NULL;
+ JournalFile *f;
+
+ f = j->current_file;
+ assert(f && f->current_offset > 0);
+
+ n++;
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ if (r < 0) {
+ log_error_errno(r, "Can't read entry: %m");
+ goto finish;
+ }
+
+ r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+ if (r >= 0)
+ continue;
+
+ if (!shall_try_append_again(s->system_journal, r)) {
+ log_error_errno(r, "Can't write entry: %m");
+ goto finish;
+ }
+
+ server_rotate(s);
+ server_vacuum(s, false, false);
+
+ if (!s->system_journal) {
+ log_notice("Didn't flush runtime journal since rotation of system journal wasn't successful.");
+ r = -EIO;
+ goto finish;
+ }
+
+ log_debug("Retrying write.");
+ r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Can't write entry: %m");
+ goto finish;
+ }
+ }
+
+ r = 0;
+
+finish:
+ journal_file_post_change(s->system_journal);
+
+ s->runtime_journal = journal_file_close(s->runtime_journal);
+
+ if (r >= 0)
+ (void) rm_rf("/run/log/journal", REMOVE_ROOT);
+
+ sd_journal_close(j);
+
+ server_driver_message(s, SD_ID128_NULL,
+ LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.",
+ format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0),
+ n),
+ NULL);
+
+ return r;
+}
+
+int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Server *s = userdata;
+ struct ucred *ucred = NULL;
+ struct timeval *tv = NULL;
+ struct cmsghdr *cmsg;
+ char *label = NULL;
+ size_t label_len = 0, m;
+ struct iovec iovec;
+ ssize_t n;
+ int *fds = NULL, v = 0;
+ unsigned n_fds = 0;
+
+ union {
+ struct cmsghdr cmsghdr;
+
+ /* We use NAME_MAX space for the SELinux label
+ * here. The kernel currently enforces no
+ * limit, but according to suggestions from
+ * the SELinux people this will change and it
+ * will probably be identical to NAME_MAX. For
+ * now we use that, but this should be updated
+ * one day when the final limit is known. */
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(sizeof(struct timeval)) +
+ CMSG_SPACE(sizeof(int)) + /* fd */
+ CMSG_SPACE(NAME_MAX)]; /* selinux label */
+ } control = {};
+
+ union sockaddr_union sa = {};
+
+ struct msghdr msghdr = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ .msg_name = &sa,
+ .msg_namelen = sizeof(sa),
+ };
+
+ assert(s);
+ assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd);
+
+ if (revents != EPOLLIN) {
+ log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents);
+ return -EIO;
+ }
+
+ /* Try to get the right size, if we can. (Not all
+ * sockets support SIOCINQ, hence we just try, but
+ * don't rely on it. */
+ (void) ioctl(fd, SIOCINQ, &v);
+
+ /* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful! */
+ m = PAGE_ALIGN(MAX3((size_t) v + 1,
+ (size_t) LINE_MAX,
+ ALIGN(sizeof(struct nlmsghdr)) + ALIGN((size_t) MAX_AUDIT_MESSAGE_LENGTH)) + 1);
+
+ if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m))
+ return log_oom();
+
+ iovec.iov_base = s->buffer;
+ iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */
+
+ n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (n < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ return 0;
+
+ return log_error_errno(errno, "recvmsg() failed: %m");
+ }
+
+ CMSG_FOREACH(cmsg, &msghdr) {
+
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred)))
+ ucred = (struct ucred*) CMSG_DATA(cmsg);
+ else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_SECURITY) {
+ label = (char*) CMSG_DATA(cmsg);
+ label_len = cmsg->cmsg_len - CMSG_LEN(0);
+ } else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SO_TIMESTAMP &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
+ tv = (struct timeval*) CMSG_DATA(cmsg);
+ else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS) {
+ fds = (int*) CMSG_DATA(cmsg);
+ n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+ }
+ }
+
+ /* And a trailing NUL, just in case */
+ s->buffer[n] = 0;
+
+ if (fd == s->syslog_fd) {
+ if (n > 0 && n_fds == 0)
+ server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
+ else if (n_fds > 0)
+ log_warning("Got file descriptors via syslog socket. Ignoring.");
+
+ } else if (fd == s->native_fd) {
+ if (n > 0 && n_fds == 0)
+ server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
+ else if (n == 0 && n_fds == 1)
+ server_process_native_file(s, fds[0], ucred, tv, label, label_len);
+ else if (n_fds > 0)
+ log_warning("Got too many file descriptors via native socket. Ignoring.");
+
+ } else {
+ assert(fd == s->audit_fd);
+
+ if (n > 0 && n_fds == 0)
+ server_process_audit_message(s, s->buffer, n, ucred, &sa, msghdr.msg_namelen);
+ else if (n_fds > 0)
+ log_warning("Got file descriptors via audit socket. Ignoring.");
+ }
+
+ close_many(fds, n_fds);
+ return 0;
+}
+
+static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+ Server *s = userdata;
+ int r;
+
+ assert(s);
+
+ log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid);
+
+ server_flush_to_var(s);
+ server_sync(s);
+ server_vacuum(s, false, false);
+
+ r = touch("/run/systemd/journal/flushed");
+ if (r < 0)
+ log_warning_errno(r, "Failed to touch /run/systemd/journal/flushed, ignoring: %m");
+
+ return 0;
+}
+
+static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+ Server *s = userdata;
+ int r;
+
+ assert(s);
+
+ log_info("Received request to rotate journal from PID " PID_FMT, si->ssi_pid);
+ server_rotate(s);
+ server_vacuum(s, true, true);
+
+ /* Let clients know when the most recent rotation happened. */
+ r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC));
+ if (r < 0)
+ log_warning_errno(r, "Failed to write /run/systemd/journal/rotated, ignoring: %m");
+
+ return 0;
+}
+
+static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+ Server *s = userdata;
+
+ assert(s);
+
+ log_received_signal(LOG_INFO, si);
+
+ sd_event_exit(s->event, 0);
+ return 0;
+}
+
+static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
+ Server *s = userdata;
+ int r;
+
+ assert(s);
+
+ log_debug("Received request to sync from PID " PID_FMT, si->ssi_pid);
+
+ server_sync(s);
+
+ /* Let clients know when the most recent sync happened. */
+ r = write_timestamp_file_atomic("/run/systemd/journal/synced", now(CLOCK_MONOTONIC));
+ if (r < 0)
+ log_warning_errno(r, "Failed to write /run/systemd/journal/synced, ignoring: %m");
+
+ return 0;
+}
+
+static int setup_signals(Server *s) {
+ int r;
+
+ assert(s);
+
+ assert(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGRTMIN+1, -1) >= 0);
+
+ r = sd_event_add_signal(s->event, &s->sigusr1_event_source, SIGUSR1, dispatch_sigusr1, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(s->event, &s->sigusr2_event_source, SIGUSR2, dispatch_sigusr2, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_add_signal(s->event, &s->sigterm_event_source, SIGTERM, dispatch_sigterm, s);
+ if (r < 0)
+ return r;
+
+ /* Let's process SIGTERM late, so that we flush all queued
+ * messages to disk before we exit */
+ r = sd_event_source_set_priority(s->sigterm_event_source, SD_EVENT_PRIORITY_NORMAL+20);
+ if (r < 0)
+ return r;
+
+ /* When journald is invoked on the terminal (when debugging),
+ * it's useful if C-c is handled equivalent to SIGTERM. */
+ r = sd_event_add_signal(s->event, &s->sigint_event_source, SIGINT, dispatch_sigterm, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s->sigint_event_source, SD_EVENT_PRIORITY_NORMAL+20);
+ if (r < 0)
+ return r;
+
+ /* SIGRTMIN+1 causes an immediate sync. We process this very
+ * late, so that everything else queued at this point is
+ * really written to disk. Clients can watch
+ * /run/systemd/journal/synced with inotify until its mtime
+ * changes to see when a sync happened. */
+ r = sd_event_add_signal(s->event, &s->sigrtmin1_event_source, SIGRTMIN+1, dispatch_sigrtmin1, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s->sigrtmin1_event_source, SD_EVENT_PRIORITY_NORMAL+15);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int server_parse_proc_cmdline(Server *s) {
+ _cleanup_free_ char *line = NULL;
+ const char *p;
+ int r;
+
+ r = proc_cmdline(&line);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
+ return 0;
+ }
+
+ p = line;
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse journald syntax \"%s\": %m", line);
+
+ if (r == 0)
+ break;
+
+ if (startswith(word, "systemd.journald.forward_to_syslog=")) {
+ r = parse_boolean(word + 35);
+ if (r < 0)
+ log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35);
+ else
+ s->forward_to_syslog = r;
+ } else if (startswith(word, "systemd.journald.forward_to_kmsg=")) {
+ r = parse_boolean(word + 33);
+ if (r < 0)
+ log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33);
+ else
+ s->forward_to_kmsg = r;
+ } else if (startswith(word, "systemd.journald.forward_to_console=")) {
+ r = parse_boolean(word + 36);
+ if (r < 0)
+ log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36);
+ else
+ s->forward_to_console = r;
+ } else if (startswith(word, "systemd.journald.forward_to_wall=")) {
+ r = parse_boolean(word + 33);
+ if (r < 0)
+ log_warning("Failed to parse forward to wall switch %s. Ignoring.", word + 33);
+ else
+ s->forward_to_wall = r;
+ } else if (startswith(word, "systemd.journald"))
+ log_warning("Invalid systemd.journald parameter. Ignoring.");
+ }
+
+ /* do not warn about state here, since probably systemd already did */
+ return 0;
+}
+
+static int server_parse_config_file(Server *s) {
+ assert(s);
+
+ return config_parse_many(PKGSYSCONFDIR "/journald.conf",
+ CONF_PATHS_NULSTR("systemd/journald.conf.d"),
+ "Journal\0",
+ config_item_perf_lookup, journald_gperf_lookup,
+ false, s);
+}
+
+static int server_dispatch_sync(sd_event_source *es, usec_t t, void *userdata) {
+ Server *s = userdata;
+
+ assert(s);
+
+ server_sync(s);
+ return 0;
+}
+
+int server_schedule_sync(Server *s, int priority) {
+ int r;
+
+ assert(s);
+
+ if (priority <= LOG_CRIT) {
+ /* Immediately sync to disk when this is of priority CRIT, ALERT, EMERG */
+ server_sync(s);
+ return 0;
+ }
+
+ if (s->sync_scheduled)
+ return 0;
+
+ if (s->sync_interval_usec > 0) {
+ usec_t when;
+
+ r = sd_event_now(s->event, CLOCK_MONOTONIC, &when);
+ if (r < 0)
+ return r;
+
+ when += s->sync_interval_usec;
+
+ if (!s->sync_event_source) {
+ r = sd_event_add_time(
+ s->event,
+ &s->sync_event_source,
+ CLOCK_MONOTONIC,
+ when, 0,
+ server_dispatch_sync, s);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(s->sync_event_source, SD_EVENT_PRIORITY_IMPORTANT);
+ } else {
+ r = sd_event_source_set_time(s->sync_event_source, when);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_enabled(s->sync_event_source, SD_EVENT_ONESHOT);
+ }
+ if (r < 0)
+ return r;
+
+ s->sync_scheduled = true;
+ }
+
+ return 0;
+}
+
+static int dispatch_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Server *s = userdata;
+
+ assert(s);
+
+ server_cache_hostname(s);
+ return 0;
+}
+
+static int server_open_hostname(Server *s) {
+ int r;
+
+ assert(s);
+
+ s->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY);
+ if (s->hostname_fd < 0)
+ return log_error_errno(errno, "Failed to open /proc/sys/kernel/hostname: %m");
+
+ r = sd_event_add_io(s->event, &s->hostname_event_source, s->hostname_fd, 0, dispatch_hostname_change, s);
+ if (r < 0) {
+ /* kernels prior to 3.2 don't support polling this file. Ignore
+ * the failure. */
+ if (r == -EPERM) {
+ log_warning_errno(r, "Failed to register hostname fd in event loop, ignoring: %m");
+ s->hostname_fd = safe_close(s->hostname_fd);
+ return 0;
+ }
+
+ return log_error_errno(r, "Failed to register hostname fd in event loop: %m");
+ }
+
+ r = sd_event_source_set_priority(s->hostname_event_source, SD_EVENT_PRIORITY_IMPORTANT-10);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust priority of host name event source: %m");
+
+ return 0;
+}
+
+static int dispatch_notify_event(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ Server *s = userdata;
+ int r;
+
+ assert(s);
+ assert(s->notify_event_source == es);
+ assert(s->notify_fd == fd);
+
+ /* The $NOTIFY_SOCKET is writable again, now send exactly one
+ * message on it. Either it's the wtachdog event, the initial
+ * READY=1 event or an stdout stream event. If there's nothing
+ * to write anymore, turn our event source off. The next time
+ * there's something to send it will be turned on again. */
+
+ if (!s->sent_notify_ready) {
+ static const char p[] =
+ "READY=1\n"
+ "STATUS=Processing requests...";
+ ssize_t l;
+
+ l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT);
+ if (l < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ return log_error_errno(errno, "Failed to send READY=1 notification message: %m");
+ }
+
+ s->sent_notify_ready = true;
+ log_debug("Sent READY=1 notification.");
+
+ } else if (s->send_watchdog) {
+
+ static const char p[] =
+ "WATCHDOG=1";
+
+ ssize_t l;
+
+ l = send(s->notify_fd, p, strlen(p), MSG_DONTWAIT);
+ if (l < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ return log_error_errno(errno, "Failed to send WATCHDOG=1 notification message: %m");
+ }
+
+ s->send_watchdog = false;
+ log_debug("Sent WATCHDOG=1 notification.");
+
+ } else if (s->stdout_streams_notify_queue)
+ /* Dispatch one stream notification event */
+ stdout_stream_send_notify(s->stdout_streams_notify_queue);
+
+ /* Leave us enabled if there's still more to to do. */
+ if (s->send_watchdog || s->stdout_streams_notify_queue)
+ return 0;
+
+ /* There was nothing to do anymore, let's turn ourselves off. */
+ r = sd_event_source_set_enabled(es, SD_EVENT_OFF);
+ if (r < 0)
+ return log_error_errno(r, "Failed to turn off notify event source: %m");
+
+ return 0;
+}
+
+static int dispatch_watchdog(sd_event_source *es, uint64_t usec, void *userdata) {
+ Server *s = userdata;
+ int r;
+
+ assert(s);
+
+ s->send_watchdog = true;
+
+ r = sd_event_source_set_enabled(s->notify_event_source, SD_EVENT_ON);
+ if (r < 0)
+ log_warning_errno(r, "Failed to turn on notify event source: %m");
+
+ r = sd_event_source_set_time(s->watchdog_event_source, usec + s->watchdog_usec / 2);
+ if (r < 0)
+ return log_error_errno(r, "Failed to restart watchdog event source: %m");
+
+ r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ON);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable watchdog event source: %m");
+
+ return 0;
+}
+
+static int server_connect_notify(Server *s) {
+ union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ };
+ const char *e;
+ int r;
+
+ assert(s);
+ assert(s->notify_fd < 0);
+ assert(!s->notify_event_source);
+
+ /*
+ So here's the problem: we'd like to send notification
+ messages to PID 1, but we cannot do that via sd_notify(),
+ since that's synchronous, and we might end up blocking on
+ it. Specifically: given that PID 1 might block on
+ dbus-daemon during IPC, and dbus-daemon is logging to us,
+ and might hence block on us, we might end up in a deadlock
+ if we block on sending PID 1 notification messages — by
+ generating a full blocking circle. To avoid this, let's
+ create a non-blocking socket, and connect it to the
+ notification socket, and then wait for POLLOUT before we
+ send anything. This should efficiently avoid any deadlocks,
+ as we'll never block on PID 1, hence PID 1 can safely block
+ on dbus-daemon which can safely block on us again.
+
+ Don't think that this issue is real? It is, see:
+ https://github.com/systemd/systemd/issues/1505
+ */
+
+ e = getenv("NOTIFY_SOCKET");
+ if (!e)
+ return 0;
+
+ if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+ log_error("NOTIFY_SOCKET set to an invalid value: %s", e);
+ return -EINVAL;
+ }
+
+ if (strlen(e) > sizeof(sa.un.sun_path)) {
+ log_error("NOTIFY_SOCKET path too long: %s", e);
+ return -EINVAL;
+ }
+
+ s->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->notify_fd < 0)
+ return log_error_errno(errno, "Failed to create notify socket: %m");
+
+ (void) fd_inc_sndbuf(s->notify_fd, NOTIFY_SNDBUF_SIZE);
+
+ strncpy(sa.un.sun_path, e, sizeof(sa.un.sun_path));
+ if (sa.un.sun_path[0] == '@')
+ sa.un.sun_path[0] = 0;
+
+ r = connect(s->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "Failed to connect to notify socket: %m");
+
+ r = sd_event_add_io(s->event, &s->notify_event_source, s->notify_fd, EPOLLOUT, dispatch_notify_event, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch notification socket: %m");
+
+ if (sd_watchdog_enabled(false, &s->watchdog_usec) > 0) {
+ s->send_watchdog = true;
+
+ r = sd_event_add_time(s->event, &s->watchdog_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + s->watchdog_usec/2, s->watchdog_usec/4, dispatch_watchdog, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add watchdog time event: %m");
+ }
+
+ /* This should fire pretty soon, which we'll use to send the
+ * READY=1 event. */
+
+ return 0;
+}
+
+int server_init(Server *s) {
+ _cleanup_fdset_free_ FDSet *fds = NULL;
+ int n, r, fd;
+ bool no_sockets;
+
+ assert(s);
+
+ zero(*s);
+ s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = s->notify_fd = -1;
+ s->compress = true;
+ s->seal = true;
+
+ s->watchdog_usec = USEC_INFINITY;
+
+ s->sync_interval_usec = DEFAULT_SYNC_INTERVAL_USEC;
+ s->sync_scheduled = false;
+
+ s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL;
+ s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST;
+
+ s->forward_to_wall = true;
+
+ s->max_file_usec = DEFAULT_MAX_FILE_USEC;
+
+ s->max_level_store = LOG_DEBUG;
+ s->max_level_syslog = LOG_DEBUG;
+ s->max_level_kmsg = LOG_NOTICE;
+ s->max_level_console = LOG_INFO;
+ s->max_level_wall = LOG_EMERG;
+
+ journal_reset_metrics(&s->system_metrics);
+ journal_reset_metrics(&s->runtime_metrics);
+
+ server_parse_config_file(s);
+ server_parse_proc_cmdline(s);
+
+ if (!!s->rate_limit_interval ^ !!s->rate_limit_burst) {
+ log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0",
+ s->rate_limit_interval, s->rate_limit_burst);
+ s->rate_limit_interval = s->rate_limit_burst = 0;
+ }
+
+ (void) mkdir_p("/run/systemd/journal", 0755);
+
+ s->user_journals = ordered_hashmap_new(NULL);
+ if (!s->user_journals)
+ return log_oom();
+
+ s->mmap = mmap_cache_new();
+ if (!s->mmap)
+ return log_oom();
+
+ s->deferred_closes = set_new(NULL);
+ if (!s->deferred_closes)
+ return log_oom();
+
+ r = sd_event_default(&s->event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create event loop: %m");
+
+ n = sd_listen_fds(true);
+ if (n < 0)
+ return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
+
+ if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) {
+
+ if (s->native_fd >= 0) {
+ log_error("Too many native sockets passed.");
+ return -EINVAL;
+ }
+
+ s->native_fd = fd;
+
+ } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) {
+
+ if (s->stdout_fd >= 0) {
+ log_error("Too many stdout sockets passed.");
+ return -EINVAL;
+ }
+
+ s->stdout_fd = fd;
+
+ } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0 ||
+ sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/dev-log", 0) > 0) {
+
+ if (s->syslog_fd >= 0) {
+ log_error("Too many /dev/log sockets passed.");
+ return -EINVAL;
+ }
+
+ s->syslog_fd = fd;
+
+ } else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
+
+ if (s->audit_fd >= 0) {
+ log_error("Too many audit sockets passed.");
+ return -EINVAL;
+ }
+
+ s->audit_fd = fd;
+
+ } else {
+
+ if (!fds) {
+ fds = fdset_new();
+ if (!fds)
+ return log_oom();
+ }
+
+ r = fdset_put(fds, fd);
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ /* Try to restore streams, but don't bother if this fails */
+ (void) server_restore_streams(s, fds);
+
+ if (fdset_size(fds) > 0) {
+ log_warning("%u unknown file descriptors passed, closing.", fdset_size(fds));
+ fds = fdset_free(fds);
+ }
+
+ no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0;
+
+ /* always open stdout, syslog, native, and kmsg sockets */
+
+ /* systemd-journald.socket: /run/systemd/journal/stdout */
+ r = server_open_stdout_socket(s);
+ if (r < 0)
+ return r;
+
+ /* systemd-journald-dev-log.socket: /run/systemd/journal/dev-log */
+ r = server_open_syslog_socket(s);
+ if (r < 0)
+ return r;
+
+ /* systemd-journald.socket: /run/systemd/journal/socket */
+ r = server_open_native_socket(s);
+ if (r < 0)
+ return r;
+
+ /* /dev/ksmg */
+ r = server_open_dev_kmsg(s);
+ if (r < 0)
+ return r;
+
+ /* Unless we got *some* sockets and not audit, open audit socket */
+ if (s->audit_fd >= 0 || no_sockets) {
+ r = server_open_audit(s);
+ if (r < 0)
+ return r;
+ }
+
+ r = server_open_kernel_seqnum(s);
+ if (r < 0)
+ return r;
+
+ r = server_open_hostname(s);
+ if (r < 0)
+ return r;
+
+ r = setup_signals(s);
+ if (r < 0)
+ return r;
+
+ s->udev = udev_new();
+ if (!s->udev)
+ return -ENOMEM;
+
+ s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
+ if (!s->rate_limit)
+ return -ENOMEM;
+
+ r = cg_get_root_path(&s->cgroup_root);
+ if (r < 0)
+ return r;
+
+ server_cache_hostname(s);
+ server_cache_boot_id(s);
+ server_cache_machine_id(s);
+
+ (void) server_connect_notify(s);
+
+ return system_journal_open(s, false);
+}
+
+void server_maybe_append_tags(Server *s) {
+#ifdef HAVE_GCRYPT
+ JournalFile *f;
+ Iterator i;
+ usec_t n;
+
+ n = now(CLOCK_REALTIME);
+
+ if (s->system_journal)
+ journal_file_maybe_append_tag(s->system_journal, n);
+
+ ORDERED_HASHMAP_FOREACH(f, s->user_journals, i)
+ journal_file_maybe_append_tag(f, n);
+#endif
+}
+
+void server_done(Server *s) {
+ JournalFile *f;
+ assert(s);
+
+ if (s->deferred_closes) {
+ journal_file_close_set(s->deferred_closes);
+ set_free(s->deferred_closes);
+ }
+
+ while (s->stdout_streams)
+ stdout_stream_free(s->stdout_streams);
+
+ if (s->system_journal)
+ (void) journal_file_close(s->system_journal);
+
+ if (s->runtime_journal)
+ (void) journal_file_close(s->runtime_journal);
+
+ while ((f = ordered_hashmap_steal_first(s->user_journals)))
+ (void) journal_file_close(f);
+
+ ordered_hashmap_free(s->user_journals);
+
+ sd_event_source_unref(s->syslog_event_source);
+ sd_event_source_unref(s->native_event_source);
+ sd_event_source_unref(s->stdout_event_source);
+ sd_event_source_unref(s->dev_kmsg_event_source);
+ sd_event_source_unref(s->audit_event_source);
+ sd_event_source_unref(s->sync_event_source);
+ sd_event_source_unref(s->sigusr1_event_source);
+ sd_event_source_unref(s->sigusr2_event_source);
+ sd_event_source_unref(s->sigterm_event_source);
+ sd_event_source_unref(s->sigint_event_source);
+ sd_event_source_unref(s->sigrtmin1_event_source);
+ sd_event_source_unref(s->hostname_event_source);
+ sd_event_source_unref(s->notify_event_source);
+ sd_event_source_unref(s->watchdog_event_source);
+ sd_event_unref(s->event);
+
+ safe_close(s->syslog_fd);
+ safe_close(s->native_fd);
+ safe_close(s->stdout_fd);
+ safe_close(s->dev_kmsg_fd);
+ safe_close(s->audit_fd);
+ safe_close(s->hostname_fd);
+ safe_close(s->notify_fd);
+
+ if (s->rate_limit)
+ journal_rate_limit_free(s->rate_limit);
+
+ if (s->kernel_seqnum)
+ munmap(s->kernel_seqnum, sizeof(uint64_t));
+
+ free(s->buffer);
+ free(s->tty_path);
+ free(s->cgroup_root);
+ free(s->hostname_field);
+
+ if (s->mmap)
+ mmap_cache_unref(s->mmap);
+
+ udev_unref(s->udev);
+}
+
+static const char* const storage_table[_STORAGE_MAX] = {
+ [STORAGE_AUTO] = "auto",
+ [STORAGE_VOLATILE] = "volatile",
+ [STORAGE_PERSISTENT] = "persistent",
+ [STORAGE_NONE] = "none"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(storage, Storage);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting");
+
+static const char* const split_mode_table[_SPLIT_MAX] = {
+ [SPLIT_LOGIN] = "login",
+ [SPLIT_UID] = "uid",
+ [SPLIT_NONE] = "none",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(split_mode, SplitMode);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_split_mode, split_mode, SplitMode, "Failed to parse split mode setting");
diff --git a/src/grp-journal/libjournal-core/journald-server.h b/src/grp-journal/libjournal-core/journald-server.h
new file mode 100644
index 0000000000..5f1af1e50b
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-server.h
@@ -0,0 +1,187 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <stdbool.h>
+#include <sys/types.h>
+
+#include <systemd/sd-event.h>
+
+#include "basic/hashmap.h"
+#include "basic/list.h"
+#include "sd-journal/journal-file.h"
+
+typedef struct Server Server;
+
+#include "journald-rate-limit.h"
+#include "journald-stream.h"
+
+typedef enum Storage {
+ STORAGE_AUTO,
+ STORAGE_VOLATILE,
+ STORAGE_PERSISTENT,
+ STORAGE_NONE,
+ _STORAGE_MAX,
+ _STORAGE_INVALID = -1
+} Storage;
+
+typedef enum SplitMode {
+ SPLIT_UID,
+ SPLIT_LOGIN,
+ SPLIT_NONE,
+ _SPLIT_MAX,
+ _SPLIT_INVALID = -1
+} SplitMode;
+
+struct Server {
+ int syslog_fd;
+ int native_fd;
+ int stdout_fd;
+ int dev_kmsg_fd;
+ int audit_fd;
+ int hostname_fd;
+ int notify_fd;
+
+ sd_event *event;
+
+ sd_event_source *syslog_event_source;
+ sd_event_source *native_event_source;
+ sd_event_source *stdout_event_source;
+ sd_event_source *dev_kmsg_event_source;
+ sd_event_source *audit_event_source;
+ sd_event_source *sync_event_source;
+ sd_event_source *sigusr1_event_source;
+ sd_event_source *sigusr2_event_source;
+ sd_event_source *sigterm_event_source;
+ sd_event_source *sigint_event_source;
+ sd_event_source *sigrtmin1_event_source;
+ sd_event_source *hostname_event_source;
+ sd_event_source *notify_event_source;
+ sd_event_source *watchdog_event_source;
+
+ JournalFile *runtime_journal;
+ JournalFile *system_journal;
+ OrderedHashmap *user_journals;
+
+ uint64_t seqnum;
+
+ char *buffer;
+ size_t buffer_size;
+
+ JournalRateLimit *rate_limit;
+ usec_t sync_interval_usec;
+ usec_t rate_limit_interval;
+ unsigned rate_limit_burst;
+
+ JournalMetrics runtime_metrics;
+ JournalMetrics system_metrics;
+
+ bool compress;
+ bool seal;
+
+ bool forward_to_kmsg;
+ bool forward_to_syslog;
+ bool forward_to_console;
+ bool forward_to_wall;
+
+ unsigned n_forward_syslog_missed;
+ usec_t last_warn_forward_syslog_missed;
+
+ uint64_t cached_space_available;
+ uint64_t cached_space_limit;
+ usec_t cached_space_timestamp;
+
+ uint64_t var_available_timestamp;
+
+ usec_t max_retention_usec;
+ usec_t max_file_usec;
+ usec_t oldest_file_usec;
+
+ LIST_HEAD(StdoutStream, stdout_streams);
+ LIST_HEAD(StdoutStream, stdout_streams_notify_queue);
+ unsigned n_stdout_streams;
+
+ char *tty_path;
+
+ int max_level_store;
+ int max_level_syslog;
+ int max_level_kmsg;
+ int max_level_console;
+ int max_level_wall;
+
+ Storage storage;
+ SplitMode split_mode;
+
+ MMapCache *mmap;
+
+ Set *deferred_closes;
+
+ struct udev *udev;
+
+ uint64_t *kernel_seqnum;
+ bool dev_kmsg_readable:1;
+
+ bool send_watchdog:1;
+ bool sent_notify_ready:1;
+ bool sync_scheduled:1;
+
+ char machine_id_field[sizeof("_MACHINE_ID=") + 32];
+ char boot_id_field[sizeof("_BOOT_ID=") + 32];
+ char *hostname_field;
+
+ /* Cached cgroup root, so that we don't have to query that all the time */
+ char *cgroup_root;
+
+ usec_t watchdog_usec;
+};
+
+#define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID="))
+
+#define N_IOVEC_META_FIELDS 20
+#define N_IOVEC_KERNEL_FIELDS 64
+#define N_IOVEC_UDEV_FIELDS 32
+#define N_IOVEC_OBJECT_FIELDS 12
+#define N_IOVEC_PAYLOAD_FIELDS 15
+
+void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid);
+void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) _printf_(3,0) _sentinel_;
+
+/* gperf lookup function */
+const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length);
+
+int config_parse_storage(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);
+
+const char *storage_to_string(Storage s) _const_;
+Storage storage_from_string(const char *s) _pure_;
+
+int config_parse_split_mode(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);
+
+const char *split_mode_to_string(SplitMode s) _const_;
+SplitMode split_mode_from_string(const char *s) _pure_;
+
+int server_init(Server *s);
+void server_done(Server *s);
+void server_sync(Server *s);
+int server_vacuum(Server *s, bool verbose, bool patch_min_use);
+void server_rotate(Server *s);
+int server_schedule_sync(Server *s, int priority);
+int server_flush_to_var(Server *s);
+void server_maybe_append_tags(Server *s);
+int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata);
diff --git a/src/grp-journal/libjournal-core/journald-stream.c b/src/grp-journal/libjournal-core/journald-stream.c
new file mode 100644
index 0000000000..e90dfac1cd
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-stream.c
@@ -0,0 +1,786 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <stddef.h>
+#include <unistd.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-event.h>
+
+#include "basic/alloc-util.h"
+#include "basic/dirent-util.h"
+#include "basic/escape.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/io-util.h"
+#include "basic/mkdir.h"
+#include "basic/parse-util.h"
+#include "basic/selinux-util.h"
+#include "basic/socket-util.h"
+#include "basic/stdio-util.h"
+#include "basic/string-util.h"
+#include "basic/syslog-util.h"
+
+#include "journald-console.h"
+#include "journald-kmsg.h"
+#include "journald-server.h"
+#include "journald-stream.h"
+#include "journald-syslog.h"
+#include "journald-wall.h"
+
+#define STDOUT_STREAMS_MAX 4096
+
+typedef enum StdoutStreamState {
+ STDOUT_STREAM_IDENTIFIER,
+ STDOUT_STREAM_UNIT_ID,
+ STDOUT_STREAM_PRIORITY,
+ STDOUT_STREAM_LEVEL_PREFIX,
+ STDOUT_STREAM_FORWARD_TO_SYSLOG,
+ STDOUT_STREAM_FORWARD_TO_KMSG,
+ STDOUT_STREAM_FORWARD_TO_CONSOLE,
+ STDOUT_STREAM_RUNNING
+} StdoutStreamState;
+
+struct StdoutStream {
+ Server *server;
+ StdoutStreamState state;
+
+ int fd;
+
+ struct ucred ucred;
+ char *label;
+ char *identifier;
+ char *unit_id;
+ int priority;
+ bool level_prefix:1;
+ bool forward_to_syslog:1;
+ bool forward_to_kmsg:1;
+ bool forward_to_console:1;
+
+ bool fdstore:1;
+ bool in_notify_queue:1;
+
+ char buffer[LINE_MAX+1];
+ size_t length;
+
+ sd_event_source *event_source;
+
+ char *state_file;
+
+ LIST_FIELDS(StdoutStream, stdout_stream);
+ LIST_FIELDS(StdoutStream, stdout_stream_notify_queue);
+};
+
+void stdout_stream_free(StdoutStream *s) {
+ if (!s)
+ return;
+
+ if (s->server) {
+ assert(s->server->n_stdout_streams > 0);
+ s->server->n_stdout_streams--;
+ LIST_REMOVE(stdout_stream, s->server->stdout_streams, s);
+
+ if (s->in_notify_queue)
+ LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
+ }
+
+ if (s->event_source) {
+ sd_event_source_set_enabled(s->event_source, SD_EVENT_OFF);
+ s->event_source = sd_event_source_unref(s->event_source);
+ }
+
+ safe_close(s->fd);
+ free(s->label);
+ free(s->identifier);
+ free(s->unit_id);
+ free(s->state_file);
+
+ free(s);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(StdoutStream*, stdout_stream_free);
+
+static void stdout_stream_destroy(StdoutStream *s) {
+ if (!s)
+ return;
+
+ if (s->state_file)
+ (void) unlink(s->state_file);
+
+ stdout_stream_free(s);
+}
+
+static int stdout_stream_save(StdoutStream *s) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(s);
+
+ if (s->state != STDOUT_STREAM_RUNNING)
+ return 0;
+
+ if (!s->state_file) {
+ struct stat st;
+
+ r = fstat(s->fd, &st);
+ if (r < 0)
+ return log_warning_errno(errno, "Failed to stat connected stream: %m");
+
+ /* We use device and inode numbers as identifier for the stream */
+ if (asprintf(&s->state_file, "/run/systemd/journal/streams/%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0)
+ return log_oom();
+ }
+
+ mkdir_p("/run/systemd/journal/streams", 0755);
+
+ r = fopen_temporary(s->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ fprintf(f,
+ "# This is private data. Do not parse\n"
+ "PRIORITY=%i\n"
+ "LEVEL_PREFIX=%i\n"
+ "FORWARD_TO_SYSLOG=%i\n"
+ "FORWARD_TO_KMSG=%i\n"
+ "FORWARD_TO_CONSOLE=%i\n",
+ s->priority,
+ s->level_prefix,
+ s->forward_to_syslog,
+ s->forward_to_kmsg,
+ s->forward_to_console);
+
+ if (!isempty(s->identifier)) {
+ _cleanup_free_ char *escaped;
+
+ escaped = cescape(s->identifier);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "IDENTIFIER=%s\n", escaped);
+ }
+
+ if (!isempty(s->unit_id)) {
+ _cleanup_free_ char *escaped;
+
+ escaped = cescape(s->unit_id);
+ if (!escaped) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ fprintf(f, "UNIT=%s\n", escaped);
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, s->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (!s->fdstore && !s->in_notify_queue) {
+ LIST_PREPEND(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
+ s->in_notify_queue = true;
+
+ if (s->server->notify_event_source) {
+ r = sd_event_source_set_enabled(s->server->notify_event_source, SD_EVENT_ON);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enable notify event source: %m");
+ }
+ }
+
+ return 0;
+
+fail:
+ (void) unlink(s->state_file);
+
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file);
+}
+
+static int stdout_stream_log(StdoutStream *s, const char *p) {
+ struct iovec iovec[N_IOVEC_META_FIELDS + 5];
+ int priority;
+ char syslog_priority[] = "PRIORITY=\0";
+ char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1];
+ _cleanup_free_ char *message = NULL, *syslog_identifier = NULL;
+ unsigned n = 0;
+ size_t label_len;
+
+ assert(s);
+ assert(p);
+
+ priority = s->priority;
+
+ if (s->level_prefix)
+ syslog_parse_priority(&p, &priority, false);
+
+ if (isempty(p))
+ return 0;
+
+ if (s->forward_to_syslog || s->server->forward_to_syslog)
+ server_forward_syslog(s->server, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
+
+ if (s->forward_to_kmsg || s->server->forward_to_kmsg)
+ server_forward_kmsg(s->server, priority, s->identifier, p, &s->ucred);
+
+ if (s->forward_to_console || s->server->forward_to_console)
+ server_forward_console(s->server, priority, s->identifier, p, &s->ucred);
+
+ if (s->server->forward_to_wall)
+ server_forward_wall(s->server, priority, s->identifier, p, &s->ucred);
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout");
+
+ syslog_priority[strlen("PRIORITY=")] = '0' + LOG_PRI(priority);
+ IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+ if (priority & LOG_FACMASK) {
+ xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority));
+ IOVEC_SET_STRING(iovec[n++], syslog_facility);
+ }
+
+ if (s->identifier) {
+ syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier);
+ if (syslog_identifier)
+ IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+ }
+
+ message = strappend("MESSAGE=", p);
+ if (message)
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ label_len = s->label ? strlen(s->label) : 0;
+ server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, s->label, label_len, s->unit_id, priority, 0);
+ return 0;
+}
+
+static int stdout_stream_line(StdoutStream *s, char *p) {
+ int r;
+ char *orig;
+
+ assert(s);
+ assert(p);
+
+ orig = p;
+ p = strstrip(p);
+
+ switch (s->state) {
+
+ case STDOUT_STREAM_IDENTIFIER:
+ if (isempty(p))
+ s->identifier = NULL;
+ else {
+ s->identifier = strdup(p);
+ if (!s->identifier)
+ return log_oom();
+ }
+
+ s->state = STDOUT_STREAM_UNIT_ID;
+ return 0;
+
+ case STDOUT_STREAM_UNIT_ID:
+ if (s->ucred.uid == 0) {
+ if (isempty(p))
+ s->unit_id = NULL;
+ else {
+ s->unit_id = strdup(p);
+ if (!s->unit_id)
+ return log_oom();
+ }
+ }
+
+ s->state = STDOUT_STREAM_PRIORITY;
+ return 0;
+
+ case STDOUT_STREAM_PRIORITY:
+ r = safe_atoi(p, &s->priority);
+ if (r < 0 || s->priority < 0 || s->priority > 999) {
+ log_warning("Failed to parse log priority line.");
+ return -EINVAL;
+ }
+
+ s->state = STDOUT_STREAM_LEVEL_PREFIX;
+ return 0;
+
+ case STDOUT_STREAM_LEVEL_PREFIX:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse level prefix line.");
+ return -EINVAL;
+ }
+
+ s->level_prefix = !!r;
+ s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
+ return 0;
+
+ case STDOUT_STREAM_FORWARD_TO_SYSLOG:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse forward to syslog line.");
+ return -EINVAL;
+ }
+
+ s->forward_to_syslog = !!r;
+ s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
+ return 0;
+
+ case STDOUT_STREAM_FORWARD_TO_KMSG:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse copy to kmsg line.");
+ return -EINVAL;
+ }
+
+ s->forward_to_kmsg = !!r;
+ s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
+ return 0;
+
+ case STDOUT_STREAM_FORWARD_TO_CONSOLE:
+ r = parse_boolean(p);
+ if (r < 0) {
+ log_warning("Failed to parse copy to console line.");
+ return -EINVAL;
+ }
+
+ s->forward_to_console = !!r;
+ s->state = STDOUT_STREAM_RUNNING;
+
+ /* Try to save the stream, so that journald can be restarted and we can recover */
+ (void) stdout_stream_save(s);
+ return 0;
+
+ case STDOUT_STREAM_RUNNING:
+ return stdout_stream_log(s, orig);
+ }
+
+ assert_not_reached("Unknown stream state");
+}
+
+static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
+ char *p;
+ size_t remaining;
+ int r;
+
+ assert(s);
+
+ p = s->buffer;
+ remaining = s->length;
+ for (;;) {
+ char *end;
+ size_t skip;
+
+ end = memchr(p, '\n', remaining);
+ if (end)
+ skip = end - p + 1;
+ else if (remaining >= sizeof(s->buffer) - 1) {
+ end = p + sizeof(s->buffer) - 1;
+ skip = remaining;
+ } else
+ break;
+
+ *end = 0;
+
+ r = stdout_stream_line(s, p);
+ if (r < 0)
+ return r;
+
+ remaining -= skip;
+ p += skip;
+ }
+
+ if (force_flush && remaining > 0) {
+ p[remaining] = 0;
+ r = stdout_stream_line(s, p);
+ if (r < 0)
+ return r;
+
+ p += remaining;
+ remaining = 0;
+ }
+
+ if (p > s->buffer) {
+ memmove(s->buffer, p, remaining);
+ s->length = remaining;
+ }
+
+ return 0;
+}
+
+static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+ StdoutStream *s = userdata;
+ ssize_t l;
+ int r;
+
+ assert(s);
+
+ if ((revents|EPOLLIN|EPOLLHUP) != (EPOLLIN|EPOLLHUP)) {
+ log_error("Got invalid event from epoll for stdout stream: %"PRIx32, revents);
+ goto terminate;
+ }
+
+ l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length);
+ if (l < 0) {
+
+ if (errno == EAGAIN)
+ return 0;
+
+ log_warning_errno(errno, "Failed to read from stream: %m");
+ goto terminate;
+ }
+
+ if (l == 0) {
+ stdout_stream_scan(s, true);
+ goto terminate;
+ }
+
+ s->length += l;
+ r = stdout_stream_scan(s, false);
+ if (r < 0)
+ goto terminate;
+
+ return 1;
+
+terminate:
+ stdout_stream_destroy(s);
+ return 0;
+}
+
+static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
+ _cleanup_(stdout_stream_freep) StdoutStream *stream = NULL;
+ int r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ stream = new0(StdoutStream, 1);
+ if (!stream)
+ return log_oom();
+
+ stream->fd = -1;
+ stream->priority = LOG_INFO;
+
+ r = getpeercred(fd, &stream->ucred);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine peer credentials: %m");
+
+ if (mac_selinux_have()) {
+ r = getpeersec(fd, &stream->label);
+ if (r < 0 && r != -EOPNOTSUPP)
+ (void) log_warning_errno(r, "Failed to determine peer security context: %m");
+ }
+
+ (void) shutdown(fd, SHUT_WR);
+
+ r = sd_event_add_io(s->event, &stream->event_source, fd, EPOLLIN, stdout_stream_process, stream);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add stream to event loop: %m");
+
+ r = sd_event_source_set_priority(stream->event_source, SD_EVENT_PRIORITY_NORMAL+5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust stdout event source priority: %m");
+
+ stream->fd = fd;
+
+ stream->server = s;
+ LIST_PREPEND(stdout_stream, s->stdout_streams, stream);
+ s->n_stdout_streams++;
+
+ if (ret)
+ *ret = stream;
+
+ stream = NULL;
+
+ return 0;
+}
+
+static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revents, void *userdata) {
+ _cleanup_close_ int fd = -1;
+ Server *s = userdata;
+ int r;
+
+ assert(s);
+
+ if (revents != EPOLLIN) {
+ log_error("Got invalid event from epoll for stdout server fd: %"PRIx32, revents);
+ return -EIO;
+ }
+
+ fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (fd < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ return log_error_errno(errno, "Failed to accept stdout connection: %m");
+ }
+
+ if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
+ log_warning("Too many stdout streams, refusing connection.");
+ return 0;
+ }
+
+ r = stdout_stream_install(s, fd, NULL);
+ if (r < 0)
+ return r;
+
+ fd = -1;
+ return 0;
+}
+
+static int stdout_stream_load(StdoutStream *stream, const char *fname) {
+ _cleanup_free_ char
+ *priority = NULL,
+ *level_prefix = NULL,
+ *forward_to_syslog = NULL,
+ *forward_to_kmsg = NULL,
+ *forward_to_console = NULL;
+ int r;
+
+ assert(stream);
+ assert(fname);
+
+ if (!stream->state_file) {
+ stream->state_file = strappend("/run/systemd/journal/streams/", fname);
+ if (!stream->state_file)
+ return log_oom();
+ }
+
+ r = parse_env_file(stream->state_file, NEWLINE,
+ "PRIORITY", &priority,
+ "LEVEL_PREFIX", &level_prefix,
+ "FORWARD_TO_SYSLOG", &forward_to_syslog,
+ "FORWARD_TO_KMSG", &forward_to_kmsg,
+ "FORWARD_TO_CONSOLE", &forward_to_console,
+ "IDENTIFIER", &stream->identifier,
+ "UNIT", &stream->unit_id,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read: %s", stream->state_file);
+
+ if (priority) {
+ int p;
+
+ p = log_level_from_string(priority);
+ if (p >= 0)
+ stream->priority = p;
+ }
+
+ if (level_prefix) {
+ r = parse_boolean(level_prefix);
+ if (r >= 0)
+ stream->level_prefix = r;
+ }
+
+ if (forward_to_syslog) {
+ r = parse_boolean(forward_to_syslog);
+ if (r >= 0)
+ stream->forward_to_syslog = r;
+ }
+
+ if (forward_to_kmsg) {
+ r = parse_boolean(forward_to_kmsg);
+ if (r >= 0)
+ stream->forward_to_kmsg = r;
+ }
+
+ if (forward_to_console) {
+ r = parse_boolean(forward_to_console);
+ if (r >= 0)
+ stream->forward_to_console = r;
+ }
+
+ return 0;
+}
+
+static int stdout_stream_restore(Server *s, const char *fname, int fd) {
+ StdoutStream *stream;
+ int r;
+
+ assert(s);
+ assert(fname);
+ assert(fd >= 0);
+
+ if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
+ log_warning("Too many stdout streams, refusing restoring of stream.");
+ return -ENOBUFS;
+ }
+
+ r = stdout_stream_install(s, fd, &stream);
+ if (r < 0)
+ return r;
+
+ stream->state = STDOUT_STREAM_RUNNING;
+ stream->fdstore = true;
+
+ /* Ignore all parsing errors */
+ (void) stdout_stream_load(stream, fname);
+
+ return 0;
+}
+
+int server_restore_streams(Server *s, FDSet *fds) {
+ _cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
+ int r;
+
+ d = opendir("/run/systemd/journal/streams");
+ if (!d) {
+ if (errno == ENOENT)
+ return 0;
+
+ return log_warning_errno(errno, "Failed to enumerate /run/systemd/journal/streams: %m");
+ }
+
+ FOREACH_DIRENT(de, d, goto fail) {
+ unsigned long st_dev, st_ino;
+ bool found = false;
+ Iterator i;
+ int fd;
+
+ if (sscanf(de->d_name, "%lu:%lu", &st_dev, &st_ino) != 2)
+ continue;
+
+ FDSET_FOREACH(fd, fds, i) {
+ struct stat st;
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat %s: %m", de->d_name);
+
+ if (S_ISSOCK(st.st_mode) && st.st_dev == st_dev && st.st_ino == st_ino) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* No file descriptor? Then let's delete the state file */
+ log_debug("Cannot restore stream file %s", de->d_name);
+ unlinkat(dirfd(d), de->d_name, 0);
+ continue;
+ }
+
+ fdset_remove(fds, fd);
+
+ r = stdout_stream_restore(s, de->d_name, fd);
+ if (r < 0)
+ safe_close(fd);
+ }
+
+ return 0;
+
+fail:
+ return log_error_errno(errno, "Failed to read streams directory: %m");
+}
+
+int server_open_stdout_socket(Server *s) {
+ static const union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/journal/stdout",
+ };
+ int r;
+
+ assert(s);
+
+ if (s->stdout_fd < 0) {
+ s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->stdout_fd < 0)
+ return log_error_errno(errno, "socket() failed: %m");
+
+ (void) unlink(sa.un.sun_path);
+
+ r = bind(s->stdout_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
+
+ (void) chmod(sa.un.sun_path, 0666);
+
+ if (listen(s->stdout_fd, SOMAXCONN) < 0)
+ return log_error_errno(errno, "listen(%s) failed: %m", sa.un.sun_path);
+ } else
+ fd_nonblock(s->stdout_fd, 1);
+
+ r = sd_event_add_io(s->event, &s->stdout_event_source, s->stdout_fd, EPOLLIN, stdout_stream_new, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add stdout server fd to event source: %m");
+
+ r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust priority of stdout server event source: %m");
+
+ return 0;
+}
+
+void stdout_stream_send_notify(StdoutStream *s) {
+ struct iovec iovec = {
+ .iov_base = (char*) "FDSTORE=1",
+ .iov_len = strlen("FDSTORE=1"),
+ };
+ struct msghdr msghdr = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ };
+ struct cmsghdr *cmsg;
+ ssize_t l;
+
+ assert(s);
+ assert(!s->fdstore);
+ assert(s->in_notify_queue);
+ assert(s->server);
+ assert(s->server->notify_fd >= 0);
+
+ /* Store the connection fd in PID 1, so that we get it passed
+ * in again on next start */
+
+ msghdr.msg_controllen = CMSG_SPACE(sizeof(int));
+ msghdr.msg_control = alloca0(msghdr.msg_controllen);
+
+ cmsg = CMSG_FIRSTHDR(&msghdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+
+ memcpy(CMSG_DATA(cmsg), &s->fd, sizeof(int));
+
+ l = sendmsg(s->server->notify_fd, &msghdr, MSG_DONTWAIT|MSG_NOSIGNAL);
+ if (l < 0) {
+ if (errno == EAGAIN)
+ return;
+
+ log_error_errno(errno, "Failed to send stream file descriptor to service manager: %m");
+ } else {
+ log_debug("Successfully sent stream file descriptor to service manager.");
+ s->fdstore = 1;
+ }
+
+ LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
+ s->in_notify_queue = false;
+
+}
diff --git a/src/grp-journal/libjournal-core/journald-stream.h b/src/grp-journal/libjournal-core/journald-stream.h
new file mode 100644
index 0000000000..c971d0a00d
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-stream.h
@@ -0,0 +1,32 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "basic/fdset.h"
+
+typedef struct StdoutStream StdoutStream;
+
+#include "journald-server.h"
+
+int server_open_stdout_socket(Server *s);
+int server_restore_streams(Server *s, FDSet *fds);
+
+void stdout_stream_free(StdoutStream *s);
+void stdout_stream_send_notify(StdoutStream *s);
diff --git a/src/grp-journal/libjournal-core/journald-syslog.c b/src/grp-journal/libjournal-core/journald-syslog.c
new file mode 100644
index 0000000000..bc793235d4
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-syslog.c
@@ -0,0 +1,455 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <stddef.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+
+#include <systemd/sd-messages.h>
+
+#include "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/formats-util.h"
+#include "basic/io-util.h"
+#include "basic/process-util.h"
+#include "basic/selinux-util.h"
+#include "basic/socket-util.h"
+#include "basic/stdio-util.h"
+#include "basic/string-util.h"
+#include "basic/syslog-util.h"
+
+#include "journald-console.h"
+#include "journald-kmsg.h"
+#include "journald-server.h"
+#include "journald-syslog.h"
+#include "journald-wall.h"
+
+/* Warn once every 30s if we missed syslog message */
+#define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC)
+
+static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, const struct ucred *ucred, const struct timeval *tv) {
+
+ static const union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/journal/syslog",
+ };
+ struct msghdr msghdr = {
+ .msg_iov = (struct iovec *) iovec,
+ .msg_iovlen = n_iovec,
+ .msg_name = (struct sockaddr*) &sa.sa,
+ .msg_namelen = SOCKADDR_UN_LEN(sa.un),
+ };
+ struct cmsghdr *cmsg;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+ } control;
+
+ assert(s);
+ assert(iovec);
+ assert(n_iovec > 0);
+
+ if (ucred) {
+ zero(control);
+ msghdr.msg_control = &control;
+ msghdr.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&msghdr);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred));
+ msghdr.msg_controllen = cmsg->cmsg_len;
+ }
+
+ /* Forward the syslog message we received via /dev/log to
+ * /run/systemd/syslog. Unfortunately we currently can't set
+ * the SO_TIMESTAMP auxiliary data, and hence we don't. */
+
+ if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
+ return;
+
+ /* The socket is full? I guess the syslog implementation is
+ * too slow, and we shouldn't wait for that... */
+ if (errno == EAGAIN) {
+ s->n_forward_syslog_missed++;
+ return;
+ }
+
+ if (ucred && (errno == ESRCH || errno == EPERM)) {
+ struct ucred u;
+
+ /* Hmm, presumably the sender process vanished
+ * by now, or we don't have CAP_SYS_AMDIN, so
+ * let's fix it as good as we can, and retry */
+
+ u = *ucred;
+ u.pid = getpid();
+ memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred));
+
+ if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
+ return;
+
+ if (errno == EAGAIN) {
+ s->n_forward_syslog_missed++;
+ return;
+ }
+ }
+
+ if (errno != ENOENT)
+ log_debug_errno(errno, "Failed to forward syslog message: %m");
+}
+
+static void forward_syslog_raw(Server *s, int priority, const char *buffer, const struct ucred *ucred, const struct timeval *tv) {
+ struct iovec iovec;
+
+ assert(s);
+ assert(buffer);
+
+ if (LOG_PRI(priority) > s->max_level_syslog)
+ return;
+
+ IOVEC_SET_STRING(iovec, buffer);
+ forward_syslog_iovec(s, &iovec, 1, ucred, tv);
+}
+
+void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv) {
+ struct iovec iovec[5];
+ char header_priority[DECIMAL_STR_MAX(priority) + 3], header_time[64],
+ header_pid[sizeof("[]: ")-1 + DECIMAL_STR_MAX(pid_t) + 1];
+ int n = 0;
+ time_t t;
+ struct tm *tm;
+ char *ident_buf = NULL;
+
+ assert(s);
+ assert(priority >= 0);
+ assert(priority <= 999);
+ assert(message);
+
+ if (LOG_PRI(priority) > s->max_level_syslog)
+ return;
+
+ /* First: priority field */
+ xsprintf(header_priority, "<%i>", priority);
+ IOVEC_SET_STRING(iovec[n++], header_priority);
+
+ /* Second: timestamp */
+ t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
+ tm = localtime(&t);
+ if (!tm)
+ return;
+ if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
+ return;
+ IOVEC_SET_STRING(iovec[n++], header_time);
+
+ /* Third: identifier and PID */
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ xsprintf(header_pid, "["PID_FMT"]: ", ucred->pid);
+
+ if (identifier)
+ IOVEC_SET_STRING(iovec[n++], identifier);
+
+ IOVEC_SET_STRING(iovec[n++], header_pid);
+ } else if (identifier) {
+ IOVEC_SET_STRING(iovec[n++], identifier);
+ IOVEC_SET_STRING(iovec[n++], ": ");
+ }
+
+ /* Fourth: message */
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ forward_syslog_iovec(s, iovec, n, ucred, tv);
+
+ free(ident_buf);
+}
+
+int syslog_fixup_facility(int priority) {
+
+ if ((priority & LOG_FACMASK) == 0)
+ return (priority & LOG_PRIMASK) | LOG_USER;
+
+ return priority;
+}
+
+size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) {
+ const char *p;
+ char *t;
+ size_t l, e;
+
+ assert(buf);
+ assert(identifier);
+ assert(pid);
+
+ p = *buf;
+
+ p += strspn(p, WHITESPACE);
+ l = strcspn(p, WHITESPACE);
+
+ if (l <= 0 ||
+ p[l-1] != ':')
+ return 0;
+
+ e = l;
+ l--;
+
+ if (p[l-1] == ']') {
+ size_t k = l-1;
+
+ for (;;) {
+
+ if (p[k] == '[') {
+ t = strndup(p+k+1, l-k-2);
+ if (t)
+ *pid = t;
+
+ l = k;
+ break;
+ }
+
+ if (k == 0)
+ break;
+
+ k--;
+ }
+ }
+
+ t = strndup(p, l);
+ if (t)
+ *identifier = t;
+
+ if (strchr(WHITESPACE, p[e]))
+ e++;
+ *buf = p + e;
+ return e;
+}
+
+static void syslog_skip_date(char **buf) {
+ enum {
+ LETTER,
+ SPACE,
+ NUMBER,
+ SPACE_OR_NUMBER,
+ COLON
+ } sequence[] = {
+ LETTER, LETTER, LETTER,
+ SPACE,
+ SPACE_OR_NUMBER, NUMBER,
+ SPACE,
+ SPACE_OR_NUMBER, NUMBER,
+ COLON,
+ SPACE_OR_NUMBER, NUMBER,
+ COLON,
+ SPACE_OR_NUMBER, NUMBER,
+ SPACE
+ };
+
+ char *p;
+ unsigned i;
+
+ assert(buf);
+ assert(*buf);
+
+ p = *buf;
+
+ for (i = 0; i < ELEMENTSOF(sequence); i++, p++) {
+
+ if (!*p)
+ return;
+
+ switch (sequence[i]) {
+
+ case SPACE:
+ if (*p != ' ')
+ return;
+ break;
+
+ case SPACE_OR_NUMBER:
+ if (*p == ' ')
+ break;
+
+ /* fall through */
+
+ case NUMBER:
+ if (*p < '0' || *p > '9')
+ return;
+
+ break;
+
+ case LETTER:
+ if (!(*p >= 'A' && *p <= 'Z') &&
+ !(*p >= 'a' && *p <= 'z'))
+ return;
+
+ break;
+
+ case COLON:
+ if (*p != ':')
+ return;
+ break;
+
+ }
+ }
+
+ *buf = p;
+}
+
+void server_process_syslog_message(
+ Server *s,
+ const char *buf,
+ const struct ucred *ucred,
+ const struct timeval *tv,
+ const char *label,
+ size_t label_len) {
+
+ char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)],
+ syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)];
+ const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL;
+ struct iovec iovec[N_IOVEC_META_FIELDS + 6];
+ unsigned n = 0;
+ int priority = LOG_USER | LOG_INFO;
+ _cleanup_free_ char *identifier = NULL, *pid = NULL;
+ const char *orig;
+
+ assert(s);
+ assert(buf);
+
+ orig = buf;
+ syslog_parse_priority(&buf, &priority, true);
+
+ if (s->forward_to_syslog)
+ forward_syslog_raw(s, priority, orig, ucred, tv);
+
+ syslog_skip_date((char**) &buf);
+ syslog_parse_identifier(&buf, &identifier, &pid);
+
+ if (s->forward_to_kmsg)
+ server_forward_kmsg(s, priority, identifier, buf, ucred);
+
+ if (s->forward_to_console)
+ server_forward_console(s, priority, identifier, buf, ucred);
+
+ if (s->forward_to_wall)
+ server_forward_wall(s, priority, identifier, buf, ucred);
+
+ IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog");
+
+ xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK);
+ IOVEC_SET_STRING(iovec[n++], syslog_priority);
+
+ if (priority & LOG_FACMASK) {
+ xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority));
+ IOVEC_SET_STRING(iovec[n++], syslog_facility);
+ }
+
+ if (identifier) {
+ syslog_identifier = strjoina("SYSLOG_IDENTIFIER=", identifier);
+ IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+ }
+
+ if (pid) {
+ syslog_pid = strjoina("SYSLOG_PID=", pid);
+ IOVEC_SET_STRING(iovec[n++], syslog_pid);
+ }
+
+ message = strjoina("MESSAGE=", buf);
+ if (message)
+ IOVEC_SET_STRING(iovec[n++], message);
+
+ server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0);
+}
+
+int server_open_syslog_socket(Server *s) {
+
+ static const union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/journal/dev-log",
+ };
+ static const int one = 1;
+ int r;
+
+ assert(s);
+
+ if (s->syslog_fd < 0) {
+ s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (s->syslog_fd < 0)
+ return log_error_errno(errno, "socket() failed: %m");
+
+ (void) unlink(sa.un.sun_path);
+
+ r = bind(s->syslog_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ if (r < 0)
+ return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
+
+ (void) chmod(sa.un.sun_path, 0666);
+ } else
+ fd_nonblock(s->syslog_fd, 1);
+
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ if (r < 0)
+ return log_error_errno(errno, "SO_PASSCRED failed: %m");
+
+#ifdef HAVE_SELINUX
+ if (mac_selinux_have()) {
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+ if (r < 0)
+ log_warning_errno(errno, "SO_PASSSEC failed: %m");
+ }
+#endif
+
+ r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+ if (r < 0)
+ return log_error_errno(errno, "SO_TIMESTAMP failed: %m");
+
+ r = sd_event_add_io(s->event, &s->syslog_event_source, s->syslog_fd, EPOLLIN, server_process_datagram, s);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add syslog server fd to event loop: %m");
+
+ r = sd_event_source_set_priority(s->syslog_event_source, SD_EVENT_PRIORITY_NORMAL+5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to adjust syslog event source priority: %m");
+
+ return 0;
+}
+
+void server_maybe_warn_forward_syslog_missed(Server *s) {
+ usec_t n;
+
+ assert(s);
+
+ if (s->n_forward_syslog_missed <= 0)
+ return;
+
+ n = now(CLOCK_MONOTONIC);
+ if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n)
+ return;
+
+ server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED,
+ LOG_MESSAGE("Forwarding to syslog missed %u messages.",
+ s->n_forward_syslog_missed),
+ NULL);
+
+ s->n_forward_syslog_missed = 0;
+ s->last_warn_forward_syslog_missed = n;
+}
diff --git a/src/grp-journal/libjournal-core/journald-syslog.h b/src/grp-journal/libjournal-core/journald-syslog.h
new file mode 100644
index 0000000000..46ad715314
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-syslog.h
@@ -0,0 +1,33 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "journald-server.h"
+
+int syslog_fixup_facility(int priority) _const_;
+
+size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid);
+
+void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv);
+
+void server_process_syslog_message(Server *s, const char *buf, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len);
+int server_open_syslog_socket(Server *s);
+
+void server_maybe_warn_forward_syslog_missed(Server *s);
diff --git a/src/grp-journal/libjournal-core/journald-wall.c b/src/grp-journal/libjournal-core/journald-wall.c
new file mode 100644
index 0000000000..0c816de55f
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-wall.c
@@ -0,0 +1,72 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Sebastian Thorarensen
+
+ 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 "basic/alloc-util.h"
+#include "basic/formats-util.h"
+#include "basic/process-util.h"
+#include "basic/string-util.h"
+#include "shared/utmp-wtmp.h"
+
+#include "journald-server.h"
+#include "journald-wall.h"
+
+void server_forward_wall(
+ Server *s,
+ int priority,
+ const char *identifier,
+ const char *message,
+ const struct ucred *ucred) {
+
+ _cleanup_free_ char *ident_buf = NULL, *l_buf = NULL;
+ const char *l;
+ int r;
+
+ assert(s);
+ assert(message);
+
+ if (LOG_PRI(priority) > s->max_level_wall)
+ return;
+
+ if (ucred) {
+ if (!identifier) {
+ get_process_comm(ucred->pid, &ident_buf);
+ identifier = ident_buf;
+ }
+
+ if (asprintf(&l_buf, "%s["PID_FMT"]: %s", strempty(identifier), ucred->pid, message) < 0) {
+ log_oom();
+ return;
+ }
+
+ l = l_buf;
+
+ } else if (identifier) {
+
+ l = l_buf = strjoin(identifier, ": ", message, NULL);
+ if (!l_buf) {
+ log_oom();
+ return;
+ }
+ } else
+ l = message;
+
+ r = utmp_wall(l, "systemd-journald", NULL, NULL, NULL);
+ if (r < 0)
+ log_debug_errno(r, "Failed to send wall message: %m");
+}
diff --git a/src/grp-journal/libjournal-core/journald-wall.h b/src/grp-journal/libjournal-core/journald-wall.h
new file mode 100644
index 0000000000..ebc2b89fa8
--- /dev/null
+++ b/src/grp-journal/libjournal-core/journald-wall.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Sebastian Thorarensen
+
+ 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 "journald-server.h"
+
+void server_forward_wall(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred);
diff --git a/src/grp-journal/libjournal-core/test-audit-type.c b/src/grp-journal/libjournal-core/test-audit-type.c
new file mode 100644
index 0000000000..812a3953ea
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-audit-type.c
@@ -0,0 +1,43 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2015 Zbigniew Jędrzejewski-Szmek
+
+ 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 <linux/audit.h>
+
+#include "sd-journal/audit-type.h"
+
+static void print_audit_label(int i) {
+ const char *name;
+
+ name = audit_type_name_alloca(i);
+ /* This is a separate function only because of alloca */
+ printf("%i → %s → %s\n", i, audit_type_to_string(i), name);
+}
+
+static void test_audit_type(void) {
+ int i;
+
+ for (i = 0; i <= AUDIT_KERNEL; i++)
+ print_audit_label(i);
+}
+
+int main(int argc, char **argv) {
+ test_audit_type();
+}
diff --git a/src/grp-journal/libjournal-core/test-catalog.c b/src/grp-journal/libjournal-core/test-catalog.c
new file mode 100644
index 0000000000..73ffb16104
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-catalog.c
@@ -0,0 +1,264 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ 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 <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <unistd.h>
+
+#include <systemd/sd-messages.h>
+
+#include "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/log.h"
+#include "basic/macro.h"
+#include "basic/string-util.h"
+#include "basic/util.h"
+#include "sd-journal/catalog.h"
+
+static const char *catalog_dirs[] = {
+ CATALOG_DIR,
+ NULL,
+};
+
+static const char *no_catalog_dirs[] = {
+ "/bin/hopefully/with/no/catalog",
+ NULL
+};
+
+static Hashmap * test_import(const char* contents, ssize_t size, int code) {
+ int r;
+ char name[] = "/tmp/test-catalog.XXXXXX";
+ _cleanup_close_ int fd;
+ Hashmap *h;
+
+ if (size < 0)
+ size = strlen(contents);
+
+ assert_se(h = hashmap_new(&catalog_hash_ops));
+
+ fd = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
+ assert_se(fd >= 0);
+ assert_se(write(fd, contents, size) == size);
+
+ r = catalog_import_file(h, name);
+ assert_se(r == code);
+
+ unlink(name);
+
+ return h;
+}
+
+static void test_catalog_import_invalid(void) {
+ _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
+
+ h = test_import("xxx", -1, -EINVAL);
+ assert_se(hashmap_isempty(h));
+}
+
+static void test_catalog_import_badid(void) {
+ _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
+ const char *input =
+"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededede\n" \
+"Subject: message\n" \
+"\n" \
+"payload\n";
+ h = test_import(input, -1, -EINVAL);
+}
+
+static void test_catalog_import_one(void) {
+ _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
+ char *payload;
+ Iterator j;
+
+ const char *input =
+"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \
+"Subject: message\n" \
+"\n" \
+"payload\n";
+ const char *expect =
+"Subject: message\n" \
+"\n" \
+"payload\n";
+
+ h = test_import(input, -1, 0);
+ assert_se(hashmap_size(h) == 1);
+
+ HASHMAP_FOREACH(payload, h, j) {
+ printf("expect: %s\n", expect);
+ printf("actual: %s\n", payload);
+ assert_se(streq(expect, payload));
+ }
+}
+
+static void test_catalog_import_merge(void) {
+ _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
+ char *payload;
+ Iterator j;
+
+ const char *input =
+"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \
+"Subject: message\n" \
+"Defined-By: me\n" \
+"\n" \
+"payload\n" \
+"\n" \
+"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \
+"Subject: override subject\n" \
+"X-Header: hello\n" \
+"\n" \
+"override payload\n";
+
+ const char *combined =
+"Subject: override subject\n" \
+"X-Header: hello\n" \
+"Subject: message\n" \
+"Defined-By: me\n" \
+"\n" \
+"override payload\n";
+
+ h = test_import(input, -1, 0);
+ assert_se(hashmap_size(h) == 1);
+
+ HASHMAP_FOREACH(payload, h, j) {
+ assert_se(streq(combined, payload));
+ }
+}
+
+static void test_catalog_import_merge_no_body(void) {
+ _cleanup_hashmap_free_free_free_ Hashmap *h = NULL;
+ char *payload;
+ Iterator j;
+
+ const char *input =
+"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \
+"Subject: message\n" \
+"Defined-By: me\n" \
+"\n" \
+"payload\n" \
+"\n" \
+"-- 0027229ca0644181a76c4e92458afaff dededededededededededededededed\n" \
+"Subject: override subject\n" \
+"X-Header: hello\n" \
+"\n";
+
+ const char *combined =
+"Subject: override subject\n" \
+"X-Header: hello\n" \
+"Subject: message\n" \
+"Defined-By: me\n" \
+"\n" \
+"payload\n";
+
+ h = test_import(input, -1, 0);
+ assert_se(hashmap_size(h) == 1);
+
+ HASHMAP_FOREACH(payload, h, j) {
+ assert_se(streq(combined, payload));
+ }
+}
+
+static const char* database = NULL;
+
+static void test_catalog_update(void) {
+ static char name[] = "/tmp/test-catalog.XXXXXX";
+ int r;
+
+ r = mkostemp_safe(name, O_RDWR|O_CLOEXEC);
+ assert_se(r >= 0);
+
+ database = name;
+
+ /* Test what happens if there are no files. */
+ r = catalog_update(database, NULL, NULL);
+ assert_se(r >= 0);
+
+ /* Test what happens if there are no files in the directory. */
+ r = catalog_update(database, NULL, no_catalog_dirs);
+ assert_se(r >= 0);
+
+ /* Make sure that we at least have some files loaded or the
+ catalog_list below will fail. */
+ r = catalog_update(database, NULL, catalog_dirs);
+ assert_se(r >= 0);
+}
+
+static void test_catalog_file_lang(void) {
+ _cleanup_free_ char *lang = NULL, *lang2 = NULL, *lang3 = NULL, *lang4 = NULL;
+
+ assert_se(catalog_file_lang("systemd.de_DE.catalog", &lang) == 1);
+ assert_se(streq(lang, "de_DE"));
+
+ assert_se(catalog_file_lang("systemd..catalog", &lang2) == 0);
+ assert_se(lang2 == NULL);
+
+ assert_se(catalog_file_lang("systemd.fr.catalog", &lang2) == 1);
+ assert_se(streq(lang2, "fr"));
+
+ assert_se(catalog_file_lang("systemd.fr.catalog.gz", &lang3) == 0);
+ assert_se(lang3 == NULL);
+
+ assert_se(catalog_file_lang("systemd.01234567890123456789012345678901.catalog", &lang3) == 0);
+ assert_se(lang3 == NULL);
+
+ assert_se(catalog_file_lang("systemd.0123456789012345678901234567890.catalog", &lang3) == 1);
+ assert_se(streq(lang3, "0123456789012345678901234567890"));
+
+ assert_se(catalog_file_lang("/x/y/systemd.catalog", &lang4) == 0);
+ assert_se(lang4 == NULL);
+
+ assert_se(catalog_file_lang("/x/y/systemd.ru_RU.catalog", &lang4) == 1);
+ assert_se(streq(lang4, "ru_RU"));
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_free_ char *text = NULL;
+ int r;
+
+ setlocale(LC_ALL, "de_DE.UTF-8");
+
+ log_parse_environment();
+ log_open();
+
+ test_catalog_file_lang();
+
+ test_catalog_import_invalid();
+ test_catalog_import_badid();
+ test_catalog_import_one();
+ test_catalog_import_merge();
+ test_catalog_import_merge_no_body();
+
+ test_catalog_update();
+
+ r = catalog_list(stdout, database, true);
+ assert_se(r >= 0);
+
+ r = catalog_list(stdout, database, false);
+ assert_se(r >= 0);
+
+ assert_se(catalog_get(database, SD_MESSAGE_COREDUMP, &text) >= 0);
+ printf(">>>%s<<<\n", text);
+
+ if (database)
+ unlink(database);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-compress-benchmark.c b/src/grp-journal/libjournal-core/test-compress-benchmark.c
new file mode 100644
index 0000000000..4292e4f8cb
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-compress-benchmark.c
@@ -0,0 +1,180 @@
+/***
+ This file is part of systemd
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ 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 "basic/alloc-util.h"
+#include "basic/macro.h"
+#include "basic/parse-util.h"
+#include "basic/random-util.h"
+#include "basic/string-util.h"
+#include "basic/util.h"
+#include "sd-journal/compress.h"
+
+typedef int (compress_t)(const void *src, uint64_t src_size, void *dst,
+ size_t dst_alloc_size, size_t *dst_size);
+typedef int (decompress_t)(const void *src, uint64_t src_size,
+ void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max);
+
+static usec_t arg_duration = 2 * USEC_PER_SEC;
+static size_t arg_start;
+
+#define MAX_SIZE (1024*1024LU)
+#define PRIME 1048571 /* A prime close enough to one megabyte that mod 4 == 3 */
+
+static size_t _permute(size_t x) {
+ size_t residue;
+
+ if (x >= PRIME)
+ return x;
+
+ residue = x*x % PRIME;
+ if (x <= PRIME / 2)
+ return residue;
+ else
+ return PRIME - residue;
+}
+
+static size_t permute(size_t x) {
+ return _permute((_permute(x) + arg_start) % MAX_SIZE ^ 0xFF345);
+}
+
+static char* make_buf(size_t count, const char *type) {
+ char *buf;
+ size_t i;
+
+ buf = malloc(count);
+ assert_se(buf);
+
+ if (streq(type, "zeros"))
+ memzero(buf, count);
+ else if (streq(type, "simple"))
+ for (i = 0; i < count; i++)
+ buf[i] = 'a' + i % ('z' - 'a' + 1);
+ else if (streq(type, "random")) {
+ size_t step = count / 10;
+
+ random_bytes(buf, step);
+ memzero(buf + 1*step, step);
+ random_bytes(buf + 2*step, step);
+ memzero(buf + 3*step, step);
+ random_bytes(buf + 4*step, step);
+ memzero(buf + 5*step, step);
+ random_bytes(buf + 6*step, step);
+ memzero(buf + 7*step, step);
+ random_bytes(buf + 8*step, step);
+ memzero(buf + 9*step, step);
+ } else
+ assert_not_reached("here");
+
+ return buf;
+}
+
+static void test_compress_decompress(const char* label, const char* type,
+ compress_t compress, decompress_t decompress) {
+ usec_t n, n2 = 0;
+ float dt;
+
+ _cleanup_free_ char *text, *buf;
+ _cleanup_free_ void *buf2 = NULL;
+ size_t buf2_allocated = 0;
+ size_t skipped = 0, compressed = 0, total = 0;
+
+ text = make_buf(MAX_SIZE, type);
+ buf = calloc(MAX_SIZE + 1, 1);
+ assert_se(text && buf);
+
+ n = now(CLOCK_MONOTONIC);
+
+ for (size_t i = 0; i <= MAX_SIZE; i++) {
+ size_t j = 0, k = 0, size;
+ int r;
+
+ size = permute(i);
+ if (size == 0)
+ continue;
+
+ log_debug("%s %zu %zu", type, i, size);
+
+ memzero(buf, MIN(size + 1000, MAX_SIZE));
+
+ r = compress(text, size, buf, size, &j);
+ /* assume compression must be successful except for small or random inputs */
+ assert_se(r == 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random"));
+
+ /* check for overwrites */
+ assert_se(buf[size] == 0);
+ if (r != 0) {
+ skipped += size;
+ continue;
+ }
+
+ assert_se(j > 0);
+ if (j >= size)
+ log_error("%s \"compressed\" %zu -> %zu", label, size, j);
+
+ r = decompress(buf, j, &buf2, &buf2_allocated, &k, 0);
+ assert_se(r == 0);
+ assert_se(buf2_allocated >= k);
+ assert_se(k == size);
+
+ assert_se(memcmp(text, buf2, size) == 0);
+
+ total += size;
+ compressed += j;
+
+ n2 = now(CLOCK_MONOTONIC);
+ if (n2 - n > arg_duration)
+ break;
+ }
+
+ dt = (n2-n) / 1e6;
+
+ log_info("%s/%s: compressed & decompressed %zu bytes in %.2fs (%.2fMiB/s), "
+ "mean compresion %.2f%%, skipped %zu bytes",
+ label, type, total, dt,
+ total / 1024. / 1024 / dt,
+ 100 - compressed * 100. / total,
+ skipped);
+}
+
+int main(int argc, char *argv[]) {
+ const char *i;
+
+ log_set_max_level(LOG_INFO);
+
+ if (argc >= 2) {
+ unsigned x;
+
+ assert_se(safe_atou(argv[1], &x) >= 0);
+ arg_duration = x * USEC_PER_SEC;
+ }
+ if (argc == 3)
+ (void) safe_atozu(argv[2], &arg_start);
+ else
+ arg_start = getpid();
+
+ NULSTR_FOREACH(i, "zeros\0simple\0random\0") {
+#ifdef HAVE_XZ
+ test_compress_decompress("XZ", i, compress_blob_xz, decompress_blob_xz);
+#endif
+#ifdef HAVE_LZ4
+ test_compress_decompress("LZ4", i, compress_blob_lz4, decompress_blob_lz4);
+#endif
+ }
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-compress.c b/src/grp-journal/libjournal-core/test-compress.c
new file mode 100644
index 0000000000..5682c28e8c
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-compress.c
@@ -0,0 +1,308 @@
+/***
+ This file is part of systemd
+
+ Copyright 2014 Ronny Chevalier
+
+ 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/>.
+***/
+
+#ifdef HAVE_LZ4
+#include <lz4.h>
+#endif
+
+#include "basic/alloc-util.h"
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/macro.h"
+#include "basic/random-util.h"
+#include "basic/util.h"
+#include "sd-journal/compress.h"
+
+#ifdef HAVE_XZ
+# define XZ_OK 0
+#else
+# define XZ_OK -EPROTONOSUPPORT
+#endif
+
+#ifdef HAVE_LZ4
+# define LZ4_OK 0
+#else
+# define LZ4_OK -EPROTONOSUPPORT
+#endif
+
+typedef int (compress_blob_t)(const void *src, uint64_t src_size,
+ void *dst, size_t dst_alloc_size, size_t *dst_size);
+typedef int (decompress_blob_t)(const void *src, uint64_t src_size,
+ void **dst, size_t *dst_alloc_size,
+ size_t* dst_size, size_t dst_max);
+typedef int (decompress_sw_t)(const void *src, uint64_t src_size,
+ void **buffer, size_t *buffer_size,
+ const void *prefix, size_t prefix_len,
+ uint8_t extra);
+
+typedef int (compress_stream_t)(int fdf, int fdt, uint64_t max_bytes);
+typedef int (decompress_stream_t)(int fdf, int fdt, uint64_t max_size);
+
+static void test_compress_decompress(int compression,
+ compress_blob_t compress,
+ decompress_blob_t decompress,
+ const char *data,
+ size_t data_len,
+ bool may_fail) {
+ char compressed[512];
+ size_t csize, usize = 0;
+ _cleanup_free_ char *decompressed = NULL;
+ int r;
+
+ log_info("/* testing %s %s blob compression/decompression */",
+ object_compressed_to_string(compression), data);
+
+ r = compress(data, data_len, compressed, sizeof(compressed), &csize);
+ if (r == -ENOBUFS) {
+ log_info_errno(r, "compression failed: %m");
+ assert_se(may_fail);
+ } else {
+ assert_se(r == 0);
+ r = decompress(compressed, csize,
+ (void **) &decompressed, &usize, &csize, 0);
+ assert_se(r == 0);
+ assert_se(decompressed);
+ assert_se(memcmp(decompressed, data, data_len) == 0);
+ }
+
+ r = decompress("garbage", 7,
+ (void **) &decompressed, &usize, &csize, 0);
+ assert_se(r < 0);
+
+ /* make sure to have the minimal lz4 compressed size */
+ r = decompress("00000000\1g", 9,
+ (void **) &decompressed, &usize, &csize, 0);
+ assert_se(r < 0);
+
+ r = decompress("\100000000g", 9,
+ (void **) &decompressed, &usize, &csize, 0);
+ assert_se(r < 0);
+
+ memzero(decompressed, usize);
+}
+
+static void test_decompress_startswith(int compression,
+ compress_blob_t compress,
+ decompress_sw_t decompress_sw,
+ const char *data,
+ size_t data_len,
+ bool may_fail) {
+
+ char *compressed;
+ _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL;
+ size_t csize, usize = 0, len;
+ int r;
+
+ log_info("/* testing decompress_startswith with %s on %.20s text*/",
+ object_compressed_to_string(compression), data);
+
+#define BUFSIZE_1 512
+#define BUFSIZE_2 20000
+
+ compressed = compressed1 = malloc(BUFSIZE_1);
+ assert_se(compressed1);
+ r = compress(data, data_len, compressed, BUFSIZE_1, &csize);
+ if (r == -ENOBUFS) {
+ log_info_errno(r, "compression failed: %m");
+ assert_se(may_fail);
+
+ compressed = compressed2 = malloc(BUFSIZE_2);
+ assert_se(compressed2);
+ r = compress(data, data_len, compressed, BUFSIZE_2, &csize);
+ assert(r == 0);
+ }
+ assert_se(r == 0);
+
+ len = strlen(data);
+
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0');
+ assert_se(r > 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, 'w');
+ assert_se(r == 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, "barbarbar", 9, ' ');
+ assert_se(r == 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, data[len-1]);
+ assert_se(r > 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, 'w');
+ assert_se(r == 0);
+ r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0');
+ assert_se(r > 0);
+}
+
+static void test_compress_stream(int compression,
+ const char* cat,
+ compress_stream_t compress,
+ decompress_stream_t decompress,
+ const char *srcfile) {
+
+ _cleanup_close_ int src = -1, dst = -1, dst2 = -1;
+ char pattern[] = "/tmp/systemd-test.compressed.XXXXXX",
+ pattern2[] = "/tmp/systemd-test.compressed.XXXXXX";
+ int r;
+ _cleanup_free_ char *cmd = NULL, *cmd2;
+ struct stat st = {};
+
+ log_debug("/* testing %s compression */",
+ object_compressed_to_string(compression));
+
+ log_debug("/* create source from %s */", srcfile);
+
+ assert_se((src = open(srcfile, O_RDONLY|O_CLOEXEC)) >= 0);
+
+ log_debug("/* test compression */");
+
+ assert_se((dst = mkostemp_safe(pattern, O_RDWR|O_CLOEXEC)) >= 0);
+
+ assert_se(compress(src, dst, -1) == 0);
+
+ if (cat) {
+ assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0);
+ assert_se(system(cmd) == 0);
+ }
+
+ log_debug("/* test decompression */");
+
+ assert_se((dst2 = mkostemp_safe(pattern2, O_RDWR|O_CLOEXEC)) >= 0);
+
+ assert_se(stat(srcfile, &st) == 0);
+
+ assert_se(lseek(dst, 0, SEEK_SET) == 0);
+ r = decompress(dst, dst2, st.st_size);
+ assert_se(r == 0);
+
+ assert_se(asprintf(&cmd2, "diff %s %s", srcfile, pattern2) > 0);
+ assert_se(system(cmd2) == 0);
+
+ log_debug("/* test faulty decompression */");
+
+ assert_se(lseek(dst, 1, SEEK_SET) == 1);
+ r = decompress(dst, dst2, st.st_size);
+ assert_se(r == -EBADMSG || r == 0);
+
+ assert_se(lseek(dst, 0, SEEK_SET) == 0);
+ assert_se(lseek(dst2, 0, SEEK_SET) == 0);
+ r = decompress(dst, dst2, st.st_size - 1);
+ assert_se(r == -EFBIG);
+
+ assert_se(unlink(pattern) == 0);
+ assert_se(unlink(pattern2) == 0);
+}
+
+#ifdef HAVE_LZ4
+static void test_lz4_decompress_partial(void) {
+ char buf[20000];
+ size_t buf_size = sizeof(buf), compressed;
+ int r;
+ _cleanup_free_ char *huge = NULL;
+
+#define HUGE_SIZE (4096*1024)
+ huge = malloc(HUGE_SIZE);
+ memset(huge, 'x', HUGE_SIZE);
+ memcpy(huge, "HUGE=", 5);
+
+ r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size);
+ assert_se(r >= 0);
+ compressed = r;
+ log_info("Compressed %i → %zu", HUGE_SIZE, compressed);
+
+ r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE);
+ assert_se(r >= 0);
+ log_info("Decompressed → %i", r);
+
+ r = LZ4_decompress_safe_partial(buf, huge,
+ compressed,
+ 12, HUGE_SIZE);
+ assert_se(r >= 0);
+ log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r);
+
+ /* We expect this to fail, because that's how current lz4 works. If this
+ * call succeeds, then lz4 has been fixed, and we need to change our code.
+ */
+ r = LZ4_decompress_safe_partial(buf, huge,
+ compressed,
+ 12, HUGE_SIZE-1);
+ assert_se(r < 0);
+ log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r);
+}
+#endif
+
+int main(int argc, char *argv[]) {
+ const char text[] =
+ "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF"
+ "foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF";
+
+ char data[512] = "random\0";
+
+ char huge[4096*1024];
+ memset(huge, 'x', sizeof(huge));
+ memcpy(huge, "HUGE=", 5);
+ char_array_0(huge);
+
+ log_set_max_level(LOG_DEBUG);
+
+ random_bytes(data + 7, sizeof(data) - 7);
+
+#ifdef HAVE_XZ
+ test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz,
+ text, sizeof(text), false);
+ test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz,
+ data, sizeof(data), true);
+
+ test_decompress_startswith(OBJECT_COMPRESSED_XZ,
+ compress_blob_xz, decompress_startswith_xz,
+ text, sizeof(text), false);
+ test_decompress_startswith(OBJECT_COMPRESSED_XZ,
+ compress_blob_xz, decompress_startswith_xz,
+ data, sizeof(data), true);
+ test_decompress_startswith(OBJECT_COMPRESSED_XZ,
+ compress_blob_xz, decompress_startswith_xz,
+ huge, sizeof(huge), true);
+
+ test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat",
+ compress_stream_xz, decompress_stream_xz, argv[0]);
+#else
+ log_info("/* XZ test skipped */");
+#endif
+
+#ifdef HAVE_LZ4
+ test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4,
+ text, sizeof(text), false);
+ test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4,
+ data, sizeof(data), true);
+
+ test_decompress_startswith(OBJECT_COMPRESSED_LZ4,
+ compress_blob_lz4, decompress_startswith_lz4,
+ text, sizeof(text), false);
+ test_decompress_startswith(OBJECT_COMPRESSED_LZ4,
+ compress_blob_lz4, decompress_startswith_lz4,
+ data, sizeof(data), true);
+ test_decompress_startswith(OBJECT_COMPRESSED_LZ4,
+ compress_blob_lz4, decompress_startswith_lz4,
+ huge, sizeof(huge), true);
+
+ test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat",
+ compress_stream_lz4, decompress_stream_lz4, argv[0]);
+
+ test_lz4_decompress_partial();
+#else
+ log_info("/* LZ4 test skipped */");
+#endif
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-enum.c b/src/grp-journal/libjournal-core/test-journal-enum.c
new file mode 100644
index 0000000000..588e12c800
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-enum.c
@@ -0,0 +1,53 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <systemd/sd-journal.h>
+
+#include "basic/log.h"
+#include "basic/macro.h"
+#include "sd-journal/journal-internal.h"
+
+int main(int argc, char *argv[]) {
+ unsigned n = 0;
+ _cleanup_(sd_journal_closep) sd_journal*j = NULL;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0);
+
+ assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0);
+
+ SD_JOURNAL_FOREACH_BACKWARDS(j) {
+ const void *d;
+ size_t l;
+
+ assert_se(sd_journal_get_data(j, "MESSAGE", &d, &l) >= 0);
+
+ printf("%.*s\n", (int) l, (char*) d);
+
+ n++;
+ if (n >= 10)
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-flush.c b/src/grp-journal/libjournal-core/test-journal-flush.c
new file mode 100644
index 0000000000..66ae8f83c4
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-flush.c
@@ -0,0 +1,75 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ 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 <fcntl.h>
+
+#include <systemd/sd-journal.h>
+
+#include "basic/alloc-util.h"
+#include "basic/macro.h"
+#include "basic/string-util.h"
+#include "sd-journal/journal-file.h"
+#include "sd-journal/journal-internal.h"
+
+int main(int argc, char *argv[]) {
+ _cleanup_free_ char *fn = NULL;
+ char dn[] = "/var/tmp/test-journal-flush.XXXXXX";
+ JournalFile *new_journal = NULL;
+ sd_journal *j = NULL;
+ unsigned n = 0;
+ int r;
+
+ assert_se(mkdtemp(dn));
+ fn = strappend(dn, "/test.journal");
+
+ r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0644, false, false, NULL, NULL, NULL, NULL, &new_journal);
+ assert_se(r >= 0);
+
+ r = sd_journal_open(&j, 0);
+ assert_se(r >= 0);
+
+ sd_journal_set_data_threshold(j, 0);
+
+ SD_JOURNAL_FOREACH(j) {
+ Object *o;
+ JournalFile *f;
+
+ f = j->current_file;
+ assert_se(f && f->current_offset > 0);
+
+ r = journal_file_move_to_object(f, OBJECT_ENTRY, f->current_offset, &o);
+ assert_se(r >= 0);
+
+ r = journal_file_copy_entry(f, new_journal, o, f->current_offset, NULL, NULL, NULL);
+ assert_se(r >= 0);
+
+ n++;
+ if (n > 10000)
+ break;
+ }
+
+ sd_journal_close(j);
+
+ (void) journal_file_close(new_journal);
+
+ unlink(fn);
+ assert_se(rmdir(dn) == 0);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-init.c b/src/grp-journal/libjournal-core/test-journal-init.c
new file mode 100644
index 0000000000..c5f1d345e9
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-init.c
@@ -0,0 +1,64 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ 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 <systemd/sd-journal.h>
+
+#include "basic/log.h"
+#include "basic/parse-util.h"
+#include "basic/rm-rf.h"
+#include "basic/util.h"
+
+int main(int argc, char *argv[]) {
+ sd_journal *j;
+ int r, i, I = 100;
+ char t[] = "/tmp/journal-stream-XXXXXX";
+
+ log_set_max_level(LOG_DEBUG);
+
+ if (argc >= 2) {
+ r = safe_atoi(argv[1], &I);
+ if (r < 0)
+ log_info("Could not parse loop count argument. Using default.");
+ }
+
+ log_info("Running %d loops", I);
+
+ assert_se(mkdtemp(t));
+
+ for (i = 0; i < I; i++) {
+ r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
+ assert_se(r == 0);
+
+ sd_journal_close(j);
+
+ r = sd_journal_open_directory(&j, t, 0);
+ assert_se(r == 0);
+
+ sd_journal_close(j);
+
+ j = NULL;
+ r = sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY);
+ assert_se(r == -EINVAL);
+ assert_se(j == NULL);
+ }
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-interleaving.c b/src/grp-journal/libjournal-core/test-journal-interleaving.c
new file mode 100644
index 0000000000..004394d18a
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-interleaving.c
@@ -0,0 +1,307 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2013 Marius Vollmer
+ Copyright 2013 Zbigniew Jędrzejewski-Szmek
+
+ 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 <fcntl.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "basic/alloc-util.h"
+#include "basic/log.h"
+#include "basic/parse-util.h"
+#include "basic/rm-rf.h"
+#include "basic/util.h"
+#include "sd-journal/journal-file.h"
+#include "sd-journal/journal-vacuum.h"
+
+/* This program tests skipping around in a multi-file journal.
+ */
+
+static bool arg_keep = false;
+
+noreturn static void log_assert_errno(const char *text, int eno, const char *file, int line, const char *func) {
+ log_internal(LOG_CRIT, 0, file, line, func,
+ "'%s' failed at %s:%u (%s): %s.",
+ text, file, line, func, strerror(eno));
+ abort();
+}
+
+#define assert_ret(expr) \
+ do { \
+ int _r_ = (expr); \
+ if (_unlikely_(_r_ < 0)) \
+ log_assert_errno(#expr, -_r_, __FILE__, __LINE__, __PRETTY_FUNCTION__); \
+ } while (false)
+
+static JournalFile *test_open(const char *name) {
+ JournalFile *f;
+ assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, 0644, true, false, NULL, NULL, NULL, NULL, &f));
+ return f;
+}
+
+static void test_close(JournalFile *f) {
+ (void) journal_file_close (f);
+}
+
+static void append_number(JournalFile *f, int n, uint64_t *seqnum) {
+ char *p;
+ dual_timestamp ts;
+ static dual_timestamp previous_ts = {};
+ struct iovec iovec[1];
+
+ dual_timestamp_get(&ts);
+
+ if (ts.monotonic <= previous_ts.monotonic)
+ ts.monotonic = previous_ts.monotonic + 1;
+
+ if (ts.realtime <= previous_ts.realtime)
+ ts.realtime = previous_ts.realtime + 1;
+
+ previous_ts = ts;
+
+ assert_se(asprintf(&p, "NUMBER=%d", n) >= 0);
+ iovec[0].iov_base = p;
+ iovec[0].iov_len = strlen(p);
+ assert_ret(journal_file_append_entry(f, &ts, iovec, 1, seqnum, NULL, NULL));
+ free(p);
+}
+
+static void test_check_number (sd_journal *j, int n) {
+ const void *d;
+ _cleanup_free_ char *k;
+ size_t l;
+ int x;
+
+ assert_ret(sd_journal_get_data(j, "NUMBER", &d, &l));
+ assert_se(k = strndup(d, l));
+ printf("%s\n", k);
+
+ assert_se(safe_atoi(k + 7, &x) >= 0);
+ assert_se(n == x);
+}
+
+static void test_check_numbers_down (sd_journal *j, int count) {
+ int i;
+
+ for (i = 1; i <= count; i++) {
+ int r;
+ test_check_number(j, i);
+ assert_ret(r = sd_journal_next(j));
+ if (i == count)
+ assert_se(r == 0);
+ else
+ assert_se(r == 1);
+ }
+
+}
+
+static void test_check_numbers_up (sd_journal *j, int count) {
+ for (int i = count; i >= 1; i--) {
+ int r;
+ test_check_number(j, i);
+ assert_ret(r = sd_journal_previous(j));
+ if (i == 1)
+ assert_se(r == 0);
+ else
+ assert_se(r == 1);
+ }
+
+}
+
+static void setup_sequential(void) {
+ JournalFile *one, *two;
+ one = test_open("one.journal");
+ two = test_open("two.journal");
+ append_number(one, 1, NULL);
+ append_number(one, 2, NULL);
+ append_number(two, 3, NULL);
+ append_number(two, 4, NULL);
+ test_close(one);
+ test_close(two);
+}
+
+static void setup_interleaved(void) {
+ JournalFile *one, *two;
+ one = test_open("one.journal");
+ two = test_open("two.journal");
+ append_number(one, 1, NULL);
+ append_number(two, 2, NULL);
+ append_number(one, 3, NULL);
+ append_number(two, 4, NULL);
+ test_close(one);
+ test_close(two);
+}
+
+static void test_skip(void (*setup)(void)) {
+ char t[] = "/tmp/journal-skip-XXXXXX";
+ sd_journal *j;
+ int r;
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ setup();
+
+ /* Seek to head, iterate down.
+ */
+ assert_ret(sd_journal_open_directory(&j, t, 0));
+ assert_ret(sd_journal_seek_head(j));
+ assert_ret(sd_journal_next(j));
+ test_check_numbers_down(j, 4);
+ sd_journal_close(j);
+
+ /* Seek to tail, iterate up.
+ */
+ assert_ret(sd_journal_open_directory(&j, t, 0));
+ assert_ret(sd_journal_seek_tail(j));
+ assert_ret(sd_journal_previous(j));
+ test_check_numbers_up(j, 4);
+ sd_journal_close(j);
+
+ /* Seek to tail, skip to head, iterate down.
+ */
+ assert_ret(sd_journal_open_directory(&j, t, 0));
+ assert_ret(sd_journal_seek_tail(j));
+ assert_ret(r = sd_journal_previous_skip(j, 4));
+ assert_se(r == 4);
+ test_check_numbers_down(j, 4);
+ sd_journal_close(j);
+
+ /* Seek to head, skip to tail, iterate up.
+ */
+ assert_ret(sd_journal_open_directory(&j, t, 0));
+ assert_ret(sd_journal_seek_head(j));
+ assert_ret(r = sd_journal_next_skip(j, 4));
+ assert_se(r == 4);
+ test_check_numbers_up(j, 4);
+ sd_journal_close(j);
+
+ log_info("Done...");
+
+ if (arg_keep)
+ log_info("Not removing %s", t);
+ else {
+ journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+ }
+
+ puts("------------------------------------------------------------");
+}
+
+static void test_sequence_numbers(void) {
+
+ char t[] = "/tmp/journal-seq-XXXXXX";
+ JournalFile *one, *two;
+ uint64_t seqnum = 0;
+ sd_id128_t seqnum_id;
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0644,
+ true, false, NULL, NULL, NULL, NULL, &one) == 0);
+
+ append_number(one, 1, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 1);
+ append_number(one, 2, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 2);
+
+ assert_se(one->header->state == STATE_ONLINE);
+ assert_se(!sd_id128_equal(one->header->file_id, one->header->machine_id));
+ assert_se(!sd_id128_equal(one->header->file_id, one->header->boot_id));
+ assert_se(sd_id128_equal(one->header->file_id, one->header->seqnum_id));
+
+ memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t));
+
+ assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0644,
+ true, false, NULL, NULL, NULL, one, &two) == 0);
+
+ assert_se(two->header->state == STATE_ONLINE);
+ assert_se(!sd_id128_equal(two->header->file_id, one->header->file_id));
+ assert_se(sd_id128_equal(one->header->machine_id, one->header->machine_id));
+ assert_se(sd_id128_equal(one->header->boot_id, one->header->boot_id));
+ assert_se(sd_id128_equal(one->header->seqnum_id, one->header->seqnum_id));
+
+ append_number(two, 3, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 3);
+ append_number(two, 4, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 4);
+
+ test_close(two);
+
+ append_number(one, 5, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 5);
+
+ append_number(one, 6, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 6);
+
+ test_close(one);
+
+ /* restart server */
+ seqnum = 0;
+
+ assert_se(journal_file_open(-1, "two.journal", O_RDWR, 0,
+ true, false, NULL, NULL, NULL, NULL, &two) == 0);
+
+ assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id));
+
+ append_number(two, 7, &seqnum);
+ printf("seqnum=%"PRIu64"\n", seqnum);
+ assert_se(seqnum == 5);
+
+ /* So..., here we have the same seqnum in two files with the
+ * same seqnum_id. */
+
+ test_close(two);
+
+ log_info("Done...");
+
+ if (arg_keep)
+ log_info("Not removing %s", t);
+ else {
+ journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ log_set_max_level(LOG_DEBUG);
+
+ /* journal_file_open requires a valid machine id */
+ if (access("/etc/machine-id", F_OK) != 0)
+ return EXIT_TEST_SKIP;
+
+ arg_keep = argc > 1;
+
+ test_skip(setup_sequential);
+ test_skip(setup_interleaved);
+
+ test_sequence_numbers();
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-match.c b/src/grp-journal/libjournal-core/test-journal-match.c
new file mode 100644
index 0000000000..967e30c46a
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-match.c
@@ -0,0 +1,76 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <systemd/sd-journal.h>
+
+#include "basic/alloc-util.h"
+#include "basic/log.h"
+#include "basic/string-util.h"
+#include "basic/util.h"
+#include "sd-journal/journal-internal.h"
+
+int main(int argc, char *argv[]) {
+ _cleanup_(sd_journal_closep) sd_journal*j = NULL;
+ _cleanup_free_ char *t;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(sd_journal_open(&j, 0) >= 0);
+
+ assert_se(sd_journal_add_match(j, "foobar", 0) < 0);
+ assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0);
+ assert_se(sd_journal_add_match(j, "", 0) < 0);
+ assert_se(sd_journal_add_match(j, "=", 0) < 0);
+ assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0);
+ assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=mmmm", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "HALLO=", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "QUUX=yyyyy", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "PIFF=paff", 0) >= 0);
+
+ assert_se(sd_journal_add_disjunction(j) >= 0);
+
+ assert_se(sd_journal_add_match(j, "ONE=one", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "ONE=two", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "TWO=two", 0) >= 0);
+
+ assert_se(sd_journal_add_conjunction(j) >= 0);
+
+ assert_se(sd_journal_add_match(j, "L4_1=yes", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "L4_1=ok", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "L4_2=yes", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "L4_2=ok", 0) >= 0);
+
+ assert_se(sd_journal_add_disjunction(j) >= 0);
+
+ assert_se(sd_journal_add_match(j, "L3=yes", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "L3=ok", 0) >= 0);
+
+ assert_se(t = journal_make_match_string(j));
+
+ printf("resulting match expression is: %s\n", t);
+
+ assert_se(streq(t, "(((L3=ok OR L3=yes) OR ((L4_2=ok OR L4_2=yes) AND (L4_1=ok OR L4_1=yes))) AND ((TWO=two AND (ONE=two OR ONE=one)) OR (PIFF=paff AND (QUUX=yyyyy OR QUUX=xxxxx OR QUUX=mmmm) AND (HALLO= OR HALLO=WALDO))))"));
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-send.c b/src/grp-journal/libjournal-core/test-journal-send.c
new file mode 100644
index 0000000000..192e12c887
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-send.c
@@ -0,0 +1,102 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "basic/macro.h"
+
+int main(int argc, char *argv[]) {
+ char huge[4096*1024];
+
+ /* utf-8 and non-utf-8, message-less and message-ful iovecs */
+ struct iovec graph1[] = {
+ {(char*) "GRAPH=graph", strlen("GRAPH=graph")}
+ };
+ struct iovec graph2[] = {
+ {(char*) "GRAPH=graph\n", strlen("GRAPH=graph\n")}
+ };
+ struct iovec message1[] = {
+ {(char*) "MESSAGE=graph", strlen("MESSAGE=graph")}
+ };
+ struct iovec message2[] = {
+ {(char*) "MESSAGE=graph\n", strlen("MESSAGE=graph\n")}
+ };
+
+ assert_se(sd_journal_print(LOG_INFO, "piepapo") == 0);
+
+ assert_se(sd_journal_send("MESSAGE=foobar",
+ "VALUE=%i", 7,
+ NULL) == 0);
+
+ errno = ENOENT;
+ assert_se(sd_journal_perror("Foobar") == 0);
+
+ assert_se(sd_journal_perror("") == 0);
+
+ memset(huge, 'x', sizeof(huge));
+ memcpy(huge, "HUGE=", 5);
+ char_array_0(huge);
+
+ assert_se(sd_journal_send("MESSAGE=Huge field attached",
+ huge,
+ NULL) == 0);
+
+ assert_se(sd_journal_send("MESSAGE=uiui",
+ "VALUE=A",
+ "VALUE=B",
+ "VALUE=C",
+ "SINGLETON=1",
+ "OTHERVALUE=X",
+ "OTHERVALUE=Y",
+ "WITH_BINARY=this is a binary value \a",
+ NULL) == 0);
+
+ syslog(LOG_NOTICE, "Hello World!");
+
+ assert_se(sd_journal_print(LOG_NOTICE, "Hello World") == 0);
+
+ assert_se(sd_journal_send("MESSAGE=Hello World!",
+ "MESSAGE_ID=52fb62f99e2c49d89cfbf9d6de5e3555",
+ "PRIORITY=5",
+ "HOME=%s", getenv("HOME"),
+ "TERM=%s", getenv("TERM"),
+ "PAGE_SIZE=%li", sysconf(_SC_PAGESIZE),
+ "N_CPUS=%li", sysconf(_SC_NPROCESSORS_ONLN),
+ NULL) == 0);
+
+ assert_se(sd_journal_sendv(graph1, 1) == 0);
+ assert_se(sd_journal_sendv(graph2, 1) == 0);
+ assert_se(sd_journal_sendv(message1, 1) == 0);
+ assert_se(sd_journal_sendv(message2, 1) == 0);
+
+ /* test without location fields */
+#undef sd_journal_sendv
+ assert_se(sd_journal_sendv(graph1, 1) == 0);
+ assert_se(sd_journal_sendv(graph2, 1) == 0);
+ assert_se(sd_journal_sendv(message1, 1) == 0);
+ assert_se(sd_journal_sendv(message2, 1) == 0);
+
+ sleep(1);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-stream.c b/src/grp-journal/libjournal-core/test-journal-stream.c
new file mode 100644
index 0000000000..b71c44ef56
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-stream.c
@@ -0,0 +1,196 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "basic/alloc-util.h"
+#include "basic/log.h"
+#include "basic/macro.h"
+#include "basic/parse-util.h"
+#include "basic/rm-rf.h"
+#include "basic/util.h"
+#include "sd-journal/journal-file.h"
+#include "sd-journal/journal-internal.h"
+
+#define N_ENTRIES 200
+
+static void verify_contents(sd_journal *j, unsigned skip) {
+ unsigned i;
+
+ assert_se(j);
+
+ i = 0;
+ SD_JOURNAL_FOREACH(j) {
+ const void *d;
+ char *k, *c;
+ size_t l;
+ unsigned u = 0;
+
+ assert_se(sd_journal_get_cursor(j, &k) >= 0);
+ printf("cursor: %s\n", k);
+ free(k);
+
+ assert_se(sd_journal_get_data(j, "MAGIC", &d, &l) >= 0);
+ printf("\t%.*s\n", (int) l, (const char*) d);
+
+ assert_se(sd_journal_get_data(j, "NUMBER", &d, &l) >= 0);
+ assert_se(k = strndup(d, l));
+ printf("\t%s\n", k);
+
+ if (skip > 0) {
+ assert_se(safe_atou(k + 7, &u) >= 0);
+ assert_se(i == u);
+ i += skip;
+ }
+
+ free(k);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ free(c);
+ }
+
+ if (skip > 0)
+ assert_se(i == N_ENTRIES);
+}
+
+int main(int argc, char *argv[]) {
+ JournalFile *one, *two, *three;
+ char t[] = "/tmp/journal-stream-XXXXXX";
+ unsigned i;
+ _cleanup_(sd_journal_closep) sd_journal *j = NULL;
+ char *z;
+ const void *data;
+ size_t l;
+ dual_timestamp previous_ts = DUAL_TIMESTAMP_NULL;
+
+ /* journal_file_open requires a valid machine id */
+ if (access("/etc/machine-id", F_OK) != 0)
+ return EXIT_TEST_SKIP;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &one) == 0);
+ assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &two) == 0);
+ assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &three) == 0);
+
+ for (i = 0; i < N_ENTRIES; i++) {
+ char *p, *q;
+ dual_timestamp ts;
+ struct iovec iovec[2];
+
+ dual_timestamp_get(&ts);
+
+ if (ts.monotonic <= previous_ts.monotonic)
+ ts.monotonic = previous_ts.monotonic + 1;
+
+ if (ts.realtime <= previous_ts.realtime)
+ ts.realtime = previous_ts.realtime + 1;
+
+ previous_ts = ts;
+
+ assert_se(asprintf(&p, "NUMBER=%u", i) >= 0);
+ iovec[0].iov_base = p;
+ iovec[0].iov_len = strlen(p);
+
+ assert_se(asprintf(&q, "MAGIC=%s", i % 5 == 0 ? "quux" : "waldo") >= 0);
+
+ iovec[1].iov_base = q;
+ iovec[1].iov_len = strlen(q);
+
+ if (i % 10 == 0)
+ assert_se(journal_file_append_entry(three, &ts, iovec, 2, NULL, NULL, NULL) == 0);
+ else {
+ if (i % 3 == 0)
+ assert_se(journal_file_append_entry(two, &ts, iovec, 2, NULL, NULL, NULL) == 0);
+
+ assert_se(journal_file_append_entry(one, &ts, iovec, 2, NULL, NULL, NULL) == 0);
+ }
+
+ free(p);
+ free(q);
+ }
+
+ (void) journal_file_close(one);
+ (void) journal_file_close(two);
+ (void) journal_file_close(three);
+
+ assert_se(sd_journal_open_directory(&j, t, 0) >= 0);
+
+ assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0);
+ SD_JOURNAL_FOREACH_BACKWARDS(j) {
+ _cleanup_free_ char *c;
+
+ assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0);
+ printf("\t%.*s\n", (int) l, (const char*) data);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ }
+
+ SD_JOURNAL_FOREACH(j) {
+ _cleanup_free_ char *c;
+
+ assert_se(sd_journal_get_data(j, "NUMBER", &data, &l) >= 0);
+ printf("\t%.*s\n", (int) l, (const char*) data);
+
+ assert_se(sd_journal_get_cursor(j, &c) >= 0);
+ assert_se(sd_journal_test_cursor(j, c) > 0);
+ }
+
+ sd_journal_flush_matches(j);
+
+ verify_contents(j, 1);
+
+ printf("NEXT TEST\n");
+ assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0);
+
+ assert_se(z = journal_make_match_string(j));
+ printf("resulting match expression is: %s\n", z);
+ free(z);
+
+ verify_contents(j, 5);
+
+ printf("NEXT TEST\n");
+ sd_journal_flush_matches(j);
+ assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0);
+ assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0);
+
+ assert_se(z = journal_make_match_string(j));
+ printf("resulting match expression is: %s\n", z);
+ free(z);
+
+ verify_contents(j, 0);
+
+ assert_se(sd_journal_query_unique(j, "NUMBER") >= 0);
+ SD_JOURNAL_FOREACH_UNIQUE(j, data, l)
+ printf("%.*s\n", (int) l, (const char*) data);
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-syslog.c b/src/grp-journal/libjournal-core/test-journal-syslog.c
new file mode 100644
index 0000000000..4756126478
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-syslog.c
@@ -0,0 +1,45 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 "basic/alloc-util.h"
+#include "basic/macro.h"
+#include "basic/string-util.h"
+
+#include "journald-syslog.h"
+
+static void test_syslog_parse_identifier(const char* str,
+ const char *ident, const char*pid, int ret) {
+ const char *buf = str;
+ _cleanup_free_ char *ident2 = NULL, *pid2 = NULL;
+ int ret2;
+
+ ret2 = syslog_parse_identifier(&buf, &ident2, &pid2);
+
+ assert_se(ret == ret2);
+ assert_se(ident == ident2 || streq_ptr(ident, ident2));
+ assert_se(pid == pid2 || streq_ptr(pid, pid2));
+}
+
+int main(void) {
+ test_syslog_parse_identifier("pidu[111]: xxx", "pidu", "111", 11);
+ test_syslog_parse_identifier("pidu: xxx", "pidu", NULL, 6);
+ test_syslog_parse_identifier("pidu xxx", NULL, NULL, 0);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal-verify.c b/src/grp-journal/libjournal-core/test-journal-verify.c
new file mode 100644
index 0000000000..485315c118
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal-verify.c
@@ -0,0 +1,150 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "basic/fd-util.h"
+#include "basic/log.h"
+#include "basic/rm-rf.h"
+#include "basic/terminal-util.h"
+#include "basic/util.h"
+#include "sd-journal/journal-file.h"
+#include "sd-journal/journal-verify.h"
+
+#define N_ENTRIES 6000
+#define RANDOM_RANGE 77
+
+static void bit_toggle(const char *fn, uint64_t p) {
+ uint8_t b;
+ ssize_t r;
+ int fd;
+
+ fd = open(fn, O_RDWR|O_CLOEXEC);
+ assert_se(fd >= 0);
+
+ r = pread(fd, &b, 1, p/8);
+ assert_se(r == 1);
+
+ b ^= 1 << (p % 8);
+
+ r = pwrite(fd, &b, 1, p/8);
+ assert_se(r == 1);
+
+ safe_close(fd);
+}
+
+static int raw_verify(const char *fn, const char *verification_key) {
+ JournalFile *f;
+ int r;
+
+ r = journal_file_open(-1, fn, O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f);
+ if (r < 0)
+ return r;
+
+ r = journal_file_verify(f, verification_key, NULL, NULL, NULL, false);
+ (void) journal_file_close(f);
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ char t[] = "/tmp/journal-XXXXXX";
+ unsigned n;
+ JournalFile *f;
+ const char *verification_key = argv[1];
+ usec_t from = 0, to = 0, total = 0;
+ char a[FORMAT_TIMESTAMP_MAX];
+ char b[FORMAT_TIMESTAMP_MAX];
+ char c[FORMAT_TIMESPAN_MAX];
+ struct stat st;
+ uint64_t p;
+
+ /* journal_file_open requires a valid machine id */
+ if (access("/etc/machine-id", F_OK) != 0)
+ return EXIT_TEST_SKIP;
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ log_info("Generating...");
+
+ assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0);
+
+ for (n = 0; n < N_ENTRIES; n++) {
+ struct iovec iovec;
+ struct dual_timestamp ts;
+ char *test;
+
+ dual_timestamp_get(&ts);
+
+ assert_se(asprintf(&test, "RANDOM=%lu", random() % RANDOM_RANGE));
+
+ iovec.iov_base = (void*) test;
+ iovec.iov_len = strlen(test);
+
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+ free(test);
+ }
+
+ (void) journal_file_close(f);
+
+ log_info("Verifying...");
+
+ assert_se(journal_file_open(-1, "test.journal", O_RDONLY, 0666, true, !!verification_key, NULL, NULL, NULL, NULL, &f) == 0);
+ /* journal_file_print_header(f); */
+ journal_file_dump(f);
+
+ assert_se(journal_file_verify(f, verification_key, &from, &to, &total, true) >= 0);
+
+ if (verification_key && JOURNAL_HEADER_SEALED(f->header))
+ log_info("=> Validated from %s to %s, %s missing",
+ format_timestamp(a, sizeof(a), from),
+ format_timestamp(b, sizeof(b), to),
+ format_timespan(c, sizeof(c), total > to ? total - to : 0, 0));
+
+ (void) journal_file_close(f);
+
+ if (verification_key) {
+ log_info("Toggling bits...");
+
+ assert_se(stat("test.journal", &st) >= 0);
+
+ for (p = 38448*8+0; p < ((uint64_t) st.st_size * 8); p ++) {
+ bit_toggle("test.journal", p);
+
+ log_info("[ %"PRIu64"+%"PRIu64"]", p / 8, p % 8);
+
+ if (raw_verify("test.journal", verification_key) >= 0)
+ log_notice(ANSI_HIGHLIGHT_RED ">>>> %"PRIu64" (bit %"PRIu64") can be toggled without detection." ANSI_NORMAL, p / 8, p % 8);
+
+ bit_toggle("test.journal", p);
+ }
+ }
+
+ log_info("Exiting...");
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-journal.c b/src/grp-journal/libjournal-core/test-journal.c
new file mode 100644
index 0000000000..05de15f0c0
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-journal.c
@@ -0,0 +1,178 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <unistd.h>
+
+#include "basic/log.h"
+#include "basic/rm-rf.h"
+#include "sd-journal/journal-authenticate.h"
+#include "sd-journal/journal-file.h"
+#include "sd-journal/journal-vacuum.h"
+
+static bool arg_keep = false;
+
+static void test_non_empty(void) {
+ dual_timestamp ts;
+ JournalFile *f;
+ struct iovec iovec;
+ static const char test[] = "TEST1=1", test2[] = "TEST2=2";
+ Object *o;
+ uint64_t p;
+ char t[] = "/tmp/journal-XXXXXX";
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f) == 0);
+
+ dual_timestamp_get(&ts);
+
+ iovec.iov_base = (void*) test;
+ iovec.iov_len = strlen(test);
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+ iovec.iov_base = (void*) test2;
+ iovec.iov_len = strlen(test2);
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+ iovec.iov_base = (void*) test;
+ iovec.iov_len = strlen(test);
+ assert_se(journal_file_append_entry(f, &ts, &iovec, 1, NULL, NULL, NULL) == 0);
+
+#ifdef HAVE_GCRYPT
+ journal_file_append_tag(f);
+#endif
+ journal_file_dump(f);
+
+ assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 1);
+
+ assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 2);
+
+ assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 3);
+
+ assert_se(journal_file_next_entry(f, p, DIRECTION_DOWN, &o, &p) == 0);
+
+ assert_se(journal_file_next_entry(f, 0, DIRECTION_DOWN, &o, &p) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 1);
+
+ assert_se(journal_file_find_data_object(f, test, strlen(test), NULL, &p) == 1);
+ assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 1);
+
+ assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 3);
+
+ assert_se(journal_file_find_data_object(f, test2, strlen(test2), NULL, &p) == 1);
+ assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_UP, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 2);
+
+ assert_se(journal_file_next_entry_for_data(f, NULL, 0, p, DIRECTION_DOWN, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 2);
+
+ assert_se(journal_file_find_data_object(f, "quux", 4, NULL, &p) == 0);
+
+ assert_se(journal_file_move_to_entry_by_seqnum(f, 1, DIRECTION_DOWN, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 1);
+
+ assert_se(journal_file_move_to_entry_by_seqnum(f, 3, DIRECTION_DOWN, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 3);
+
+ assert_se(journal_file_move_to_entry_by_seqnum(f, 2, DIRECTION_DOWN, &o, NULL) == 1);
+ assert_se(le64toh(o->entry.seqnum) == 2);
+
+ assert_se(journal_file_move_to_entry_by_seqnum(f, 10, DIRECTION_DOWN, &o, NULL) == 0);
+
+ journal_file_rotate(&f, true, true, NULL);
+ journal_file_rotate(&f, true, true, NULL);
+
+ (void) journal_file_close(f);
+
+ log_info("Done...");
+
+ if (arg_keep)
+ log_info("Not removing %s", t);
+ else {
+ journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+ }
+
+ puts("------------------------------------------------------------");
+}
+
+static void test_empty(void) {
+ JournalFile *f1, *f2, *f3, *f4;
+ char t[] = "/tmp/journal-XXXXXX";
+
+ log_set_max_level(LOG_DEBUG);
+
+ assert_se(mkdtemp(t));
+ assert_se(chdir(t) >= 0);
+
+ assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0666, false, false, NULL, NULL, NULL, NULL, &f1) == 0);
+
+ assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, 0666, true, false, NULL, NULL, NULL, NULL, &f2) == 0);
+
+ assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, 0666, false, true, NULL, NULL, NULL, NULL, &f3) == 0);
+
+ assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, 0666, true, true, NULL, NULL, NULL, NULL, &f4) == 0);
+
+ journal_file_print_header(f1);
+ puts("");
+ journal_file_print_header(f2);
+ puts("");
+ journal_file_print_header(f3);
+ puts("");
+ journal_file_print_header(f4);
+ puts("");
+
+ log_info("Done...");
+
+ if (arg_keep)
+ log_info("Not removing %s", t);
+ else {
+ journal_directory_vacuum(".", 3000000, 0, 0, NULL, true);
+
+ assert_se(rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
+ }
+
+ (void) journal_file_close(f1);
+ (void) journal_file_close(f2);
+ (void) journal_file_close(f3);
+ (void) journal_file_close(f4);
+}
+
+int main(int argc, char *argv[]) {
+ arg_keep = argc > 1;
+
+ /* journal_file_open requires a valid machine id */
+ if (access("/etc/machine-id", F_OK) != 0)
+ return EXIT_TEST_SKIP;
+
+ test_non_empty();
+ test_empty();
+
+ return 0;
+}
diff --git a/src/grp-journal/libjournal-core/test-mmap-cache.c b/src/grp-journal/libjournal-core/test-mmap-cache.c
new file mode 100644
index 0000000000..3eb5444480
--- /dev/null
+++ b/src/grp-journal/libjournal-core/test-mmap-cache.c
@@ -0,0 +1,79 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <fcntl.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "basic/fd-util.h"
+#include "basic/fileio.h"
+#include "basic/macro.h"
+#include "basic/util.h"
+#include "sd-journal/mmap-cache.h"
+
+int main(int argc, char *argv[]) {
+ int x, y, z, r;
+ char px[] = "/tmp/testmmapXXXXXXX", py[] = "/tmp/testmmapYXXXXXX", pz[] = "/tmp/testmmapZXXXXXX";
+ MMapCache *m;
+ void *p, *q;
+
+ assert_se(m = mmap_cache_new());
+
+ x = mkostemp_safe(px, O_RDWR|O_CLOEXEC);
+ assert_se(x >= 0);
+ unlink(px);
+
+ y = mkostemp_safe(py, O_RDWR|O_CLOEXEC);
+ assert_se(y >= 0);
+ unlink(py);
+
+ z = mkostemp_safe(pz, O_RDWR|O_CLOEXEC);
+ assert_se(z >= 0);
+ unlink(pz);
+
+ r = mmap_cache_get(m, x, PROT_READ, 0, false, 1, 2, NULL, &p);
+ assert_se(r >= 0);
+
+ r = mmap_cache_get(m, x, PROT_READ, 0, false, 2, 2, NULL, &q);
+ assert_se(r >= 0);
+
+ assert_se((uint8_t*) p + 1 == (uint8_t*) q);
+
+ r = mmap_cache_get(m, x, PROT_READ, 1, false, 3, 2, NULL, &q);
+ assert_se(r >= 0);
+
+ assert_se((uint8_t*) p + 2 == (uint8_t*) q);
+
+ r = mmap_cache_get(m, x, PROT_READ, 0, false, 16ULL*1024ULL*1024ULL, 2, NULL, &p);
+ assert_se(r >= 0);
+
+ r = mmap_cache_get(m, x, PROT_READ, 1, false, 16ULL*1024ULL*1024ULL+1, 2, NULL, &q);
+ assert_se(r >= 0);
+
+ assert_se((uint8_t*) p + 1 == (uint8_t*) q);
+
+ mmap_cache_unref(m);
+
+ safe_close(x);
+ safe_close(y);
+ safe_close(z);
+
+ return 0;
+}
diff --git a/src/grp-journal/systemd-cat/Makefile b/src/grp-journal/systemd-cat/Makefile
new file mode 100644
index 0000000000..77c0861f2c
--- /dev/null
+++ b/src/grp-journal/systemd-cat/Makefile
@@ -0,0 +1,35 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+systemd_cat_SOURCES = \
+ src/journal/cat.c
+
+systemd_cat_LDADD = \
+ libjournal-core.la
+
+bin_PROGRAMS += \
+ systemd-cat
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/systemd-cat/cat.c b/src/grp-journal/systemd-cat/cat.c
new file mode 100644
index 0000000000..8ab4febd53
--- /dev/null
+++ b/src/grp-journal/systemd-cat/cat.c
@@ -0,0 +1,161 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ 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 <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <systemd/sd-journal.h>
+
+#include "basic/fd-util.h"
+#include "basic/parse-util.h"
+#include "basic/string-util.h"
+#include "basic/syslog-util.h"
+#include "basic/util.h"
+
+static const char *arg_identifier = NULL;
+static int arg_priority = LOG_INFO;
+static bool arg_level_prefix = true;
+
+static void help(void) {
+ printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+ "Execute process with stdout/stderr connected to the journal.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -t --identifier=STRING Set syslog identifier\n"
+ " -p --priority=PRIORITY Set priority value (0..7)\n"
+ " --level-prefix=BOOL Control whether level prefix shall be parsed\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_LEVEL_PREFIX
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "identifier", required_argument, NULL, 't' },
+ { "priority", required_argument, NULL, 'p' },
+ { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case 't':
+ if (isempty(optarg))
+ arg_identifier = NULL;
+ else
+ arg_identifier = optarg;
+ break;
+
+ case 'p':
+ arg_priority = log_level_from_string(optarg);
+ if (arg_priority < 0) {
+ log_error("Failed to parse priority value.");
+ return -EINVAL;
+ }
+ break;
+
+ case ARG_LEVEL_PREFIX: {
+ int k;
+
+ k = parse_boolean(optarg);
+ if (k < 0)
+ return log_error_errno(k, "Failed to parse level prefix value.");
+
+ arg_level_prefix = k;
+ break;
+ }
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_close_ int fd = -1, saved_stderr = -1;
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ fd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix);
+ if (fd < 0) {
+ r = log_error_errno(fd, "Failed to create stream fd: %m");
+ goto finish;
+ }
+
+ saved_stderr = fcntl(STDERR_FILENO, F_DUPFD_CLOEXEC, 3);
+
+ if (dup3(fd, STDOUT_FILENO, 0) < 0 ||
+ dup3(fd, STDERR_FILENO, 0) < 0) {
+ r = log_error_errno(errno, "Failed to duplicate fd: %m");
+ goto finish;
+ }
+
+ if (fd >= 3)
+ safe_close(fd);
+ fd = -1;
+
+ if (argc <= optind)
+ (void) execl("/bin/cat", "/bin/cat", NULL);
+ else
+ (void) execvp(argv[optind], argv + optind);
+ r = -errno;
+
+ /* Let's try to restore a working stderr, so we can print the error message */
+ if (saved_stderr >= 0)
+ (void) dup3(saved_stderr, STDERR_FILENO, 0);
+
+ log_error_errno(r, "Failed to execute process: %m");
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-journal/systemd-cat/systemd-cat.completion.bash b/src/grp-journal/systemd-cat/systemd-cat.completion.bash
new file mode 100644
index 0000000000..8d84042af1
--- /dev/null
+++ b/src/grp-journal/systemd-cat/systemd-cat.completion.bash
@@ -0,0 +1,57 @@
+# systemd-cat(1) completion -*- shell-script -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2014 Thomas H.P. Andersen
+#
+# 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
+# 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/>.
+
+__contains_word() {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+_systemd_cat() {
+ local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+ local i verb comps
+
+ local -A OPTS=(
+ [STANDALONE]='-h --help --version'
+ [ARG]='-t --identifier -p --priority --level-prefix'
+ )
+
+ _init_completion || return
+
+ if __contains_word "$prev" ${OPTS[ARG]}; then
+ case $prev in
+ --identifier|-t)
+ comps=''
+ ;;
+ --priority|-p)
+ comps='emerg alert crit err warning notice info debug'
+ ;;
+ --level-prefix)
+ comps='yes no'
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+}
+
+complete -F _systemd_cat systemd-cat
diff --git a/src/grp-journal/systemd-cat/systemd-cat.completion.zsh b/src/grp-journal/systemd-cat/systemd-cat.completion.zsh
new file mode 100644
index 0000000000..7487b00ee8
--- /dev/null
+++ b/src/grp-journal/systemd-cat/systemd-cat.completion.zsh
@@ -0,0 +1,12 @@
+#compdef systemd-cat
+
+local curcontext="$curcontext" state lstate line
+_arguments \
+ {-h,--help}'[Show this help]' \
+ '--version[Show package version.]' \
+ {-t+,--identifier=}'[Set syslog identifier.]:syslog identifier:' \
+ {-p+,--priority=}'[Set priority value.]:value:({0..7})' \
+ '--level-prefix=[Control whether level prefix shall be parsed.]:boolean:(1 0)' \
+ ':Message'
+
+#vim: set ft=zsh sw=4 ts=4 et
diff --git a/src/grp-journal/systemd-cat/systemd-cat.xml b/src/grp-journal/systemd-cat/systemd-cat.xml
new file mode 100644
index 0000000000..160db9fb5c
--- /dev/null
+++ b/src/grp-journal/systemd-cat/systemd-cat.xml
@@ -0,0 +1,178 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!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 2012 Lennart Poettering
+
+ 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-cat"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-cat</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart@poettering.net</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-cat</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-cat</refname>
+ <refpurpose>Connect a pipeline or program's output with the journal</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-cat <arg choice="opt" rep="repeat">OPTIONS</arg> <arg>COMMAND</arg> <arg choice="opt" rep="repeat">ARGUMENTS</arg></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-cat <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-cat</command> may be used to connect the
+ standard input and output of a process to the journal, or as a
+ filter tool in a shell pipeline to pass the output the previous
+ pipeline element generates to the journal.</para>
+
+ <para>If no parameter is passed, <command>systemd-cat</command>
+ will write everything it reads from standard input (stdin) to the
+ journal.</para>
+
+ <para>If parameters are passed, they are executed as command line
+ with standard output (stdout) and standard error output (stderr)
+ connected to the journal, so that all it writes is stored in the
+ journal.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--identifier=</option></term>
+
+ <listitem><para>Specify a short string that is used to
+ identify the logging tool. If not specified, no identification
+ string is written to the journal.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--priority=</option></term>
+
+ <listitem><para>Specify the default priority level for the
+ logged messages. Pass one of
+ <literal>emerg</literal>,
+ <literal>alert</literal>,
+ <literal>crit</literal>,
+ <literal>err</literal>,
+ <literal>warning</literal>,
+ <literal>notice</literal>,
+ <literal>info</literal>,
+ <literal>debug</literal>, or a
+ value between 0 and 7 (corresponding to the same named
+ levels). These priority values are the same as defined by
+ <citerefentry project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+ Defaults to <literal>info</literal>. Note that this simply
+ controls the default, individual lines may be logged with
+ different levels if they are prefixed accordingly. For details,
+ see <option>--level-prefix=</option> below.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--level-prefix=</option></term>
+
+ <listitem><para>Controls whether lines read are parsed for
+ syslog priority level prefixes. If enabled (the default), a
+ line prefixed with a priority prefix such as
+ <literal>&lt;5&gt;</literal> is logged at priority 5
+ (<literal>notice</literal>), and similar for the other
+ priority levels. Takes a boolean argument.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+
+ <example>
+ <title>Invoke a program</title>
+
+ <para>This calls <filename noindex='true'>/bin/ls</filename>
+ with standard output and error connected to the journal:</para>
+
+ <programlisting># systemd-cat ls</programlisting>
+ </example>
+
+ <example>
+ <title>Usage in a shell pipeline</title>
+
+ <para>This builds a shell pipeline also invoking
+ <filename>/bin/ls</filename> and writes the output it generates
+ to the journal:</para>
+
+ <programlisting># ls | systemd-cat</programlisting>
+ </example>
+
+ <para>Even though the two examples have very similar effects the
+ first is preferable since only one process is running at a time,
+ and both stdout and stderr are captured while in the second
+ example, only stdout is captured.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry project='man-pages'><refentrytitle>logger</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-journal/systemd-journald/Makefile b/src/grp-journal/systemd-journald/Makefile
new file mode 100644
index 0000000000..b71b2dc494
--- /dev/null
+++ b/src/grp-journal/systemd-journald/Makefile
@@ -0,0 +1,75 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+systemd_journald_SOURCES = \
+ src/journal/journald.c \
+ src/journal/journald-server.h
+
+systemd_journald_LDADD = \
+ libjournal-core.la \
+ libshared.la
+
+rootlibexec_PROGRAMS += \
+ systemd-journald
+
+dist_systemunit_DATA += \
+ units/systemd-journald.socket \
+ units/systemd-journald-dev-log.socket \
+ units/systemd-journald-audit.socket
+
+nodist_systemunit_DATA += \
+ units/systemd-journald.service
+
+dist_pkgsysconf_DATA += \
+ src/journal/journald.conf
+
+dist_catalog_DATA = \
+ catalog/systemd.bg.catalog \
+ catalog/systemd.be.catalog \
+ catalog/systemd.be@latin.catalog \
+ catalog/systemd.fr.catalog \
+ catalog/systemd.it.catalog \
+ catalog/systemd.pl.catalog \
+ catalog/systemd.pt_BR.catalog \
+ catalog/systemd.ru.catalog \
+ catalog/systemd.zh_CN.catalog \
+ catalog/systemd.zh_TW.catalog \
+ catalog/systemd.catalog
+
+SOCKETS_TARGET_WANTS += \
+ systemd-journald.socket \
+ systemd-journald-dev-log.socket \
+ systemd-journald-audit.socket
+
+SYSINIT_TARGET_WANTS += \
+ systemd-journald.service
+
+EXTRA_DIST += \
+ units/systemd-journald.service.in
+
+gperf_gperf_sources += \
+ src/journal/journald-gperf.gperf
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/systemd-journald/journald.c b/src/grp-journal/systemd-journald/journald.c
new file mode 100644
index 0000000000..cd887feed8
--- /dev/null
+++ b/src/grp-journal/systemd-journald/journald.c
@@ -0,0 +1,120 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2011 Lennart Poettering
+
+ 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 <systemd/sd-daemon.h>
+#include <systemd/sd-messages.h>
+
+#include "basic/formats-util.h"
+#include "basic/sigbus.h"
+#include "journald-kmsg.h"
+#include "journald-server.h"
+#include "journald-syslog.h"
+#include "sd-journal/journal-authenticate.h"
+
+int main(int argc, char *argv[]) {
+ Server server;
+ int r;
+
+ if (argc > 1) {
+ log_error("This program does not take arguments.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_SAFE);
+ log_set_facility(LOG_SYSLOG);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ sigbus_install();
+
+ r = server_init(&server);
+ if (r < 0)
+ goto finish;
+
+ server_vacuum(&server, false, false);
+ server_flush_to_var(&server);
+ server_flush_dev_kmsg(&server);
+
+ log_debug("systemd-journald running as pid "PID_FMT, getpid());
+ server_driver_message(&server, SD_MESSAGE_JOURNAL_START,
+ LOG_MESSAGE("Journal started"),
+ NULL);
+
+ for (;;) {
+ usec_t t = USEC_INFINITY, n;
+
+ r = sd_event_get_state(server.event);
+ if (r < 0)
+ goto finish;
+ if (r == SD_EVENT_FINISHED)
+ break;
+
+ n = now(CLOCK_REALTIME);
+
+ if (server.max_retention_usec > 0 && server.oldest_file_usec > 0) {
+
+ /* The retention time is reached, so let's vacuum! */
+ if (server.oldest_file_usec + server.max_retention_usec < n) {
+ log_info("Retention time reached.");
+ server_rotate(&server);
+ server_vacuum(&server, false, false);
+ continue;
+ }
+
+ /* Calculate when to rotate the next time */
+ t = server.oldest_file_usec + server.max_retention_usec - n;
+ }
+
+#ifdef HAVE_GCRYPT
+ if (server.system_journal) {
+ usec_t u;
+
+ if (journal_file_next_evolve_usec(server.system_journal, &u)) {
+ if (n >= u)
+ t = 0;
+ else
+ t = MIN(t, u - n);
+ }
+ }
+#endif
+
+ r = sd_event_run(server.event, t);
+ if (r < 0) {
+ log_error_errno(r, "Failed to run event loop: %m");
+ goto finish;
+ }
+
+ server_maybe_append_tags(&server);
+ server_maybe_warn_forward_syslog_missed(&server);
+ }
+
+ log_debug("systemd-journald stopped as pid "PID_FMT, getpid());
+ server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP,
+ LOG_MESSAGE("Journal stopped"),
+ NULL);
+
+finish:
+ server_done(&server);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-journal/systemd-journald/journald.conf b/src/grp-journal/systemd-journald/journald.conf
new file mode 100644
index 0000000000..2541b949be
--- /dev/null
+++ b/src/grp-journal/systemd-journald/journald.conf
@@ -0,0 +1,41 @@
+# 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.
+#
+# Entries in this file show the compile time defaults.
+# You can change settings by editing this file.
+# Defaults can be restored by simply deleting this file.
+#
+# See journald.conf(5) for details.
+
+[Journal]
+#Storage=auto
+#Compress=yes
+#Seal=yes
+#SplitMode=uid
+#SyncIntervalSec=5m
+#RateLimitIntervalSec=30s
+#RateLimitBurst=1000
+#SystemMaxUse=
+#SystemKeepFree=
+#SystemMaxFileSize=
+#SystemMaxFiles=100
+#RuntimeMaxUse=
+#RuntimeKeepFree=
+#RuntimeMaxFileSize=
+#RuntimeMaxFiles=100
+#MaxRetentionSec=
+#MaxFileSec=1month
+#ForwardToSyslog=no
+#ForwardToKMsg=no
+#ForwardToConsole=no
+#ForwardToWall=yes
+#TTYPath=/dev/console
+#MaxLevelStore=debug
+#MaxLevelSyslog=debug
+#MaxLevelKMsg=notice
+#MaxLevelConsole=info
+#MaxLevelWall=emerg
diff --git a/src/grp-journal/systemd-journald/journald.conf.xml b/src/grp-journal/systemd-journald/journald.conf.xml
new file mode 100644
index 0000000000..3964cd6bc5
--- /dev/null
+++ b/src/grp-journal/systemd-journald/journald.conf.xml
@@ -0,0 +1,410 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!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 2010 Lennart Poettering
+
+ 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="journald.conf"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>journald.conf</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart@poettering.net</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>journald.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>journald.conf</refname>
+ <refname>journald.conf.d</refname>
+ <refpurpose>Journal service configuration files</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/etc/systemd/journald.conf</filename></para>
+ <para><filename>/etc/systemd/journald.conf.d/*.conf</filename></para>
+ <para><filename>/run/systemd/journald.conf.d/*.conf</filename></para>
+ <para><filename>/usr/lib/systemd/journald.conf.d/*.conf</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>These files configure various parameters of the systemd
+ journal service,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+
+ </refsect1>
+
+ <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>All options are configured in the
+ <literal>[Journal]</literal> section:</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><varname>Storage=</varname></term>
+
+ <listitem><para>Controls where to store journal data. One of
+ <literal>volatile</literal>,
+ <literal>persistent</literal>,
+ <literal>auto</literal> and
+ <literal>none</literal>. If
+ <literal>volatile</literal>, journal
+ log data will be stored only in memory, i.e. below the
+ <filename>/run/log/journal</filename> hierarchy (which is
+ created if needed). If <literal>persistent</literal>, data
+ will be stored preferably on disk, i.e. below the
+ <filename>/var/log/journal</filename> hierarchy (which is
+ created if needed), with a fallback to
+ <filename>/run/log/journal</filename> (which is created if
+ needed), during early boot and if the disk is not writable.
+ <literal>auto</literal> is similar to
+ <literal>persistent</literal> but the directory
+ <filename>/var/log/journal</filename> is not created if
+ needed, so that its existence controls where log data goes.
+ <literal>none</literal> turns off all storage, all log data
+ received will be dropped. Forwarding to other targets, such as
+ the console, the kernel log buffer, or a syslog socket will
+ still work however. Defaults to
+ <literal>auto</literal>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>Compress=</varname></term>
+
+ <listitem><para>Takes a boolean value. If enabled (the
+ default), data objects that shall be stored in the journal and
+ are larger than a certain threshold are compressed before they
+ are written to the file system.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>Seal=</varname></term>
+
+ <listitem><para>Takes a boolean value. If enabled (the
+ default), and a sealing key is available (as created by
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
+ <option>--setup-keys</option> command), Forward Secure Sealing
+ (FSS) for all persistent journal files is enabled. FSS is
+ based on <ulink
+ url="https://eprint.iacr.org/2013/397">Seekable Sequential Key
+ Generators</ulink> by G. A. Marson and B. Poettering
+ (doi:10.1007/978-3-642-40203-6_7) and may be used to protect
+ journal files from unnoticed alteration.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>SplitMode=</varname></term>
+
+ <listitem><para>Controls whether to split up journal files per
+ user. One of <literal>uid</literal>, <literal>login</literal>
+ and <literal>none</literal>. If <literal>uid</literal>, all
+ users will get each their own journal files regardless of
+ whether they possess a login session or not, however system
+ users will log into the system journal. If
+ <literal>login</literal>, actually logged-in users will get
+ each their own journal files, but users without login session
+ and system users will log into the system journal. If
+ <literal>none</literal>, journal files are not split up by
+ user and all messages are instead stored in the single system
+ journal. Note that splitting up journal files by user is only
+ available for journals stored persistently. If journals are
+ stored on volatile storage (see above), only a single journal
+ file for all user IDs is kept. Defaults to
+ <literal>uid</literal>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>RateLimitIntervalSec=</varname></term>
+ <term><varname>RateLimitBurst=</varname></term>
+
+ <listitem><para>Configures the rate limiting that is applied
+ to all messages generated on the system. If, in the time
+ interval defined by <varname>RateLimitIntervalSec=</varname>,
+ more messages than specified in
+ <varname>RateLimitBurst=</varname> are logged by a service,
+ all further messages within the interval are dropped until the
+ interval is over. A message about the number of dropped
+ messages is generated. This rate limiting is applied
+ per-service, so that two services which log do not interfere
+ with each other's limits. Defaults to 1000 messages in 30s.
+ The time specification for
+ <varname>RateLimitIntervalSec=</varname> may be specified in the
+ following units: <literal>s</literal>, <literal>min</literal>,
+ <literal>h</literal>, <literal>ms</literal>,
+ <literal>us</literal>. To turn off any kind of rate limiting,
+ set either value to 0.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>SystemMaxUse=</varname></term>
+ <term><varname>SystemKeepFree=</varname></term>
+ <term><varname>SystemMaxFileSize=</varname></term>
+ <term><varname>SystemMaxFiles=</varname></term>
+ <term><varname>RuntimeMaxUse=</varname></term>
+ <term><varname>RuntimeKeepFree=</varname></term>
+ <term><varname>RuntimeMaxFileSize=</varname></term>
+ <term><varname>RuntimeMaxFiles=</varname></term>
+
+ <listitem><para>Enforce size limits on the journal files
+ stored. The options prefixed with <literal>System</literal>
+ apply to the journal files when stored on a persistent file
+ system, more specifically
+ <filename>/var/log/journal</filename>. The options prefixed
+ with <literal>Runtime</literal> apply to the journal files
+ when stored on a volatile in-memory file system, more
+ specifically <filename>/run/log/journal</filename>. The former
+ is used only when <filename>/var</filename> is mounted,
+ writable, and the directory
+ <filename>/var/log/journal</filename> exists. Otherwise, only
+ the latter applies. Note that this means that during early
+ boot and if the administrator disabled persistent logging,
+ only the latter options apply, while the former apply if
+ persistent logging is enabled and the system is fully booted
+ up. <command>journalctl</command> and
+ <command>systemd-journald</command> ignore all files with
+ names not ending with <literal>.journal</literal> or
+ <literal>.journal~</literal>, so only such files, located in
+ the appropriate directories, are taken into account when
+ calculating current disk usage.</para>
+
+ <para><varname>SystemMaxUse=</varname> and
+ <varname>RuntimeMaxUse=</varname> control how much disk space
+ the journal may use up at most.
+ <varname>SystemKeepFree=</varname> and
+ <varname>RuntimeKeepFree=</varname> control how much disk
+ space systemd-journald shall leave free for other uses.
+ <command>systemd-journald</command> will respect both limits
+ and use the smaller of the two values.</para>
+
+ <para>The first pair defaults to 10% and the second to 15% of
+ the size of the respective file system, but each value is
+ capped to 4G. If the file system is nearly full and either
+ <varname>SystemKeepFree=</varname> or
+ <varname>RuntimeKeepFree=</varname> are violated when
+ systemd-journald is started, the limit will be raised to the
+ percentage that is actually free. This means that if there was
+ enough free space before and journal files were created, and
+ subsequently something else causes the file system to fill up,
+ journald will stop using more space, but it will not be
+ removing existing files to reduce the footprint again,
+ either.</para>
+
+ <para><varname>SystemMaxFileSize=</varname> and
+ <varname>RuntimeMaxFileSize=</varname> control how large
+ individual journal files may grow at most. This influences
+ the granularity in which disk space is made available through
+ rotation, i.e. deletion of historic data. Defaults to one
+ eighth of the values configured with
+ <varname>SystemMaxUse=</varname> and
+ <varname>RuntimeMaxUse=</varname>, so that usually seven
+ rotated journal files are kept as history.</para>
+
+ <para>Specify values in bytes or use K, M, G, T, P, E as
+ units for the specified sizes (equal to 1024, 1024², ... bytes).
+ Note that size limits are enforced synchronously when journal
+ files are extended, and no explicit rotation step triggered by
+ time is needed.</para>
+
+ <para><varname>SystemMaxFiles=</varname> and
+ <varname>RuntimeMaxFiles=</varname> control how many
+ individual journal files to keep at most. Note that only
+ archived files are deleted to reduce the number of files until
+ this limit is reached; active files will stay around. This
+ means that, in effect, there might still be more journal files
+ around in total than this limit after a vacuuming operation is
+ complete. This setting defaults to 100.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>MaxFileSec=</varname></term>
+
+ <listitem><para>The maximum time to store entries in a single
+ journal file before rotating to the next one. Normally,
+ time-based rotation should not be required as size-based
+ rotation with options such as
+ <varname>SystemMaxFileSize=</varname> should be sufficient to
+ ensure that journal files do not grow without bounds. However,
+ to ensure that not too much data is lost at once when old
+ journal files are deleted, it might make sense to change this
+ value from the default of one month. Set to 0 to turn off this
+ feature. This setting takes time values which may be suffixed
+ with the units <literal>year</literal>,
+ <literal>month</literal>, <literal>week</literal>,
+ <literal>day</literal>, <literal>h</literal> or
+ <literal>m</literal> to override the default time unit of
+ seconds.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>MaxRetentionSec=</varname></term>
+
+ <listitem><para>The maximum time to store journal entries.
+ This controls whether journal files containing entries older
+ then the specified time span are deleted. Normally, time-based
+ deletion of old journal files should not be required as
+ size-based deletion with options such as
+ <varname>SystemMaxUse=</varname> should be sufficient to
+ ensure that journal files do not grow without bounds. However,
+ to enforce data retention policies, it might make sense to
+ change this value from the default of 0 (which turns off this
+ feature). This setting also takes time values which may be
+ suffixed with the units <literal>year</literal>,
+ <literal>month</literal>, <literal>week</literal>,
+ <literal>day</literal>, <literal>h</literal> or <literal>
+ m</literal> to override the default time unit of
+ seconds.</para></listitem>
+ </varlistentry>
+
+
+ <varlistentry>
+ <term><varname>SyncIntervalSec=</varname></term>
+
+ <listitem><para>The timeout before synchronizing journal files
+ to disk. After syncing, journal files are placed in the
+ OFFLINE state. Note that syncing is unconditionally done
+ immediately after a log message of priority CRIT, ALERT or
+ EMERG has been logged. This setting hence applies only to
+ messages of the levels ERR, WARNING, NOTICE, INFO, DEBUG. The
+ default timeout is 5 minutes. </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>ForwardToSyslog=</varname></term>
+ <term><varname>ForwardToKMsg=</varname></term>
+ <term><varname>ForwardToConsole=</varname></term>
+ <term><varname>ForwardToWall=</varname></term>
+
+ <listitem><para>Control whether log messages received by the
+ journal daemon shall be forwarded to a traditional syslog
+ daemon, to the kernel log buffer (kmsg), to the system
+ console, or sent as wall messages to all logged-in users.
+ These options take boolean arguments. If forwarding to syslog
+ is enabled but nothing reads messages from the socket,
+ forwarding to syslog has no effect. By default, only
+ forwarding to wall is enabled. These settings may be
+ overridden at boot time with the kernel command line options
+ <literal>systemd.journald.forward_to_syslog=</literal>,
+ <literal>systemd.journald.forward_to_kmsg=</literal>,
+ <literal>systemd.journald.forward_to_console=</literal>, and
+ <literal>systemd.journald.forward_to_wall=</literal>. When
+ forwarding to the console, the TTY to log to can be changed
+ with <varname>TTYPath=</varname>, described
+ below.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>MaxLevelStore=</varname></term>
+ <term><varname>MaxLevelSyslog=</varname></term>
+ <term><varname>MaxLevelKMsg=</varname></term>
+ <term><varname>MaxLevelConsole=</varname></term>
+ <term><varname>MaxLevelWall=</varname></term>
+
+ <listitem><para>Controls the maximum log level of messages
+ that are stored on disk, forwarded to syslog, kmsg, the
+ console or wall (if that is enabled, see above). As argument,
+ takes one of
+ <literal>emerg</literal>,
+ <literal>alert</literal>,
+ <literal>crit</literal>,
+ <literal>err</literal>,
+ <literal>warning</literal>,
+ <literal>notice</literal>,
+ <literal>info</literal>,
+ <literal>debug</literal>,
+ or integer values in the range of 0–7 (corresponding to the
+ same levels). Messages equal or below the log level specified
+ are stored/forwarded, messages above are dropped. Defaults to
+ <literal>debug</literal> for <varname>MaxLevelStore=</varname>
+ and <varname>MaxLevelSyslog=</varname>, to ensure that the all
+ messages are written to disk and forwarded to syslog. Defaults
+ to
+ <literal>notice</literal> for <varname>MaxLevelKMsg=</varname>,
+ <literal>info</literal> for <varname>MaxLevelConsole=</varname>,
+ and <literal>emerg</literal> for
+ <varname>MaxLevelWall=</varname>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>TTYPath=</varname></term>
+
+ <listitem><para>Change the console TTY to use if
+ <varname>ForwardToConsole=yes</varname> is used. Defaults to
+ <filename>/dev/console</filename>.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Forwarding to traditional syslog daemons</title>
+
+ <para>
+ Journal events can be transferred to a different logging daemon
+ in two different ways. With the first method, messages are
+ immediately forwarded to a socket
+ (<filename>/run/systemd/journal/syslog</filename>), where the
+ traditional syslog daemon can read them. This method is
+ controlled by the <varname>ForwardToSyslog=</varname> option. With a
+ second method, a syslog daemon behaves like a normal journal
+ client, and reads messages from the journal files, similarly to
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+ With this, messages do not have to be read immediately,
+ which allows a logging daemon which is only started late in boot
+ to access all messages since the start of the system. In
+ addition, full structured meta-data is available to it. This
+ method of course is available only if the messages are stored in
+ a journal file at all. So it will not work if
+ <varname>Storage=none</varname> is set. It should be noted that
+ usually the <emphasis>second</emphasis> method is used by syslog
+ daemons, so the <varname>Storage=</varname> option, and not the
+ <varname>ForwardToSyslog=</varname> option, is relevant for them.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-journal/systemd-journald/systemd-journald-audit.socket b/src/grp-journal/systemd-journald/systemd-journald-audit.socket
new file mode 100644
index 0000000000..541f2cf38d
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald-audit.socket
@@ -0,0 +1,20 @@
+# 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 Audit Socket
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Before=sockets.target
+ConditionSecurity=audit
+ConditionCapability=CAP_AUDIT_READ
+
+[Socket]
+Service=systemd-journald.service
+ReceiveBuffer=128M
+ListenNetlink=audit 1
+PassCredentials=yes
diff --git a/src/grp-journal/systemd-journald/systemd-journald-dev-log.socket b/src/grp-journal/systemd-journald/systemd-journald-dev-log.socket
new file mode 100644
index 0000000000..ffd44bb507
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald-dev-log.socket
@@ -0,0 +1,32 @@
+# 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 Socket (/dev/log)
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Before=sockets.target
+
+# Mount and swap units need this. If this socket unit is removed by an
+# isolate request the mount and swap units would be removed too,
+# hence let's exclude this from isolate requests.
+IgnoreOnIsolate=yes
+
+[Socket]
+Service=systemd-journald.service
+ListenDatagram=/run/systemd/journal/dev-log
+Symlinks=/dev/log
+SocketMode=0666
+PassCredentials=yes
+PassSecurity=yes
+
+# Increase both the send and receive buffer, so that things don't
+# block early. Note that journald internally uses the this socket both
+# for receiving syslog messages, and for forwarding them to any other
+# syslog, hence we bump both values.
+ReceiveBuffer=8M
+SendBuffer=8M
diff --git a/src/grp-journal/systemd-journald/systemd-journald.service.in b/src/grp-journal/systemd-journald/systemd-journald.service.in
new file mode 100644
index 0000000000..41bfde5be3
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald.service.in
@@ -0,0 +1,32 @@
+# 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 Service
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Requires=systemd-journald.socket
+After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
+Before=sysinit.target
+
+[Service]
+Type=notify
+Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
+ExecStart=@rootlibexecdir@/systemd-journald
+Restart=always
+RestartSec=0
+NotifyAccess=all
+StandardOutput=null
+CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
+WatchdogSec=3min
+FileDescriptorStoreMax=1024
+
+# Increase the default a bit in order to allow many simultaneous
+# services being run since we keep one fd open per service. Also, when
+# flushing journal files to disk, we might need a lot of fds when many
+# journal files are combined.
+LimitNOFILE=16384
diff --git a/src/grp-journal/systemd-journald/systemd-journald.service.xml b/src/grp-journal/systemd-journald/systemd-journald.service.xml
new file mode 100644
index 0000000000..2810638bc2
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald.service.xml
@@ -0,0 +1,276 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!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 2010 Lennart Poettering
+
+ 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-journald.service">
+
+ <refentryinfo>
+ <title>systemd-journald.service</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart@poettering.net</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journald.service</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journald.service</refname>
+ <refname>systemd-journald.socket</refname>
+ <refname>systemd-journald-dev-log.socket</refname>
+ <refname>systemd-journald-audit.socket</refname>
+ <refname>systemd-journald</refname>
+ <refpurpose>Journal service</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>systemd-journald.service</filename></para>
+ <para><filename>systemd-journald.socket</filename></para>
+ <para><filename>systemd-journald-dev-log.socket</filename></para>
+ <para><filename>systemd-journald-audit.socket</filename></para>
+ <para><filename>/usr/lib/systemd/systemd-journald</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><filename>systemd-journald</filename> is a system service
+ that collects and stores logging data. It creates and maintains
+ structured, indexed journals based on logging information that is
+ received from a variety of sources:</para>
+
+ <itemizedlist>
+ <listitem><para>Kernel log messages, via kmsg</para></listitem>
+
+ <listitem><para>Simple system log messages, via the libc
+ <citerefentry project='man-pages'><refentrytitle>syslog</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+ call</para></listitem>
+
+ <listitem><para>Structured system log messages via the native
+ Journal API, see
+ <citerefentry><refentrytitle>sd_journal_print</refentrytitle><manvolnum>4</manvolnum></citerefentry></para></listitem>
+
+ <listitem><para>Standard output and standard error of system
+ services</para></listitem>
+
+ <listitem><para>Audit records, via the audit
+ subsystem</para></listitem>
+ </itemizedlist>
+
+ <para>The daemon will implicitly collect numerous metadata fields
+ for each log messages in a secure and unfakeable way. See
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ for more information about the collected metadata.
+ </para>
+
+ <para>Log data collected by the journal is primarily text-based
+ but can also include binary data where necessary. All objects
+ stored in the journal can be up to 2^64-1 bytes in size.</para>
+
+ <para>By default, the journal stores log data in
+ <filename>/run/log/journal/</filename>. Since
+ <filename>/run/</filename> is volatile, log data is lost at
+ reboot. To make the data persistent, it is sufficient to create
+ <filename>/var/log/journal/</filename> where
+ <filename>systemd-journald</filename> will then store the
+ data:</para>
+
+ <programlisting>mkdir -p /var/log/journal
+systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
+
+ <para>See
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for information about the configuration of this service.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Signals</title>
+
+ <variablelist>
+ <varlistentry>
+ <term>SIGUSR1</term>
+
+ <listitem><para>Request that journal data from
+ <filename>/run/</filename> is flushed to
+ <filename>/var/</filename> in order to make it persistent (if
+ this is enabled). This must be used after
+ <filename>/var/</filename> is mounted, as otherwise log data
+ from <filename>/run</filename> is never flushed to
+ <filename>/var</filename> regardless of the configuration. The
+ <command>journalctl --flush</command> command uses this signal
+ to request flushing of the journal files, and then waits for
+ the operation to complete. See
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ for details.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>SIGUSR2</term>
+
+ <listitem><para>Request immediate rotation of the journal
+ files. The <command>journalctl --rotate</command> command uses
+ this signal to request journal file
+ rotation.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>SIGRTMIN+1</term>
+
+ <listitem><para>Request that all unwritten log data is written
+ to disk. The <command>journalctl --sync</command> command uses
+ this signal to trigger journal synchronization, and then waits
+ for the operation to complete.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Kernel Command Line</title>
+
+ <para>A few configuration parameters from
+ <filename>journald.conf</filename> may be overridden on the kernel
+ command line:</para>
+
+ <variablelist class='kernel-commandline-options'>
+ <varlistentry>
+ <term><varname>systemd.journald.forward_to_syslog=</varname></term>
+ <term><varname>systemd.journald.forward_to_kmsg=</varname></term>
+ <term><varname>systemd.journald.forward_to_console=</varname></term>
+ <term><varname>systemd.journald.forward_to_wall=</varname></term>
+
+ <listitem><para>Enables/disables forwarding of collected log
+ messages to syslog, the kernel log buffer, the system console
+ or wall.
+ </para>
+
+ <para>See
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for information about these settings.</para>
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Access Control</title>
+
+ <para>Journal files are, by default, owned and readable by the
+ <literal>systemd-journal</literal> system group but are not
+ writable. Adding a user to this group thus enables her/him to read
+ the journal files.</para>
+
+ <para>By default, each logged in user will get her/his own set of
+ journal files in <filename>/var/log/journal/</filename>. These
+ files will not be owned by the user, however, in order to avoid
+ that the user can write to them directly. Instead, file system
+ ACLs are used to ensure the user gets read access only.</para>
+
+ <para>Additional users and groups may be granted access to journal
+ files via file system access control lists (ACL). Distributions
+ and administrators may choose to grant read access to all members
+ of the <literal>wheel</literal> and <literal>adm</literal> system
+ groups with a command such as the following:</para>
+
+ <programlisting># setfacl -Rnm g:wheel:rx,d:g:wheel:rx,g:adm:rx,d:g:adm:rx /var/log/journal/</programlisting>
+
+ <para>Note that this command will update the ACLs both for
+ existing journal files and for future journal files created in the
+ <filename>/var/log/journal/</filename> directory.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Files</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><filename>/etc/systemd/journald.conf</filename></term>
+
+ <listitem><para>Configure
+ <command>systemd-journald</command>
+ behavior. See
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><filename>/run/log/journal/<replaceable>machine-id</replaceable>/*.journal</filename></term>
+ <term><filename>/run/log/journal/<replaceable>machine-id</replaceable>/*.journal~</filename></term>
+ <term><filename>/var/log/journal/<replaceable>machine-id</replaceable>/*.journal</filename></term>
+ <term><filename>/var/log/journal/<replaceable>machine-id</replaceable>/*.journal~</filename></term>
+
+ <listitem><para><command>systemd-journald</command> writes
+ entries to files in
+ <filename>/run/log/journal/<replaceable>machine-id</replaceable>/</filename>
+ or
+ <filename>/var/log/journal/<replaceable>machine-id</replaceable>/</filename>
+ with the <literal>.journal</literal> suffix. If the daemon is
+ stopped uncleanly, or if the files are found to be corrupted,
+ they are renamed using the <literal>.journal~</literal>
+ suffix, and <command>systemd-journald</command> starts writing
+ to a new file. <filename>/run</filename> is used when
+ <filename>/var/log/journal</filename> is not available, or
+ when <option>Storage=volatile</option> is set in the
+ <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ configuration file.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><filename>/dev/kmsg</filename></term>
+ <term><filename>/dev/log</filename></term>
+ <term><filename>/run/systemd/journal/dev-log</filename></term>
+ <term><filename>/run/systemd/journal/socket</filename></term>
+ <term><filename>/run/systemd/journal/stdout</filename></term>
+
+ <listitem><para>Sockets and other paths that
+ <command>systemd-journald</command> will listen on that are
+ visible in the file system. In addition to these, journald can
+ listen for audit events using netlink.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </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>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>sd-journal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-coredump</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry project='die-net'><refentrytitle>setfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>sd_journal_print</refentrytitle><manvolnum>4</manvolnum></citerefentry>,
+ <command>pydoc systemd.journal</command>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-journal/systemd-journald/systemd-journald.socket b/src/grp-journal/systemd-journald/systemd-journald.socket
new file mode 100644
index 0000000000..71737014ca
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald.socket
@@ -0,0 +1,26 @@
+# 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 Socket
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Before=sockets.target
+
+# Mount and swap units need this. If this socket unit is removed by an
+# isolate request the mount and swap units would be removed too,
+# hence let's exclude this from isolate requests.
+IgnoreOnIsolate=yes
+
+[Socket]
+ListenStream=/run/systemd/journal/stdout
+ListenDatagram=/run/systemd/journal/socket
+SocketMode=0666
+PassCredentials=yes
+PassSecurity=yes
+ReceiveBuffer=8M
+Service=systemd-journald.service
diff --git a/src/grp-journal/systemd-journald/systemd-journald.sysusers b/src/grp-journal/systemd-journald/systemd-journald.sysusers
new file mode 100644
index 0000000000..dcb01f606a
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald.sysusers
@@ -0,0 +1,8 @@
+# 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.
+
+g systemd-journal - -
diff --git a/src/grp-journal/systemd-journald/systemd-journald.tmpfiles.m4 b/src/grp-journal/systemd-journald/systemd-journald.tmpfiles.m4
new file mode 100644
index 0000000000..2e8bd8cbef
--- /dev/null
+++ b/src/grp-journal/systemd-journald/systemd-journald.tmpfiles.m4
@@ -0,0 +1,55 @@
+# 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.
+
+# See tmpfiles.d(5) for details
+
+d /run/log 0755 root root -
+
+z /run/log/journal 2755 root systemd-journal - -
+Z /run/log/journal/%m ~2750 root systemd-journal - -
+m4_ifdef(`HAVE_ACL',`m4_dnl
+m4_ifdef(`ENABLE_ADM_GROUP',`m4_dnl
+m4_ifdef(`ENABLE_WHEEL_GROUP',``
+a+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x
+a+ /run/log/journal/%m - - - - group:adm:r-x,group:wheel:r-x
+a+ /run/log/journal/%m/*.journal* - - - - group:adm:r--,group:wheel:r--
+'',``
+a+ /run/log/journal/%m - - - - d:group:adm:r-x
+a+ /run/log/journal/%m - - - - group:adm:r-x
+a+ /run/log/journal/%m/*.journal* - - - - group:adm:r--
+'')',`m4_dnl
+m4_ifdef(`ENABLE_WHEEL_GROUP',``
+a+ /run/log/journal/%m - - - - d:group:wheel:r-x
+a+ /run/log/journal/%m - - - - group:wheel:r-x
+a+ /run/log/journal/%m/*.journal* - - - - group:wheel:r--
+'')')')m4_dnl
+
+z /var/log/journal 2755 root systemd-journal - -
+z /var/log/journal/%m 2755 root systemd-journal - -
+z /var/log/journal/%m/system.journal 0640 root systemd-journal - -
+m4_ifdef(`HAVE_ACL',`m4_dnl
+m4_ifdef(`ENABLE_ADM_GROUP',`m4_dnl
+m4_ifdef(`ENABLE_WHEEL_GROUP',``
+a+ /var/log/journal - - - - d:group:adm:r-x,d:group:wheel:r-x
+a+ /var/log/journal - - - - group:adm:r-x,group:wheel:r-x
+a+ /var/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x
+a+ /var/log/journal/%m - - - - group:adm:r-x,group:wheel:r-x
+a+ /var/log/journal/%m/system.journal - - - - group:adm:r--,group:wheel:r--
+'', ``
+a+ /var/log/journal - - - - d:group:adm:r-x
+a+ /var/log/journal - - - - group:adm:r-x
+a+ /var/log/journal/%m - - - - d:group:adm:r-x
+a+ /var/log/journal/%m - - - - group:adm:r-x
+a+ /var/log/journal/%m/system.journal - - - - group:adm:r--
+'')',`m4_dnl
+m4_ifdef(`ENABLE_WHEEL_GROUP',``
+a+ /var/log/journal - - - - d:group:wheel:r-x
+a+ /var/log/journal - - - - group:wheel:r-x
+a+ /var/log/journal/%m - - - - d:group:wheel:r-x
+a+ /var/log/journal/%m - - - - group:wheel:r-x
+a+ /var/log/journal/%m/system.journal - - - - group:wheel:r--
+'')')')m4_dnl