6f381c
From 2f75df5cd6dcd56775fec9e89fc79672e702d826 Mon Sep 17 00:00:00 2001
6f381c
From: Eric DeVolder <eric.devolder@oracle.com>
6f381c
Date: Thu, 16 May 2019 08:59:01 -0500
6f381c
Subject: [PATCH] pstore: Tool to archive contents of pstore
6f381c
MIME-Version: 1.0
6f381c
Content-Type: text/plain; charset=UTF-8
6f381c
Content-Transfer-Encoding: 8bit
6f381c
6f381c
This patch introduces the systemd pstore service which will archive the
6f381c
contents of the Linux persistent storage filesystem, pstore, to other storage,
6f381c
thus preserving the existing information contained in the pstore, and clearing
6f381c
pstore storage for future error events.
6f381c
6f381c
Linux provides a persistent storage file system, pstore[1], that can store
6f381c
error records when the kernel dies (or reboots or powers-off). These records in
6f381c
turn can be referenced to debug kernel problems (currently the kernel stuffs
6f381c
the tail of the dmesg, which also contains a stack backtrace, into pstore).
6f381c
6f381c
The pstore file system supports a variety of backends that map onto persistent
6f381c
storage, such as the ACPI ERST[2, Section 18.5 Error Serialization] and UEFI
6f381c
variables[3 Appendix N Common Platform Error Record]. The pstore backends
6f381c
typically offer a relatively small amount of persistent storage, e.g. 64KiB,
6f381c
which can quickly fill up and thus prevent subsequent kernel crashes from
6f381c
recording errors. Thus there is a need to monitor and extract the pstore
6f381c
contents so that future kernel problems can also record information in the
6f381c
pstore.
6f381c
6f381c
The pstore service is independent of the kdump service. In cloud environments
6f381c
specifically, host and guest filesystems are on remote filesystems (eg. iSCSI
6f381c
or NFS), thus kdump relies [implicitly and/or explicitly] upon proper operation
6f381c
of networking software *and* hardware *and* infrastructure.  Thus it may not be
6f381c
possible to capture a kernel coredump to a file since writes over the network
6f381c
may not be possible.
6f381c
6f381c
The pstore backend, on the other hand, is completely local and provides a path
6f381c
to store error records which will survive a reboot and aid in post-mortem
6f381c
debugging.
6f381c
6f381c
Usage Notes:
6f381c
This tool moves files from /sys/fs/pstore into /var/lib/systemd/pstore.
6f381c
6f381c
To enable kernel recording of error records into pstore, one must either pass
6f381c
crash_kexec_post_notifiers[4] to the kernel command line or enable via 'echo Y
6f381c
 > /sys/module/kernel/parameters/crash_kexec_post_notifiers'. This option
6f381c
invokes the recording of errors into pstore *before* an attempt to kexec/kdump
6f381c
on a kernel crash.
6f381c
6f381c
Optionally, to record reboots and shutdowns in the pstore, one can either pass
6f381c
the printk.always_kmsg_dump[4] to the kernel command line or enable via 'echo Y >
6f381c
/sys/module/printk/parameters/always_kmsg_dump'. This option enables code on the
6f381c
shutdown path to record information via pstore.
6f381c
6f381c
This pstore service is a oneshot service. When run, the service invokes
6f381c
systemd-pstore which is a tool that performs the following:
6f381c
 - reads the pstore.conf configuration file
6f381c
 - collects the lists of files in the pstore (eg. /sys/fs/pstore)
6f381c
 - for certain file types (eg. dmesg) a handler is invoked
6f381c
 - for all other files, the file is moved from pstore
6f381c
6f381c
 - In the case of dmesg handler, final processing occurs as such:
6f381c
   - files processed in reverse lexigraphical order to faciliate
6f381c
     reconstruction of original dmesg
6f381c
   - the filename is examined to determine which dmesg it is a part
6f381c
   - the file is appended to the reconstructed dmesg
6f381c
6f381c
For example, the following pstore contents:
6f381c
6f381c
 root@vm356:~# ls -al /sys/fs/pstore
6f381c
 total 0
6f381c
 drwxr-x--- 2 root root    0 May  9 09:50 .
6f381c
 drwxr-xr-x 7 root root    0 May  9 09:50 ..
6f381c
 -r--r--r-- 1 root root 1610 May  9 09:49 dmesg-efi-155741337601001
6f381c
 -r--r--r-- 1 root root 1778 May  9 09:49 dmesg-efi-155741337602001
6f381c
 -r--r--r-- 1 root root 1726 May  9 09:49 dmesg-efi-155741337603001
6f381c
 -r--r--r-- 1 root root 1746 May  9 09:49 dmesg-efi-155741337604001
6f381c
 -r--r--r-- 1 root root 1686 May  9 09:49 dmesg-efi-155741337605001
6f381c
 -r--r--r-- 1 root root 1690 May  9 09:49 dmesg-efi-155741337606001
6f381c
 -r--r--r-- 1 root root 1775 May  9 09:49 dmesg-efi-155741337607001
6f381c
 -r--r--r-- 1 root root 1811 May  9 09:49 dmesg-efi-155741337608001
6f381c
 -r--r--r-- 1 root root 1817 May  9 09:49 dmesg-efi-155741337609001
6f381c
 -r--r--r-- 1 root root 1795 May  9 09:49 dmesg-efi-155741337710001
6f381c
 -r--r--r-- 1 root root 1770 May  9 09:49 dmesg-efi-155741337711001
6f381c
 -r--r--r-- 1 root root 1796 May  9 09:49 dmesg-efi-155741337712001
6f381c
 -r--r--r-- 1 root root 1787 May  9 09:49 dmesg-efi-155741337713001
6f381c
 -r--r--r-- 1 root root 1808 May  9 09:49 dmesg-efi-155741337714001
6f381c
 -r--r--r-- 1 root root 1754 May  9 09:49 dmesg-efi-155741337715001
6f381c
6f381c
results in the following:
6f381c
6f381c
 root@vm356:~# ls -al /var/lib/systemd/pstore/155741337/
6f381c
 total 92
6f381c
 drwxr-xr-x 2 root root  4096 May  9 09:50 .
6f381c
 drwxr-xr-x 4 root root    40 May  9 09:50 ..
6f381c
 -rw-r--r-- 1 root root  1610 May  9 09:50 dmesg-efi-155741337601001
6f381c
 -rw-r--r-- 1 root root  1778 May  9 09:50 dmesg-efi-155741337602001
6f381c
 -rw-r--r-- 1 root root  1726 May  9 09:50 dmesg-efi-155741337603001
6f381c
 -rw-r--r-- 1 root root  1746 May  9 09:50 dmesg-efi-155741337604001
6f381c
 -rw-r--r-- 1 root root  1686 May  9 09:50 dmesg-efi-155741337605001
6f381c
 -rw-r--r-- 1 root root  1690 May  9 09:50 dmesg-efi-155741337606001
6f381c
 -rw-r--r-- 1 root root  1775 May  9 09:50 dmesg-efi-155741337607001
6f381c
 -rw-r--r-- 1 root root  1811 May  9 09:50 dmesg-efi-155741337608001
6f381c
 -rw-r--r-- 1 root root  1817 May  9 09:50 dmesg-efi-155741337609001
6f381c
 -rw-r--r-- 1 root root  1795 May  9 09:50 dmesg-efi-155741337710001
6f381c
 -rw-r--r-- 1 root root  1770 May  9 09:50 dmesg-efi-155741337711001
6f381c
 -rw-r--r-- 1 root root  1796 May  9 09:50 dmesg-efi-155741337712001
6f381c
 -rw-r--r-- 1 root root  1787 May  9 09:50 dmesg-efi-155741337713001
6f381c
 -rw-r--r-- 1 root root  1808 May  9 09:50 dmesg-efi-155741337714001
6f381c
 -rw-r--r-- 1 root root  1754 May  9 09:50 dmesg-efi-155741337715001
6f381c
 -rw-r--r-- 1 root root 26754 May  9 09:50 dmesg.txt
6f381c
6f381c
where dmesg.txt is reconstructed from the group of related
6f381c
dmesg-efi-155741337* files.
6f381c
6f381c
Configuration file:
6f381c
The pstore.conf configuration file has four settings, described below.
6f381c
 - Storage : one of "none", "external", or "journal". With "none", this
6f381c
   tool leaves the contents of pstore untouched. With "external", the
6f381c
   contents of the pstore are moved into the /var/lib/systemd/pstore,
6f381c
   as well as logged into the journal.  With "journal", the contents of
6f381c
   the pstore are recorded only in the systemd journal. The default is
6f381c
   "external".
6f381c
 - Unlink : is a boolean. When "true", the default, then files in the
6f381c
   pstore are removed once processed. When "false", processing of the
6f381c
   pstore occurs normally, but the pstore files remain.
6f381c
6f381c
References:
6f381c
[1] "Persistent storage for a kernel's dying breath",
6f381c
    March 23, 2011.
6f381c
    https://lwn.net/Articles/434821/
6f381c
6f381c
[2] "Advanced Configuration and Power Interface Specification",
6f381c
    version 6.2, May 2017.
6f381c
    https://www.uefi.org/sites/default/files/resources/ACPI_6_2.pdf
6f381c
6f381c
[3] "Unified Extensible Firmware Interface Specification",
6f381c
    version 2.8, March 2019.
6f381c
    https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf
6f381c
6f381c
[4] "The kernel’s command-line parameters",
6f381c
    https://static.lwn.net/kerneldoc/admin-guide/kernel-parameters.html
6f381c
6f381c
(cherry picked from commit 9b4abc69b201e5d7295e1b0762883659f053e747)
6f381c
6f381c
Resolves: #2158832
6f381c
---
6f381c
 man/pstore.conf.xml             |  89 +++++++
6f381c
 man/rules/meson.build           |   2 +
6f381c
 man/systemd-pstore.xml          |  99 ++++++++
6f381c
 meson.build                     |  20 ++
6f381c
 meson_options.txt               |   2 +
6f381c
 src/pstore/meson.build          |  10 +
6f381c
 src/pstore/pstore.c             | 395 ++++++++++++++++++++++++++++++++
6f381c
 src/pstore/pstore.conf          |  16 ++
6f381c
 units/meson.build               |   1 +
6f381c
 units/systemd-pstore.service.in |  24 ++
6f381c
 10 files changed, 658 insertions(+)
6f381c
 create mode 100644 man/pstore.conf.xml
6f381c
 create mode 100644 man/systemd-pstore.xml
6f381c
 create mode 100644 src/pstore/meson.build
6f381c
 create mode 100644 src/pstore/pstore.c
6f381c
 create mode 100644 src/pstore/pstore.conf
6f381c
 create mode 100644 units/systemd-pstore.service.in
6f381c
6f381c
diff --git a/man/pstore.conf.xml b/man/pstore.conf.xml
6f381c
new file mode 100644
6f381c
index 0000000000..b5cda47d02
6f381c
--- /dev/null
6f381c
+++ b/man/pstore.conf.xml
6f381c
@@ -0,0 +1,89 @@
6f381c
+
6f381c
+
6f381c
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
6f381c
+
6f381c
+
6f381c
+
6f381c
+          xmlns:xi="http://www.w3.org/2001/XInclude">
6f381c
+  <refentryinfo>
6f381c
+    <title>pstore.conf</title>
6f381c
+    <productname>systemd</productname>
6f381c
+  </refentryinfo>
6f381c
+
6f381c
+  <refmeta>
6f381c
+    <refentrytitle>pstore.conf</refentrytitle>
6f381c
+    <manvolnum>5</manvolnum>
6f381c
+  </refmeta>
6f381c
+
6f381c
+  <refnamediv>
6f381c
+    <refname>pstore.conf</refname>
6f381c
+    <refname>pstore.conf.d</refname>
6f381c
+    <refpurpose>PStore configuration file</refpurpose>
6f381c
+  </refnamediv>
6f381c
+
6f381c
+  <refsynopsisdiv>
6f381c
+    <para>
6f381c
+    <filename>/etc/systemd/pstore.conf</filename>
6f381c
+    <filename>/etc/systemd/pstore.conf.d/*</filename>
6f381c
+    </para>
6f381c
+  </refsynopsisdiv>
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>Description</title>
6f381c
+
6f381c
+    <para>This file configures the behavior of
6f381c
+    <citerefentry><refentrytitle>systemd-pstore</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
6f381c
+    a tool for archiving the contents of the persistent storage filesystem,
6f381c
+    <ulink url="https://www.kernel.org/doc/Documentation/ABI/testing/pstore">pstore</ulink>.
6f381c
+    </para>
6f381c
+  </refsect1>
6f381c
+
6f381c
+  <xi:include href="standard-conf.xml" xpointer="main-conf" />
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>Options</title>
6f381c
+
6f381c
+    <para>All options are configured in the
6f381c
+    <literal>[PStore]</literal> section:</para>
6f381c
+
6f381c
+    <variablelist>
6f381c
+
6f381c
+      <varlistentry>
6f381c
+        <term><varname>Storage=</varname></term>
6f381c
+
6f381c
+        <listitem><para>Controls where to archive (i.e. copy) files from the pstore filesystem. One of <literal>none</literal>,
6f381c
+        <literal>external</literal>, and <literal>journal</literal>. When
6f381c
+        <literal>none</literal>, the tool exits without processing files in the pstore filesystem.
6f381c
+        When <literal>external</literal> (the default), files are archived into <filename>/var/lib/systemd/pstore/</filename>,
6f381c
+        and logged into the journal.
6f381c
+        When <literal>journal</literal>, pstore file contents are logged only in the journal.</para>
6f381c
+        </listitem>
6f381c
+
6f381c
+      </varlistentry>
6f381c
+
6f381c
+      <varlistentry>
6f381c
+        <term><varname>Unlink=</varname></term>
6f381c
+
6f381c
+        <listitem><para>Controls whether or not files are removed from pstore after processing.
6f381c
+        Takes a boolean value. When true, a pstore file is removed from the pstore once it has been
6f381c
+        archived (either to disk or into the journal). When false, processing of pstore files occurs
6f381c
+        normally, but the files remain in the pstore.
6f381c
+        The default is true in order to maintain the pstore in a nearly empty state, so that the pstore
6f381c
+        has storage available for the next kernel error event.
6f381c
+        </para></listitem>
6f381c
+      </varlistentry>
6f381c
+    </variablelist>
6f381c
+
6f381c
+    <para>The defaults for all values are listed as comments in the
6f381c
+    template <filename>/etc/systemd/pstore.conf</filename> file that
6f381c
+    is installed by default.</para>
6f381c
+  </refsect1>
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>See Also</title>
6f381c
+    <para>
6f381c
+      <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
6f381c
+    </para>
6f381c
+  </refsect1>
6f381c
+
6f381c
+</refentry>
6f381c
diff --git a/man/rules/meson.build b/man/rules/meson.build
6f381c
index e6c0a99bbd..6295330c5e 100644
6f381c
--- a/man/rules/meson.build
6f381c
+++ b/man/rules/meson.build
6f381c
@@ -44,6 +44,7 @@ manpages = [
6f381c
  ['os-release', '5', [], ''],
6f381c
  ['pam_systemd', '8', [], 'HAVE_PAM'],
6f381c
  ['portablectl', '1', [], 'ENABLE_PORTABLED'],
6f381c
+ ['pstore.conf', '5', ['pstore.conf.d'], 'ENABLE_PSTORE'],
6f381c
  ['resolvectl', '1', ['resolvconf'], 'ENABLE_RESOLVE'],
6f381c
  ['resolved.conf', '5', ['resolved.conf.d'], 'ENABLE_RESOLVE'],
6f381c
  ['runlevel', '8', [], 'ENABLE_UTMP'],
6f381c
@@ -633,6 +634,7 @@ manpages = [
6f381c
  ['systemd-nspawn', '1', [], ''],
6f381c
  ['systemd-path', '1', [], ''],
6f381c
  ['systemd-portabled.service', '8', ['systemd-portabled'], 'ENABLE_PORTABLED'],
6f381c
+ ['systemd-pstore', '8', ['systemd-pstore.service'], 'ENABLE_PSTORE'],
6f381c
  ['systemd-quotacheck.service',
6f381c
   '8',
6f381c
   ['systemd-quotacheck'],
6f381c
diff --git a/man/systemd-pstore.xml b/man/systemd-pstore.xml
6f381c
new file mode 100644
6f381c
index 0000000000..dd1aa5e83b
6f381c
--- /dev/null
6f381c
+++ b/man/systemd-pstore.xml
6f381c
@@ -0,0 +1,99 @@
6f381c
+
6f381c
+
6f381c
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
6f381c
+
6f381c
+
6f381c
+
6f381c
+          xmlns:xi="http://www.w3.org/2001/XInclude">
6f381c
+
6f381c
+  <refentryinfo>
6f381c
+    <title>systemd-pstore</title>
6f381c
+    <productname>systemd</productname>
6f381c
+  </refentryinfo>
6f381c
+
6f381c
+  <refmeta>
6f381c
+    <refentrytitle>systemd-pstore</refentrytitle>
6f381c
+    <manvolnum>8</manvolnum>
6f381c
+  </refmeta>
6f381c
+
6f381c
+  <refnamediv>
6f381c
+    <refname>systemd-pstore</refname>
6f381c
+    <refname>systemd-pstore.service</refname>
6f381c
+    <refpurpose>Tool to archive contents of the persistent storage filesytem</refpurpose>
6f381c
+  </refnamediv>
6f381c
+
6f381c
+  <refsynopsisdiv>
6f381c
+    <para><filename>/usr/lib/systemd/systemd-pstore</filename></para>
6f381c
+    <para><filename>systemd-pstore.service</filename></para>
6f381c
+  </refsynopsisdiv>
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>Description</title>
6f381c
+    <para><filename>systemd-pstore.service</filename> is a system service that archives the
6f381c
+    contents of the Linux persistent storage filesystem, pstore, to other storage,
6f381c
+    thus preserving the existing information contained in the pstore, and clearing
6f381c
+    pstore storage for future error events.</para>
6f381c
+
6f381c
+    <para>Linux provides a persistent storage file system, pstore, that can store
6f381c
+    error records when the kernel dies (or reboots or powers-off). These records in
6f381c
+    turn can be referenced to debug kernel problems (currently the kernel stuffs
6f381c
+    the tail of the dmesg, which also contains a stack backtrace, into pstore).</para>
6f381c
+
6f381c
+    <para>The pstore file system supports a variety of backends that map onto persistent
6f381c
+    storage, such as the ACPI ERST and UEFI variables. The pstore backends
6f381c
+    typically offer a relatively small amount of persistent storage, e.g. 64KiB,
6f381c
+    which can quickly fill up and thus prevent subsequent kernel crashes from
6f381c
+    recording errors. Thus there is a need to monitor and extract the pstore
6f381c
+    contents so that future kernel problems can also record information in the
6f381c
+    pstore.</para>
6f381c
+
6f381c
+    <para>The pstore service is independent of the kdump service. In cloud environments
6f381c
+    specifically, host and guest filesystems are on remote filesystems (eg. iSCSI
6f381c
+    or NFS), thus kdump relies [implicitly and/or explicitly] upon proper operation
6f381c
+    of networking software *and* hardware *and* infrastructure.  Thus it may not be
6f381c
+    possible to capture a kernel coredump to a file since writes over the network
6f381c
+    may not be possible.</para>
6f381c
+
6f381c
+    <para>The pstore backend, on the other hand, is completely local and provides a path
6f381c
+    to store error records which will survive a reboot and aid in post-mortem
6f381c
+    debugging.</para>
6f381c
+
6f381c
+    <para>The <command>systemd-pstore</command> executable does the actual work. Upon starting,
6f381c
+    the <filename>pstore.conf</filename> is read to obtain options, then the /sys/fs/pstore
6f381c
+    directory contents are processed according to the options. Pstore files are written to the
6f381c
+    journal, and optionally saved into /var/lib/systemd/pstore.</para>
6f381c
+  </refsect1>
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>Configuration</title>
6f381c
+
6f381c
+    <para>The behavior of <command>systemd-pstore</command> is configured through the configuration file
6f381c
+    <filename>/etc/systemd/pstore.conf</filename> and corresponding snippets
6f381c
+    <filename>/etc/systemd/pstore.conf.d/*.conf</filename>, see
6f381c
+    <citerefentry><refentrytitle>pstore.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
6f381c
+    </para>
6f381c
+
6f381c
+    <refsect2>
6f381c
+      <title>Disabling pstore processing</title>
6f381c
+
6f381c
+      <para>To disable pstore processing by <command>systemd-pstore</command>,
6f381c
+      set <programlisting>Storage=none</programlisting> in
6f381c
+      <citerefentry><refentrytitle>pstore.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
6f381c
+      </para>
6f381c
+    </refsect2>
6f381c
+  </refsect1>
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>Usage</title>
6f381c
+    <para>Data stored in the journal can be viewed with
6f381c
+    <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
6f381c
+    as usual.</para>
6f381c
+  </refsect1>
6f381c
+
6f381c
+  <refsect1>
6f381c
+    <title>See Also</title>
6f381c
+    <para>
6f381c
+      <citerefentry><refentrytitle>pstore.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
6f381c
+    </para>
6f381c
+  </refsect1>
6f381c
+</refentry>
6f381c
diff --git a/meson.build b/meson.build
6f381c
index af4cf331da..972a8fb6f7 100644
6f381c
--- a/meson.build
6f381c
+++ b/meson.build
6f381c
@@ -1224,6 +1224,7 @@ foreach term : ['utmp',
6f381c
                 'environment-d',
6f381c
                 'binfmt',
6f381c
                 'coredump',
6f381c
+                'pstore',
6f381c
                 'resolve',
6f381c
                 'logind',
6f381c
                 'hostnamed',
6f381c
@@ -1439,6 +1440,7 @@ subdir('src/network')
6f381c
 subdir('src/analyze')
6f381c
 subdir('src/journal-remote')
6f381c
 subdir('src/coredump')
6f381c
+subdir('src/pstore')
6f381c
 subdir('src/hostname')
6f381c
 subdir('src/import')
6f381c
 subdir('src/kernel-install')
6f381c
@@ -2151,6 +2153,23 @@ if conf.get('ENABLE_COREDUMP') == 1
6f381c
         public_programs += [exe]
6f381c
 endif
6f381c
 
6f381c
+if conf.get('ENABLE_PSTORE') == 1
6f381c
+        executable('systemd-pstore',
6f381c
+                   systemd_pstore_sources,
6f381c
+                   include_directories : includes,
6f381c
+                   link_with : [libshared],
6f381c
+                   dependencies : [threads,
6f381c
+                                   libacl,
6f381c
+                                   libdw,
6f381c
+                                   libxz,
6f381c
+                                   liblz4],
6f381c
+                   install_rpath : rootlibexecdir,
6f381c
+                   install : true,
6f381c
+                   install_dir : rootlibexecdir)
6f381c
+
6f381c
+        public_programs += exe
6f381c
+endif
6f381c
+
6f381c
 if conf.get('ENABLE_BINFMT') == 1
6f381c
         exe = executable('systemd-binfmt',
6f381c
                          'src/binfmt/binfmt.c',
6f381c
@@ -3014,6 +3033,7 @@ foreach tuple : [
6f381c
         ['resolve'],
6f381c
         ['DNS-over-TLS'],
6f381c
         ['coredump'],
6f381c
+        ['pstore'],
6f381c
         ['polkit'],
6f381c
         ['legacy pkla',      install_polkit_pkla],
6f381c
         ['efi'],
6f381c
diff --git a/meson_options.txt b/meson_options.txt
6f381c
index 213079ac15..5624304bf4 100644
6f381c
--- a/meson_options.txt
6f381c
+++ b/meson_options.txt
6f381c
@@ -76,6 +76,8 @@ option('binfmt', type : 'boolean',
6f381c
        description : 'support for custom binary formats')
6f381c
 option('coredump', type : 'boolean',
6f381c
        description : 'install the coredump handler')
6f381c
+option('pstore', type : 'boolean',
6f381c
+       description : 'install the pstore archival tool')
6f381c
 option('logind', type : 'boolean',
6f381c
        description : 'install the systemd-logind stack')
6f381c
 option('hostnamed', type : 'boolean',
6f381c
diff --git a/src/pstore/meson.build b/src/pstore/meson.build
6f381c
new file mode 100644
6f381c
index 0000000000..adbac24b54
6f381c
--- /dev/null
6f381c
+++ b/src/pstore/meson.build
6f381c
@@ -0,0 +1,10 @@
6f381c
+# SPDX-License-Identifier: LGPL-2.1+
6f381c
+
6f381c
+systemd_pstore_sources = files('''
6f381c
+        pstore.c
6f381c
+'''.split())
6f381c
+
6f381c
+if conf.get('ENABLE_PSTORE') == 1
6f381c
+        install_data('pstore.conf',
6f381c
+                     install_dir : pkgsysconfdir)
6f381c
+endif
6f381c
diff --git a/src/pstore/pstore.c b/src/pstore/pstore.c
6f381c
new file mode 100644
6f381c
index 0000000000..f95e016eb6
6f381c
--- /dev/null
6f381c
+++ b/src/pstore/pstore.c
6f381c
@@ -0,0 +1,395 @@
6f381c
+/* SPDX-License-Identifier: LGPL-2.1+ */
6f381c
+
6f381c
+/* Copyright © 2019 Oracle and/or its affiliates. */
6f381c
+
6f381c
+/* Generally speaking, the pstore contains a small number of files
6f381c
+ * that in turn contain a small amount of data.  */
6f381c
+#include <errno.h>
6f381c
+#include <stdio.h>
6f381c
+#include <stdio_ext.h>
6f381c
+#include <sys/prctl.h>
6f381c
+#include <sys/xattr.h>
6f381c
+#include <unistd.h>
6f381c
+
6f381c
+#include "sd-daemon.h"
6f381c
+#include "sd-journal.h"
6f381c
+#include "sd-login.h"
6f381c
+#include "sd-messages.h"
6f381c
+
6f381c
+#include "acl-util.h"
6f381c
+#include "alloc-util.h"
6f381c
+#include "capability-util.h"
6f381c
+#include "cgroup-util.h"
6f381c
+#include "compress.h"
6f381c
+#include "conf-parser.h"
6f381c
+#include "copy.h"
6f381c
+#include "dirent-util.h"
6f381c
+#include "escape.h"
6f381c
+#include "fd-util.h"
6f381c
+#include "fileio.h"
6f381c
+#include "fs-util.h"
6f381c
+#include "io-util.h"
6f381c
+#include "journal-importer.h"
6f381c
+#include "log.h"
6f381c
+#include "macro.h"
6f381c
+#include "missing.h"
6f381c
+#include "mkdir.h"
6f381c
+#include "parse-util.h"
6f381c
+#include "process-util.h"
6f381c
+#include "signal-util.h"
6f381c
+#include "socket-util.h"
6f381c
+#include "special.h"
6f381c
+#include "string-table.h"
6f381c
+#include "string-util.h"
6f381c
+#include "strv.h"
6f381c
+#include "user-util.h"
6f381c
+#include "util.h"
6f381c
+
6f381c
+/* Command line argument handling */
6f381c
+typedef enum PStoreStorage {
6f381c
+        PSTORE_STORAGE_NONE,
6f381c
+        PSTORE_STORAGE_EXTERNAL,
6f381c
+        PSTORE_STORAGE_JOURNAL,
6f381c
+        _PSTORE_STORAGE_MAX,
6f381c
+        _PSTORE_STORAGE_INVALID = -1
6f381c
+} PStoreStorage;
6f381c
+
6f381c
+static const char* const pstore_storage_table[_PSTORE_STORAGE_MAX] = {
6f381c
+        [PSTORE_STORAGE_NONE] = "none",
6f381c
+        [PSTORE_STORAGE_EXTERNAL] = "external",
6f381c
+        [PSTORE_STORAGE_JOURNAL] = "journal",
6f381c
+};
6f381c
+
6f381c
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(pstore_storage, PStoreStorage);
6f381c
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_pstore_storage, pstore_storage, PStoreStorage, "Failed to parse storage setting");
6f381c
+
6f381c
+static PStoreStorage arg_storage = PSTORE_STORAGE_EXTERNAL;
6f381c
+
6f381c
+static bool arg_unlink = true;
6f381c
+static const char *arg_sourcedir = "/sys/fs/pstore";
6f381c
+static const char *arg_archivedir = "/var/lib/systemd/pstore";
6f381c
+
6f381c
+static int parse_config(void) {
6f381c
+        static const ConfigTableItem items[] = {
6f381c
+                { "PStore", "Unlink",  config_parse_bool,           0, &arg_unlink },
6f381c
+                { "PStore", "Storage", config_parse_pstore_storage, 0, &arg_storage },
6f381c
+                {}
6f381c
+        };
6f381c
+
6f381c
+        return config_parse_many_nulstr(PKGSYSCONFDIR "/pstore.conf",
6f381c
+                                        CONF_PATHS_NULSTR("systemd/pstore.conf.d"),
6f381c
+                                        "PStore\0",
6f381c
+                                        config_item_table_lookup, items,
6f381c
+                                        CONFIG_PARSE_WARN, NULL);
6f381c
+}
6f381c
+
6f381c
+/* File list handling - PStoreEntry is the struct and
6f381c
+ * and PStoreEntry is the type that contains all info
6f381c
+ * about a pstore entry.  */
6f381c
+typedef struct PStoreEntry {
6f381c
+        struct dirent dirent;
6f381c
+        bool is_binary;
6f381c
+        bool handled;
6f381c
+        char *content;
6f381c
+        size_t content_size;
6f381c
+} PStoreEntry;
6f381c
+
6f381c
+typedef struct PStoreList {
6f381c
+        PStoreEntry *entries;
6f381c
+        size_t n_entries;
6f381c
+        size_t n_entries_allocated;
6f381c
+} PStoreList;
6f381c
+
6f381c
+static void pstore_entries_reset(PStoreList *list) {
6f381c
+        for (size_t i = 0; i < list->n_entries; i++)
6f381c
+                free(list->entries[i].content);
6f381c
+        free(list->entries);
6f381c
+        list->n_entries = 0;
6f381c
+}
6f381c
+
6f381c
+static int compare_pstore_entries(const void *_a, const void *_b) {
6f381c
+        PStoreEntry *a = (PStoreEntry *)_a, *b = (PStoreEntry *)_b;
6f381c
+        return strcmp(a->dirent.d_name, b->dirent.d_name);
6f381c
+}
6f381c
+
6f381c
+static int move_file(PStoreEntry *pe, const char *subdir) {
6f381c
+        _cleanup_free_ char *ifd_path = NULL;
6f381c
+        _cleanup_free_ char *ofd_path = NULL;
6f381c
+        int r = 0;
6f381c
+        struct iovec iovec[2] = {};
6f381c
+        int n_iovec = 0;
6f381c
+        _cleanup_free_ void *field = NULL;
6f381c
+        const char *suffix = NULL;
6f381c
+        size_t field_size;
6f381c
+
6f381c
+        if (pe->handled)
6f381c
+                return 0;
6f381c
+
6f381c
+        ifd_path = path_join(NULL, arg_sourcedir, pe->dirent.d_name);
6f381c
+        if (!ifd_path)
6f381c
+                return log_oom();
6f381c
+
6f381c
+        ofd_path = path_join(arg_archivedir, subdir, pe->dirent.d_name);
6f381c
+        if (!ofd_path)
6f381c
+                return log_oom();
6f381c
+
6f381c
+        /* Always log to the journal */
6f381c
+        suffix = arg_storage == PSTORE_STORAGE_EXTERNAL ? strjoina(" moved to ", ofd_path) : (char *)".";
6f381c
+        field = strjoina("MESSAGE=PStore ", pe->dirent.d_name, suffix);
6f381c
+        iovec[n_iovec++] = IOVEC_MAKE_STRING(field);
6f381c
+
6f381c
+        field_size = strlen("FILE=") + pe->content_size;
6f381c
+        field = malloc(field_size);
6f381c
+        if (!field)
6f381c
+                return log_oom();
6f381c
+        memcpy(stpcpy(field, "FILE="), pe->content, pe->content_size);
6f381c
+        iovec[n_iovec++] = IOVEC_MAKE(field, field_size);
6f381c
+
6f381c
+        r = sd_journal_sendv(iovec, n_iovec);
6f381c
+        if (r < 0)
6f381c
+                return log_error_errno(r, "Failed to log pstore entry: %m");
6f381c
+
6f381c
+        if (arg_storage == PSTORE_STORAGE_EXTERNAL) {
6f381c
+                /* Move file from pstore to external storage */
6f381c
+                r = mkdir_parents(ofd_path, 0755);
6f381c
+                if (r < 0)
6f381c
+                        return log_error_errno(r, "Failed to create directoy %s: %m", ofd_path);
6f381c
+                r = copy_file_atomic(ifd_path, ofd_path, 0600, 0, COPY_REPLACE);
6f381c
+                if (r < 0)
6f381c
+                        return log_error_errno(r, "Failed to copy_file_atomic: %s to %s", ifd_path, ofd_path);
6f381c
+        }
6f381c
+
6f381c
+        /* If file copied properly, remove it from pstore */
6f381c
+        if (arg_unlink)
6f381c
+                (void) unlink(ifd_path);
6f381c
+
6f381c
+        pe->handled = true;
6f381c
+
6f381c
+        return 0;
6f381c
+}
6f381c
+
6f381c
+static int write_dmesg(const char *dmesg, size_t size, const char *id) {
6f381c
+        _cleanup_(unlink_and_freep) char *ofd_path = NULL;
6f381c
+        _cleanup_free_ char *tmp_path = NULL;
6f381c
+        _cleanup_close_ int ofd = -1;
6f381c
+        ssize_t wr;
6f381c
+        int r;
6f381c
+
6f381c
+        if (isempty(dmesg) || size == 0)
6f381c
+                return 0;
6f381c
+
6f381c
+        /* log_info("Record ID %s", id); */
6f381c
+
6f381c
+        ofd_path = path_join(arg_archivedir, id, "dmesg.txt");
6f381c
+        if (!ofd_path)
6f381c
+                return log_oom();
6f381c
+
6f381c
+        ofd = open_tmpfile_linkable(ofd_path, O_CLOEXEC|O_CREAT|O_TRUNC|O_WRONLY, &tmp_path);
6f381c
+        if (ofd < 0)
6f381c
+                return log_error_errno(ofd, "Failed to open temporary file %s: %m", ofd_path);
6f381c
+        wr = write(ofd, dmesg, size);
6f381c
+        if (wr < 0)
6f381c
+                return log_error_errno(errno, "Failed to store dmesg to %s: %m", ofd_path);
6f381c
+        if (wr != (ssize_t)size)
6f381c
+                return log_error_errno(-EIO, "Failed to store dmesg to %s. %zu bytes are lost.", ofd_path, size - wr);
6f381c
+        r = link_tmpfile(ofd, tmp_path, ofd_path);
6f381c
+        if (r < 0)
6f381c
+                return log_error_errno(r, "Failed to write temporary file %s: %m", ofd_path);
6f381c
+        ofd_path = mfree(ofd_path);
6f381c
+
6f381c
+        return 0;
6f381c
+}
6f381c
+
6f381c
+static void process_dmesg_files(PStoreList *list) {
6f381c
+        /* Move files, reconstruct dmesg.txt */
6f381c
+        PStoreEntry *pe;
6f381c
+        _cleanup_free_ char *dmesg = NULL;
6f381c
+        size_t dmesg_size = 0;
6f381c
+        _cleanup_free_ char *dmesg_id = NULL;
6f381c
+
6f381c
+        /* Handle each dmesg file: files processed in reverse
6f381c
+         * order so as to properly reconstruct original dmesg */
6f381c
+        for (size_t n = list->n_entries; n > 0; n--) {
6f381c
+                bool move_file_and_continue = false;
6f381c
+                _cleanup_free_ char *pe_id = NULL;
6f381c
+                char *p;
6f381c
+                size_t plen;
6f381c
+
6f381c
+                pe = &list->entries[n-1];
6f381c
+
6f381c
+                if (pe->handled)
6f381c
+                        continue;
6f381c
+                if (!startswith(pe->dirent.d_name, "dmesg-"))
6f381c
+                        continue;
6f381c
+
6f381c
+                if (endswith(pe->dirent.d_name, ".enc.z")) /* indicates a problem */
6f381c
+                        move_file_and_continue = true;
6f381c
+                p = strrchr(pe->dirent.d_name, '-');
6f381c
+                if (!p)
6f381c
+                        move_file_and_continue = true;
6f381c
+
6f381c
+                if (move_file_and_continue) {
6f381c
+                        /* A dmesg file on which we do NO additional processing */
6f381c
+                        (void) move_file(pe, NULL);
6f381c
+                        continue;
6f381c
+                }
6f381c
+
6f381c
+                /* See if this file is one of a related group of files
6f381c
+                 * in order to reconstruct dmesg */
6f381c
+
6f381c
+                /* When dmesg is written into pstore, it is done so in
6f381c
+                 * small chunks, whatever the exchange buffer size is
6f381c
+                 * with the underlying pstore backend (ie. EFI may be
6f381c
+                 * ~2KiB), which means an example pstore with approximately
6f381c
+                 * 64KB of storage may have up to roughly 32 dmesg files
6f381c
+                 * that could be related, depending upon the size of the
6f381c
+                 * original dmesg.
6f381c
+                 *
6f381c
+                 * Here we look at the dmesg filename and try to discern
6f381c
+                 * if files are part of a related group, meaning the same
6f381c
+                 * original dmesg.
6f381c
+                 *
6f381c
+                 * The two known pstore backends are EFI and ERST. These
6f381c
+                 * backends store data in the Common Platform Error
6f381c
+                 * Record, CPER, format. The dmesg- filename contains the
6f381c
+                 * CPER record id, a 64bit number (in decimal notation).
6f381c
+                 * In Linux, the record id is encoded with two digits for
6f381c
+                 * the dmesg part (chunk) number and 3 digits for the
6f381c
+                 * count number. So allowing an additional digit to
6f381c
+                 * compensate for advancing time, this code ignores the
6f381c
+                 * last six digits of the filename in determining the
6f381c
+                 * record id.
6f381c
+                 *
6f381c
+                 * For the EFI backend, the record id encodes an id in the
6f381c
+                 * upper 32 bits, and a timestamp in the lower 32-bits.
6f381c
+                 * So ignoring the least significant 6 digits has proven
6f381c
+                 * to generally identify related dmesg entries.  */
6f381c
+#define PSTORE_FILENAME_IGNORE 6
6f381c
+
6f381c
+                /* determine common portion of record id */
6f381c
+                ++p; /* move beyond dmesg- */
6f381c
+                plen = strlen(p);
6f381c
+                if (plen > PSTORE_FILENAME_IGNORE) {
6f381c
+                        pe_id = memdup_suffix0(p, plen - PSTORE_FILENAME_IGNORE);
6f381c
+                        if (!pe_id) {
6f381c
+                                log_oom();
6f381c
+                                return;
6f381c
+                        }
6f381c
+                } else
6f381c
+                        pe_id = mfree(pe_id);
6f381c
+
6f381c
+                /* Now move file from pstore to archive storage */
6f381c
+                move_file(pe, pe_id);
6f381c
+
6f381c
+                /* If the current record id is NOT the same as the
6f381c
+                 * previous record id, then start a new dmesg.txt file */
6f381c
+                if (!pe_id || !dmesg_id || !streq(pe_id, dmesg_id)) {
6f381c
+                        /* Encountered a new dmesg group, close out old one, open new one */
6f381c
+                        if (dmesg) {
6f381c
+                                (void) write_dmesg(dmesg, dmesg_size, dmesg_id);
6f381c
+                                dmesg = mfree(dmesg);
6f381c
+                                dmesg_size = 0;
6f381c
+                        }
6f381c
+
6f381c
+                        /* now point dmesg_id to storage of pe_id */
6f381c
+                        free_and_replace(dmesg_id, pe_id);
6f381c
+                }
6f381c
+
6f381c
+                /* Reconstruction of dmesg is done as a useful courtesy, do not log errors */
6f381c
+                dmesg = realloc(dmesg, dmesg_size + strlen(pe->dirent.d_name) + strlen(":\n") + pe->content_size + 1);
6f381c
+                if (dmesg) {
6f381c
+                        dmesg_size += sprintf(&dmesg[dmesg_size], "%s:\n", pe->dirent.d_name);
6f381c
+                        if (pe->content) {
6f381c
+                                memcpy(&dmesg[dmesg_size], pe->content, pe->content_size);
6f381c
+                                dmesg_size += pe->content_size;
6f381c
+                        }
6f381c
+                }
6f381c
+
6f381c
+                pe_id = mfree(pe_id);
6f381c
+        }
6f381c
+        if (dmesg)
6f381c
+                (void) write_dmesg(dmesg, dmesg_size, dmesg_id);
6f381c
+}
6f381c
+
6f381c
+static int list_files(PStoreList *list, const char *sourcepath) {
6f381c
+        _cleanup_(closedirp) DIR *dirp = NULL;
6f381c
+        struct dirent *de;
6f381c
+        int r = 0;
6f381c
+
6f381c
+        dirp = opendir(sourcepath);
6f381c
+        if (!dirp)
6f381c
+                return log_error_errno(errno, "Failed to opendir %s: %m", sourcepath);
6f381c
+
6f381c
+        FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", sourcepath)) {
6f381c
+                _cleanup_free_ char *ifd_path = NULL;
6f381c
+
6f381c
+                ifd_path = path_join(NULL, sourcepath, de->d_name);
6f381c
+                if (!ifd_path)
6f381c
+                        return log_oom();
6f381c
+
6f381c
+                _cleanup_free_ char *buf = NULL;
6f381c
+                size_t buf_size;
6f381c
+
6f381c
+                /* Now read contents of pstore file */
6f381c
+                r = read_full_file(ifd_path, &buf, &buf_size);
6f381c
+                if (r < 0) {
6f381c
+                        log_warning_errno(r, "Failed to read file %s: %m", ifd_path);
6f381c
+                        continue;
6f381c
+                }
6f381c
+
6f381c
+                if (!GREEDY_REALLOC(list->entries, list->n_entries_allocated, list->n_entries + 1))
6f381c
+                        return log_oom();
6f381c
+
6f381c
+                list->entries[list->n_entries++] = (PStoreEntry) {
6f381c
+                        .dirent = *de,
6f381c
+                        .content = TAKE_PTR(buf),
6f381c
+                        .content_size = buf_size,
6f381c
+                        .is_binary = true,
6f381c
+                        .handled = false,
6f381c
+                };
6f381c
+        }
6f381c
+
6f381c
+        return r;
6f381c
+}
6f381c
+
6f381c
+static int run(int argc, char *argv[]) {
6f381c
+        _cleanup_(pstore_entries_reset) PStoreList list = {};
6f381c
+        int r;
6f381c
+
6f381c
+        log_open();
6f381c
+
6f381c
+        /* Ignore all parse errors */
6f381c
+        (void) parse_config();
6f381c
+
6f381c
+        log_debug("Selected storage '%s'.", pstore_storage_to_string(arg_storage));
6f381c
+        log_debug("Selected Unlink '%d'.", arg_unlink);
6f381c
+
6f381c
+        if (arg_storage == PSTORE_STORAGE_NONE)
6f381c
+                /* Do nothing, intentionally, leaving pstore untouched */
6f381c
+                return 0;
6f381c
+
6f381c
+        /* Obtain list of files in pstore */
6f381c
+        r = list_files(&list, arg_sourcedir);
6f381c
+        if (r < 0)
6f381c
+                return r;
6f381c
+
6f381c
+        /* Handle each pstore file */
6f381c
+        /* Sort files lexigraphically ascending, generally needed by all */
6f381c
+        qsort_safe(list.entries, list.n_entries, sizeof(PStoreEntry), compare_pstore_entries);
6f381c
+
6f381c
+        /* Process known file types */
6f381c
+        process_dmesg_files(&list);
6f381c
+
6f381c
+        /* Move left over files out of pstore */
6f381c
+        for (size_t n = 0; n < list.n_entries; n++)
6f381c
+                move_file(&list.entries[n], NULL);
6f381c
+
6f381c
+        return 0;
6f381c
+}
6f381c
+
6f381c
+int main(int argc, char *argv[]) {
6f381c
+        int r;
6f381c
+
6f381c
+        r = run(argc, argv);
6f381c
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
6f381c
+}
6f381c
diff --git a/src/pstore/pstore.conf b/src/pstore/pstore.conf
6f381c
new file mode 100644
6f381c
index 0000000000..93a8b6707c
6f381c
--- /dev/null
6f381c
+++ b/src/pstore/pstore.conf
6f381c
@@ -0,0 +1,16 @@
6f381c
+#  This file is part of systemd.
6f381c
+#
6f381c
+#  systemd is free software; you can redistribute it and/or modify it
6f381c
+#  under the terms of the GNU Lesser General Public License as published by
6f381c
+#  the Free Software Foundation; either version 2.1 of the License, or
6f381c
+#  (at your option) any later version.
6f381c
+#
6f381c
+# Entries in this file show the compile time defaults.
6f381c
+# You can change settings by editing this file.
6f381c
+# Defaults can be restored by simply deleting this file.
6f381c
+#
6f381c
+# See pstore.conf(5) for details.
6f381c
+
6f381c
+[PStore]
6f381c
+#Storage=external
6f381c
+#Unlink=yes
6f381c
diff --git a/units/meson.build b/units/meson.build
6f381c
index a74fa95195..e8e64eb30a 100644
6f381c
--- a/units/meson.build
6f381c
+++ b/units/meson.build
6f381c
@@ -136,6 +136,7 @@ in_units = [
6f381c
         ['systemd-binfmt.service',               'ENABLE_BINFMT',
6f381c
          'sysinit.target.wants/'],
6f381c
         ['systemd-coredump@.service',            'ENABLE_COREDUMP'],
6f381c
+        ['systemd-pstore.service',               'ENABLE_PSTORE'],
6f381c
         ['systemd-firstboot.service',            'ENABLE_FIRSTBOOT',
6f381c
          'sysinit.target.wants/'],
6f381c
         ['systemd-fsck-root.service',            ''],
6f381c
diff --git a/units/systemd-pstore.service.in b/units/systemd-pstore.service.in
6f381c
new file mode 100644
6f381c
index 0000000000..fec2b1aebf
6f381c
--- /dev/null
6f381c
+++ b/units/systemd-pstore.service.in
6f381c
@@ -0,0 +1,24 @@
6f381c
+#  SPDX-License-Identifier: LGPL-2.1+
6f381c
+#
6f381c
+#  This file is part of systemd.
6f381c
+#
6f381c
+#  systemd is free software; you can redistribute it and/or modify it
6f381c
+#  under the terms of the GNU Lesser General Public License as published by
6f381c
+#  the Free Software Foundation; either version 2.1 of the License, or
6f381c
+#  (at your option) any later version.
6f381c
+
6f381c
+[Unit]
6f381c
+Description=Platform Persistent Storage Archival
6f381c
+Documentation=man:systemd-pstore(8)
6f381c
+DefaultDependencies=no
6f381c
+Wants=systemd-remount-fs.service
6f381c
+After=systemd-remount-fs.service
6f381c
+
6f381c
+[Service]
6f381c
+Type=oneshot
6f381c
+ExecStart=@rootlibexecdir@/systemd-pstore
6f381c
+RemainAfterExit=yes
6f381c
+StateDirectory=systemd/pstore
6f381c
+
6f381c
+[Install]
6f381c
+WantedBy=systemd-remount-fs.service