summaryrefslogtreecommitdiff
path: root/src/grp-initprogs
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-09-07 19:32:21 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-09-07 19:32:21 -0400
commitd351468cd9898f9a0714640bc6f7c9177dcf578a (patch)
tree816b07c101bcf8e21817ba712999eacbdbcb32a9 /src/grp-initprogs
parentcf8410fb2cb47740865b8ba9ab5f4d42eb53cec2 (diff)
./move.sh
Diffstat (limited to 'src/grp-initprogs')
-rw-r--r--src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/hibernate-resume-generator.c99
-rw-r--r--src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/systemd-hibernate-resume-generator.xml93
-rw-r--r--src/grp-initprogs/grp-sleep/systemd-hibernate-resume/hibernate-resume.c82
-rw-r--r--src/grp-initprogs/grp-sleep/systemd-hibernate-resume/systemd-hibernate-resume@.service.in20
-rw-r--r--src/grp-initprogs/grp-sleep/systemd-sleep/sleep.c215
-rw-r--r--src/grp-initprogs/systemd-backlight/backlight.c434
-rw-r--r--src/grp-initprogs/systemd-backlight/systemd-backlight@.service.in22
-rw-r--r--src/grp-initprogs/systemd-binfmt/binfmt.c203
-rw-r--r--src/grp-initprogs/systemd-binfmt/systemd-binfmt.service.in27
-rw-r--r--src/grp-initprogs/systemd-detect-virt/detect-virt.c169
-rw-r--r--src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.bash40
-rw-r--r--src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.zsh11
-rw-r--r--src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.xml245
-rw-r--r--src/grp-initprogs/systemd-firstboot/firstboot.c870
-rw-r--r--src/grp-initprogs/systemd-firstboot/systemd-firstboot.service.in24
-rw-r--r--src/grp-initprogs/systemd-firstboot/systemd-firstboot.xml259
-rw-r--r--src/grp-initprogs/systemd-fsck/fsck.c487
-rw-r--r--src/grp-initprogs/systemd-fsck/systemd-fsck@.service.in20
-rw-r--r--src/grp-initprogs/systemd-modules-load/modules-load.c283
-rw-r--r--src/grp-initprogs/systemd-modules-load/systemd-modules-load.service.in27
-rw-r--r--src/grp-initprogs/systemd-quotacheck/quotacheck.c124
-rw-r--r--src/grp-initprogs/systemd-quotacheck/systemd-quotacheck.service.in20
-rw-r--r--src/grp-initprogs/systemd-random-seed/random-seed.c176
-rw-r--r--src/grp-initprogs/systemd-random-seed/systemd-random-seed.service.in22
-rw-r--r--src/grp-initprogs/systemd-rfkill/rfkill.c426
-rw-r--r--src/grp-initprogs/systemd-rfkill/systemd-rfkill.service.in21
-rw-r--r--src/grp-initprogs/systemd-rfkill/systemd-rfkill.socket19
-rw-r--r--src/grp-initprogs/systemd-sysctl/sysctl.c287
-rw-r--r--src/grp-initprogs/systemd-sysctl/systemd-sysctl.service.in21
-rw-r--r--src/grp-initprogs/systemd-sysusers/systemd-sysusers.service.in21
-rw-r--r--src/grp-initprogs/systemd-sysusers/systemd-sysusers.xml116
-rw-r--r--src/grp-initprogs/systemd-sysusers/sysusers.c1919
-rw-r--r--src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.completion.zsh13
-rw-r--r--src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.xml200
-rw-r--r--src/grp-initprogs/systemd-tmpfiles/tmpfiles.c2343
-rw-r--r--src/grp-initprogs/systemd-update-done/systemd-update-done.service.in21
-rw-r--r--src/grp-initprogs/systemd-update-done/update-done.c115
-rw-r--r--src/grp-initprogs/systemd-update-utmp/systemd-update-utmp.service.in21
-rw-r--r--src/grp-initprogs/systemd-update-utmp/update-utmp.c283
-rw-r--r--src/grp-initprogs/systemd-user-sessions/systemd-user-sessions.service.in17
-rw-r--r--src/grp-initprogs/systemd-user-sessions/user-sessions.c84
-rw-r--r--src/grp-initprogs/systemd-vconsole-setup/.gitignore1
-rw-r--r--src/grp-initprogs/systemd-vconsole-setup/90-vconsole.rules.in10
-rw-r--r--src/grp-initprogs/systemd-vconsole-setup/systemd-vconsole-setup.service.in19
-rw-r--r--src/grp-initprogs/systemd-vconsole-setup/vconsole-setup.c332
45 files changed, 10261 insertions, 0 deletions
diff --git a/src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/hibernate-resume-generator.c b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/hibernate-resume-generator.c
new file mode 100644
index 0000000000..d7ee80d58f
--- /dev/null
+++ b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/hibernate-resume-generator.c
@@ -0,0 +1,99 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Ivan Shapovalov
+
+ 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 <stdio.h>
+
+#include "alloc-util.h"
+#include "fstab-util.h"
+#include "log.h"
+#include "mkdir.h"
+#include "proc-cmdline.h"
+#include "special.h"
+#include "string-util.h"
+#include "unit-name.h"
+#include "util.h"
+
+static const char *arg_dest = "/tmp";
+static char *arg_resume_dev = NULL;
+
+static int parse_proc_cmdline_item(const char *key, const char *value) {
+
+ if (streq(key, "resume") && value) {
+ free(arg_resume_dev);
+ arg_resume_dev = fstab_node_to_udev_node(value);
+ if (!arg_resume_dev)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+static int process_resume(void) {
+ _cleanup_free_ char *name = NULL, *lnk = NULL;
+ int r;
+
+ if (!arg_resume_dev)
+ return 0;
+
+ r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_dev, ".service", &name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit name: %m");
+
+ lnk = strjoin(arg_dest, "/" SPECIAL_SYSINIT_TARGET ".wants/", name, NULL);
+ if (!lnk)
+ return log_oom();
+
+ mkdir_parents_label(lnk, 0755);
+ if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-hibernate-resume@.service", lnk) < 0)
+ return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ int r = 0;
+
+ if (argc > 1 && argc != 4) {
+ log_error("This program takes three or no arguments.");
+ return EXIT_FAILURE;
+ }
+
+ if (argc > 1)
+ arg_dest = argv[1];
+
+ log_set_target(LOG_TARGET_SAFE);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ /* Don't even consider resuming outside of initramfs. */
+ if (!in_initrd())
+ return EXIT_SUCCESS;
+
+ r = parse_proc_cmdline(parse_proc_cmdline_item);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ r = process_resume();
+ free(arg_resume_dev);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/systemd-hibernate-resume-generator.xml b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/systemd-hibernate-resume-generator.xml
new file mode 100644
index 0000000000..d811b9b551
--- /dev/null
+++ b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume-generator/systemd-hibernate-resume-generator.xml
@@ -0,0 +1,93 @@
+<?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 2014 Ivan Shapovalov
+
+ 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-hibernate-resume-generator">
+
+ <refentryinfo>
+ <title>systemd-hibernate-resume-generator</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Ivan</firstname>
+ <surname>Shapovalov</surname>
+ <email>intelfx100@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-hibernate-resume-generator</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-hibernate-resume-generator</refname>
+ <refpurpose>Unit generator for resume= kernel parameter</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/usr/lib/systemd/system-generators/systemd-hibernate-resume-generator</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><filename>systemd-hibernate-resume-generator</filename> is a
+ generator that instantiates
+ <citerefentry><refentrytitle>systemd-hibernate-resume@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ unit according to the value of <option>resume=</option> parameter
+ specified on the kernel command line.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Kernel Command Line</title>
+
+ <para><filename>systemd-hibernate-resume-generator</filename>
+ understands the following kernel command line parameters:</para>
+
+ <variablelist class='kernel-commandline-options'>
+
+ <varlistentry>
+ <term><varname>resume=</varname></term>
+
+ <listitem><para>Takes a path to the resume device. Both
+ persistent block device paths like
+ <filename>/dev/disk/by-foo/bar</filename> and
+ <citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>-style
+ specifiers like <literal>FOO=bar</literal> are
+ supported.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-hibernate-resume@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-initprogs/grp-sleep/systemd-hibernate-resume/hibernate-resume.c b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume/hibernate-resume.c
new file mode 100644
index 0000000000..21df3c4461
--- /dev/null
+++ b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume/hibernate-resume.c
@@ -0,0 +1,82 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Ivan Shapovalov
+
+ 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 <stdio.h>
+#include <sys/stat.h>
+
+#include "alloc-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "util.h"
+
+int main(int argc, char *argv[]) {
+ struct stat st;
+ const char *device;
+ _cleanup_free_ char *major_minor = NULL;
+ int r;
+
+ if (argc != 2) {
+ log_error("This program expects one argument.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ /* Refuse to run unless we are in an initrd() */
+ if (!in_initrd())
+ return EXIT_SUCCESS;
+
+ device = argv[1];
+
+ if (stat(device, &st) < 0) {
+ log_error_errno(errno, "Failed to stat '%s': %m", device);
+ return EXIT_FAILURE;
+ }
+
+ if (!S_ISBLK(st.st_mode)) {
+ log_error("Resume device '%s' is not a block device.", device);
+ return EXIT_FAILURE;
+ }
+
+ if (asprintf(&major_minor, "%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ r = write_string_file("/sys/power/resume", major_minor, WRITE_STRING_FILE_CREATE);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write '%s' to /sys/power/resume: %m", major_minor);
+ return EXIT_FAILURE;
+ }
+
+ /*
+ * The write above shall not return.
+ *
+ * However, failed resume is a normal condition (may mean that there is
+ * no hibernation image).
+ */
+
+ log_info("Could not resume from '%s' (%s).", device, major_minor);
+ return EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/grp-sleep/systemd-hibernate-resume/systemd-hibernate-resume@.service.in b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume/systemd-hibernate-resume@.service.in
new file mode 100644
index 0000000000..65e8eb83f1
--- /dev/null
+++ b/src/grp-initprogs/grp-sleep/systemd-hibernate-resume/systemd-hibernate-resume@.service.in
@@ -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=Resume from hibernation using device %f
+Documentation=man:systemd-hibernate-resume@.service(8)
+DefaultDependencies=no
+BindsTo=%i.device
+Wants=local-fs-pre.target
+After=%i.device
+Before=local-fs-pre.target
+ConditionPathExists=/etc/initrd-release
+
+[Service]
+Type=oneshot
+ExecStart=@rootlibexecdir@/systemd-hibernate-resume %f
diff --git a/src/grp-initprogs/grp-sleep/systemd-sleep/sleep.c b/src/grp-initprogs/grp-sleep/systemd-sleep/sleep.c
new file mode 100644
index 0000000000..7f8a95728d
--- /dev/null
+++ b/src/grp-initprogs/grp-sleep/systemd-sleep/sleep.c
@@ -0,0 +1,215 @@
+/***
+ 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 <getopt.h>
+#include <stdio.h>
+
+#include <systemd/sd-messages.h>
+
+#include "def.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "sleep-config.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+static char* arg_verb = NULL;
+
+static int write_mode(char **modes) {
+ int r = 0;
+ char **mode;
+
+ STRV_FOREACH(mode, modes) {
+ int k;
+
+ k = write_string_file("/sys/power/disk", *mode, 0);
+ if (k == 0)
+ return 0;
+
+ log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m",
+ *mode);
+ if (r == 0)
+ r = k;
+ }
+
+ if (r < 0)
+ log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");
+
+ return r;
+}
+
+static int write_state(FILE **f, char **states) {
+ char **state;
+ int r = 0;
+
+ STRV_FOREACH(state, states) {
+ int k;
+
+ k = write_string_stream(*f, *state, true);
+ if (k == 0)
+ return 0;
+ log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m",
+ *state);
+ if (r == 0)
+ r = k;
+
+ fclose(*f);
+ *f = fopen("/sys/power/state", "we");
+ if (!*f)
+ return log_error_errno(errno, "Failed to open /sys/power/state: %m");
+ }
+
+ return r;
+}
+
+static int execute(char **modes, char **states) {
+
+ char *arguments[] = {
+ NULL,
+ (char*) "pre",
+ arg_verb,
+ NULL
+ };
+ static const char* const dirs[] = {SYSTEM_SLEEP_PATH, NULL};
+
+ int r;
+ _cleanup_fclose_ FILE *f = NULL;
+
+ /* This file is opened first, so that if we hit an error,
+ * we can abort before modifying any state. */
+ f = fopen("/sys/power/state", "we");
+ if (!f)
+ return log_error_errno(errno, "Failed to open /sys/power/state: %m");
+
+ /* Configure the hibernation mode */
+ r = write_mode(modes);
+ if (r < 0)
+ return r;
+
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments);
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_START),
+ LOG_MESSAGE("Suspending system..."),
+ "SLEEP=%s", arg_verb,
+ NULL);
+
+ r = write_state(&f, states);
+ if (r < 0)
+ return r;
+
+ log_struct(LOG_INFO,
+ LOG_MESSAGE_ID(SD_MESSAGE_SLEEP_STOP),
+ LOG_MESSAGE("System resumed."),
+ "SLEEP=%s", arg_verb,
+ NULL);
+
+ arguments[1] = (char*) "post";
+ execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments);
+
+ return r;
+}
+
+static void help(void) {
+ printf("%s COMMAND\n\n"
+ "Suspend the system, hibernate the system, or both.\n\n"
+ "Commands:\n"
+ " -h --help Show this help and exit\n"
+ " --version Print version string and exit\n"
+ " suspend Suspend the system\n"
+ " hibernate Hibernate the system\n"
+ " hybrid-sleep Both hibernate and suspend the system\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch(c) {
+ case 'h':
+ help();
+ return 0; /* done */
+
+ case ARG_VERSION:
+ return version();
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ if (argc - optind != 1) {
+ log_error("Usage: %s COMMAND",
+ program_invocation_short_name);
+ return -EINVAL;
+ }
+
+ arg_verb = argv[optind];
+
+ if (!streq(arg_verb, "suspend") &&
+ !streq(arg_verb, "hibernate") &&
+ !streq(arg_verb, "hybrid-sleep")) {
+ log_error("Unknown command '%s'.", arg_verb);
+ return -EINVAL;
+ }
+
+ return 1 /* work to do */;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_strv_free_ char **modes = NULL, **states = NULL;
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ r = parse_sleep_config(arg_verb, &modes, &states);
+ if (r < 0)
+ goto finish;
+
+ r = execute(modes, states);
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-backlight/backlight.c b/src/grp-initprogs/systemd-backlight/backlight.c
new file mode 100644
index 0000000000..45be135a23
--- /dev/null
+++ b/src/grp-initprogs/systemd-backlight/backlight.c
@@ -0,0 +1,434 @@
+/***
+ 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 "libudev.h"
+
+#include "alloc-util.h"
+#include "def.h"
+#include "escape.h"
+#include "fileio.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "proc-cmdline.h"
+#include "string-util.h"
+#include "udev-util.h"
+#include "util.h"
+
+static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) {
+ struct udev_device *parent;
+ const char *subsystem, *sysname;
+
+ assert(device);
+
+ parent = udev_device_get_parent(device);
+ if (!parent)
+ return NULL;
+
+ subsystem = udev_device_get_subsystem(parent);
+ if (!subsystem)
+ return NULL;
+
+ sysname = udev_device_get_sysname(parent);
+ if (!sysname)
+ return NULL;
+
+ if (streq(subsystem, "drm")) {
+ const char *c;
+
+ c = startswith(sysname, "card");
+ if (!c)
+ return NULL;
+
+ c += strspn(c, DIGITS);
+ if (*c == '-') {
+ /* A connector DRM device, let's ignore all but LVDS and eDP! */
+
+ if (!startswith(c, "-LVDS-") &&
+ !startswith(c, "-Embedded DisplayPort-"))
+ return NULL;
+ }
+
+ } else if (streq(subsystem, "pci")) {
+ const char *value;
+
+ value = udev_device_get_sysattr_value(parent, "class");
+ if (value) {
+ unsigned long class = 0;
+
+ if (safe_atolu(value, &class) < 0) {
+ log_warning("Cannot parse PCI class %s of device %s:%s.",
+ value, subsystem, sysname);
+ return NULL;
+ }
+
+ /* Graphics card */
+ if (class == 0x30000)
+ return parent;
+ }
+
+ } else if (streq(subsystem, "platform"))
+ return parent;
+
+ return find_pci_or_platform_parent(parent);
+}
+
+static bool same_device(struct udev_device *a, struct udev_device *b) {
+ assert(a);
+ assert(b);
+
+ if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b)))
+ return false;
+
+ if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b)))
+ return false;
+
+ return true;
+}
+
+static bool validate_device(struct udev *udev, struct udev_device *device) {
+ _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL;
+ struct udev_list_entry *item = NULL, *first = NULL;
+ struct udev_device *parent;
+ const char *v, *subsystem;
+ int r;
+
+ assert(udev);
+ assert(device);
+
+ /* Verify whether we should actually care for a specific
+ * backlight device. For backlight devices there might be
+ * multiple ways to access the same control: "firmware"
+ * (i.e. ACPI), "platform" (i.e. via the machine's EC) and
+ * "raw" (via the graphics card). In general we should prefer
+ * "firmware" (i.e. ACPI) or "platform" access over "raw"
+ * access, in order not to confuse the BIOS/EC, and
+ * compatibility with possible low-level hotkey handling of
+ * screen brightness. The kernel will already make sure to
+ * expose only one of "firmware" and "platform" for the same
+ * device to userspace. However, we still need to make sure
+ * that we use "raw" only if no "firmware" or "platform"
+ * device for the same device exists. */
+
+ subsystem = udev_device_get_subsystem(device);
+ if (!streq_ptr(subsystem, "backlight"))
+ return true;
+
+ v = udev_device_get_sysattr_value(device, "type");
+ if (!streq_ptr(v, "raw"))
+ return true;
+
+ parent = find_pci_or_platform_parent(device);
+ if (!parent)
+ return true;
+
+ subsystem = udev_device_get_subsystem(parent);
+ if (!subsystem)
+ return true;
+
+ enumerate = udev_enumerate_new(udev);
+ if (!enumerate)
+ return true;
+
+ r = udev_enumerate_add_match_subsystem(enumerate, "backlight");
+ if (r < 0)
+ return true;
+
+ r = udev_enumerate_scan_devices(enumerate);
+ if (r < 0)
+ return true;
+
+ first = udev_enumerate_get_list_entry(enumerate);
+ udev_list_entry_foreach(item, first) {
+ _cleanup_udev_device_unref_ struct udev_device *other;
+ struct udev_device *other_parent;
+ const char *other_subsystem;
+
+ other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
+ if (!other)
+ return true;
+
+ if (same_device(device, other))
+ continue;
+
+ v = udev_device_get_sysattr_value(other, "type");
+ if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware"))
+ continue;
+
+ /* OK, so there's another backlight device, and it's a
+ * platform or firmware device, so, let's see if we
+ * can verify it belongs to the same device as
+ * ours. */
+ other_parent = find_pci_or_platform_parent(other);
+ if (!other_parent)
+ continue;
+
+ if (same_device(parent, other_parent)) {
+ /* Both have the same PCI parent, that means
+ * we are out. */
+ log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.",
+ udev_device_get_sysname(device),
+ udev_device_get_sysname(other));
+ return false;
+ }
+
+ other_subsystem = udev_device_get_subsystem(other_parent);
+ if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) {
+ /* The other is connected to the platform bus
+ * and we are a PCI device, that also means we
+ * are out. */
+ log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.",
+ udev_device_get_sysname(device),
+ udev_device_get_sysname(other));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static unsigned get_max_brightness(struct udev_device *device) {
+ int r;
+ const char *max_brightness_str;
+ unsigned max_brightness;
+
+ max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness");
+ if (!max_brightness_str) {
+ log_warning("Failed to read 'max_brightness' attribute.");
+ return 0;
+ }
+
+ r = safe_atou(max_brightness_str, &max_brightness);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str);
+ return 0;
+ }
+
+ if (max_brightness <= 0) {
+ log_warning("Maximum brightness is 0, ignoring device.");
+ return 0;
+ }
+
+ return max_brightness;
+}
+
+/* Some systems turn the backlight all the way off at the lowest levels.
+ * clamp_brightness clamps the saved brightness to at least 1 or 5% of
+ * max_brightness in case of 'backlight' subsystem. This avoids preserving
+ * an unreadably dim screen, which would otherwise force the user to
+ * disable state restoration. */
+static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) {
+ int r;
+ unsigned brightness, new_brightness, min_brightness;
+ const char *subsystem;
+
+ r = safe_atou(*value, &brightness);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value);
+ return;
+ }
+
+ subsystem = udev_device_get_subsystem(device);
+ if (streq_ptr(subsystem, "backlight"))
+ min_brightness = MAX(1U, max_brightness/20);
+ else
+ min_brightness = 0;
+
+ new_brightness = CLAMP(brightness, min_brightness, max_brightness);
+ if (new_brightness != brightness) {
+ char *old_value = *value;
+
+ r = asprintf(value, "%u", new_brightness);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ log_info("Saved brightness %s %s to %s.", old_value,
+ new_brightness > brightness ?
+ "too low; increasing" : "too high; decreasing",
+ *value);
+
+ free(old_value);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL;
+ const char *sysname, *path_id;
+ unsigned max_brightness;
+ int r;
+
+ if (argc != 3) {
+ log_error("This program requires two arguments.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = mkdir_p("/var/lib/systemd/backlight", 0755);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m");
+ return EXIT_FAILURE;
+ }
+
+ udev = udev_new();
+ if (!udev) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ sysname = strchr(argv[2], ':');
+ if (!sysname) {
+ log_error("Requires a subsystem and sysname pair specifying a backlight device.");
+ return EXIT_FAILURE;
+ }
+
+ ss = strndup(argv[2], sysname - argv[2]);
+ if (!ss) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ sysname++;
+
+ if (!streq(ss, "backlight") && !streq(ss, "leds")) {
+ log_error("Not a backlight or LED device: '%s:%s'", ss, sysname);
+ return EXIT_FAILURE;
+ }
+
+ errno = 0;
+ device = udev_device_new_from_subsystem_sysname(udev, ss, sysname);
+ if (!device) {
+ if (errno > 0)
+ log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname);
+ else
+ log_oom();
+
+ return EXIT_FAILURE;
+ }
+
+ /* If max_brightness is 0, then there is no actual backlight
+ * device. This happens on desktops with Asus mainboards
+ * that load the eeepc-wmi module.
+ */
+ max_brightness = get_max_brightness(device);
+ if (max_brightness == 0)
+ return EXIT_SUCCESS;
+
+ escaped_ss = cescape(ss);
+ if (!escaped_ss) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ escaped_sysname = cescape(sysname);
+ if (!escaped_sysname) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ path_id = udev_device_get_property_value(device, "ID_PATH");
+ if (path_id) {
+ escaped_path_id = cescape(path_id);
+ if (!escaped_path_id) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL);
+ } else
+ saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL);
+
+ if (!saved) {
+ log_oom();
+ return EXIT_FAILURE;
+ }
+
+ /* If there are multiple conflicting backlight devices, then
+ * their probing at boot-time might happen in any order. This
+ * means the validity checking of the device then is not
+ * reliable, since it might not see other devices conflicting
+ * with a specific backlight. To deal with this, we will
+ * actively delete backlight state files at shutdown (where
+ * device probing should be complete), so that the validity
+ * check at boot time doesn't have to be reliable. */
+
+ if (streq(argv[1], "load")) {
+ _cleanup_free_ char *value = NULL;
+ const char *clamp;
+
+ if (shall_restore_state() == 0)
+ return EXIT_SUCCESS;
+
+ if (!validate_device(udev, device))
+ return EXIT_SUCCESS;
+
+ r = read_one_line_file(saved, &value);
+ if (r < 0) {
+
+ if (r == -ENOENT)
+ return EXIT_SUCCESS;
+
+ log_error_errno(r, "Failed to read %s: %m", saved);
+ return EXIT_FAILURE;
+ }
+
+ clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP");
+ if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */
+ clamp_brightness(device, &value, max_brightness);
+
+ r = udev_device_set_sysattr_value(device, "brightness", value);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write system 'brightness' attribute: %m");
+ return EXIT_FAILURE;
+ }
+
+ } else if (streq(argv[1], "save")) {
+ const char *value;
+
+ if (!validate_device(udev, device)) {
+ unlink(saved);
+ return EXIT_SUCCESS;
+ }
+
+ value = udev_device_get_sysattr_value(device, "brightness");
+ if (!value) {
+ log_error("Failed to read system 'brightness' attribute");
+ return EXIT_FAILURE;
+ }
+
+ r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE);
+ if (r < 0) {
+ log_error_errno(r, "Failed to write %s: %m", saved);
+ return EXIT_FAILURE;
+ }
+
+ } else {
+ log_error("Unknown verb %s.", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-backlight/systemd-backlight@.service.in b/src/grp-initprogs/systemd-backlight/systemd-backlight@.service.in
new file mode 100644
index 0000000000..5e6706c11c
--- /dev/null
+++ b/src/grp-initprogs/systemd-backlight/systemd-backlight@.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=Load/Save Screen Backlight Brightness of %i
+Documentation=man:systemd-backlight@.service(8)
+DefaultDependencies=no
+RequiresMountsFor=/var/lib/systemd/backlight
+Conflicts=shutdown.target
+After=systemd-remount-fs.service
+Before=sysinit.target shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-backlight load %i
+ExecStop=@rootlibexecdir@/systemd-backlight save %i
+TimeoutSec=90s
diff --git a/src/grp-initprogs/systemd-binfmt/binfmt.c b/src/grp-initprogs/systemd-binfmt/binfmt.c
new file mode 100644
index 0000000000..eeef04fb1c
--- /dev/null
+++ b/src/grp-initprogs/systemd-binfmt/binfmt.c
@@ -0,0 +1,203 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "def.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+static const char conf_file_dirs[] = CONF_PATHS_NULSTR("binfmt.d");
+
+static int delete_rule(const char *rule) {
+ _cleanup_free_ char *x = NULL, *fn = NULL;
+ char *e;
+
+ assert(rule[0]);
+
+ x = strdup(rule);
+ if (!x)
+ return log_oom();
+
+ e = strchrnul(x+1, x[0]);
+ *e = 0;
+
+ fn = strappend("/proc/sys/fs/binfmt_misc/", x+1);
+ if (!fn)
+ return log_oom();
+
+ return write_string_file(fn, "-1", 0);
+}
+
+static int apply_rule(const char *rule) {
+ int r;
+
+ delete_rule(rule);
+
+ r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add binary format: %m");
+
+ return 0;
+}
+
+static int apply_file(const char *path, bool ignore_enoent) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(path);
+
+ r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f);
+ if (r < 0) {
+ if (ignore_enoent && r == -ENOENT)
+ return 0;
+
+ return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
+ }
+
+ log_debug("apply: %s", path);
+ for (;;) {
+ char l[LINE_MAX], *p;
+ int k;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path);
+ }
+
+ p = strstrip(l);
+ if (!*p)
+ continue;
+ if (strchr(COMMENTS "\n", *p))
+ continue;
+
+ k = apply_rule(p);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+ "Registers binary formats.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int c;
+
+ 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 '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int r, k;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = 0;
+
+ if (argc > optind) {
+ int i;
+
+ for (i = optind; i < argc; i++) {
+ k = apply_file(argv[i], false);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ } else {
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+
+ r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
+ if (r < 0) {
+ log_error_errno(r, "Failed to enumerate binfmt.d files: %m");
+ goto finish;
+ }
+
+ /* Flush out all rules */
+ write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0);
+
+ STRV_FOREACH(f, files) {
+ k = apply_file(*f, true);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ }
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-binfmt/systemd-binfmt.service.in b/src/grp-initprogs/systemd-binfmt/systemd-binfmt.service.in
new file mode 100644
index 0000000000..d53073ee61
--- /dev/null
+++ b/src/grp-initprogs/systemd-binfmt/systemd-binfmt.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=Set Up Additional Binary Formats
+Documentation=man:systemd-binfmt.service(8) man:binfmt.d(5)
+Documentation=https://www.kernel.org/doc/Documentation/binfmt_misc.txt
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=proc-sys-fs-binfmt_misc.automount
+Before=sysinit.target shutdown.target
+ConditionPathIsReadWrite=/proc/sys/
+ConditionDirectoryNotEmpty=|/lib/binfmt.d
+ConditionDirectoryNotEmpty=|/usr/lib/binfmt.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/binfmt.d
+ConditionDirectoryNotEmpty=|/etc/binfmt.d
+ConditionDirectoryNotEmpty=|/run/binfmt.d
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-binfmt
+TimeoutSec=90s
diff --git a/src/grp-initprogs/systemd-detect-virt/detect-virt.c b/src/grp-initprogs/systemd-detect-virt/detect-virt.c
new file mode 100644
index 0000000000..5d51589a31
--- /dev/null
+++ b/src/grp-initprogs/systemd-detect-virt/detect-virt.c
@@ -0,0 +1,169 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "util.h"
+#include "virt.h"
+
+static bool arg_quiet = false;
+static enum {
+ ANY_VIRTUALIZATION,
+ ONLY_VM,
+ ONLY_CONTAINER,
+ ONLY_CHROOT,
+} arg_mode = ANY_VIRTUALIZATION;
+
+static void help(void) {
+ printf("%s [OPTIONS...]\n\n"
+ "Detect execution in a virtualized environment.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " -c --container Only detect whether we are run in a container\n"
+ " -v --vm Only detect whether we are run in a VM\n"
+ " -r --chroot Detect whether we are run in a chroot() environment\n"
+ " -q --quiet Don't output anything, just set return value\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "container", no_argument, NULL, 'c' },
+ { "vm", no_argument, NULL, 'v' },
+ { "chroot", no_argument, NULL, 'r' },
+ { "quiet", no_argument, NULL, 'q' },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hqcvr", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case 'c':
+ arg_mode = ONLY_CONTAINER;
+ break;
+
+ case 'v':
+ arg_mode = ONLY_VM;
+ break;
+
+ case 'r':
+ arg_mode = ONLY_CHROOT;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ if (optind < argc) {
+ log_error("%s takes no arguments.", program_invocation_short_name);
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ /* This is mostly intended to be used for scripts which want
+ * to detect whether we are being run in a virtualized
+ * environment or not */
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ switch (arg_mode) {
+
+ case ONLY_VM:
+ r = detect_vm();
+ if (r < 0) {
+ log_error_errno(r, "Failed to check for VM: %m");
+ return EXIT_FAILURE;
+ }
+
+ break;
+
+ case ONLY_CONTAINER:
+ r = detect_container();
+ if (r < 0) {
+ log_error_errno(r, "Failed to check for container: %m");
+ return EXIT_FAILURE;
+ }
+
+ break;
+
+ case ONLY_CHROOT:
+ r = running_in_chroot();
+ if (r < 0) {
+ log_error_errno(r, "Failed to check for chroot() environment: %m");
+ return EXIT_FAILURE;
+ }
+
+ return r ? EXIT_SUCCESS : EXIT_FAILURE;
+
+ case ANY_VIRTUALIZATION:
+ default:
+ r = detect_virtualization();
+ if (r < 0) {
+ log_error_errno(r, "Failed to check for virtualization: %m");
+ return EXIT_FAILURE;
+ }
+
+ break;
+ }
+
+ if (!arg_quiet)
+ puts(virtualization_to_string(r));
+
+ return r != VIRTUALIZATION_NONE ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.bash b/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.bash
new file mode 100644
index 0000000000..df06c29841
--- /dev/null
+++ b/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.bash
@@ -0,0 +1,40 @@
+# systemd-detect-virt(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_detect_virt() {
+ local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+ local i verb comps
+
+ local -A OPTS=(
+ [STANDALONE]='-h --help --version -c --container -v --vm -q --quiet'
+ )
+
+ _init_completion || return
+
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+}
+
+complete -F _systemd_detect_virt systemd-detect-virt
diff --git a/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.zsh b/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.zsh
new file mode 100644
index 0000000000..a0c7df727c
--- /dev/null
+++ b/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.completion.zsh
@@ -0,0 +1,11 @@
+#compdef systemd-detect-virt
+
+local curcontext="$curcontext" state lstate line
+_arguments \
+ {-h,--help}'[Show this help]' \
+ '--version[Show package version]' \
+ {-c,--container}'[Only detect whether we are run in a container]' \
+ {-v,--vm}'[Only detect whether we are run in a VM]' \
+ {-q,--quiet}"[Don't output anything, just set return value]"
+
+#vim: set ft=zsh sw=4 ts=4 et
diff --git a/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.xml b/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.xml
new file mode 100644
index 0000000000..2b7f4e69ab
--- /dev/null
+++ b/src/grp-initprogs/systemd-detect-virt/systemd-detect-virt.xml
@@ -0,0 +1,245 @@
+<?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-detect-virt"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-detect-virt</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-detect-virt</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-detect-virt</refname>
+ <refpurpose>Detect execution in a virtualized environment</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-detect-virt <arg choice="opt" rep="repeat">OPTIONS</arg></command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-detect-virt</command> detects execution in
+ a virtualized environment. It identifies the virtualization
+ technology and can distinguish full machine virtualization from
+ container virtualization. <filename>systemd-detect-virt</filename>
+ exits with a return value of 0 (success) if a virtualization
+ technology is detected, and non-zero (error) otherwise. By default,
+ any type of virtualization is detected, and the options
+ <option>--container</option> and <option>--vm</option> can be used
+ to limit what types of virtualization are detected.</para>
+
+ <para>When executed without <option>--quiet</option> will print a
+ short identifier for the detected virtualization technology. The
+ following technologies are currently identified:</para>
+
+ <table>
+ <title>Known virtualization technologies (both
+ VM, i.e. full hardware virtualization,
+ and container, i.e. shared kernel virtualization)</title>
+ <tgroup cols='3' align='left' colsep='1' rowsep='1'>
+ <colspec colname="type" />
+ <colspec colname="id" />
+ <colspec colname="product" />
+ <thead>
+ <row>
+ <entry>Type</entry>
+ <entry>ID</entry>
+ <entry>Product</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry valign="top" morerows="9">VM</entry>
+ <entry><varname>qemu</varname></entry>
+ <entry>QEMU software virtualization</entry>
+ </row>
+
+ <row>
+ <entry><varname>kvm</varname></entry>
+ <entry>Linux KVM kernel virtual machine</entry>
+ </row>
+
+ <row>
+ <entry><varname>zvm</varname></entry>
+ <entry>s390 z/VM</entry>
+ </row>
+
+ <row>
+ <entry><varname>vmware</varname></entry>
+ <entry>VMware Workstation or Server, and related products</entry>
+ </row>
+
+ <row>
+ <entry><varname>microsoft</varname></entry>
+ <entry>Hyper-V, also known as Viridian or Windows Server Virtualization</entry>
+ </row>
+
+ <row>
+ <entry><varname>oracle</varname></entry>
+ <entry>Oracle VM VirtualBox (historically marketed by innotek and Sun Microsystems)</entry>
+ </row>
+
+ <row>
+ <entry><varname>xen</varname></entry>
+ <entry>Xen hypervisor (only domU, not dom0)</entry>
+ </row>
+
+ <row>
+ <entry><varname>bochs</varname></entry>
+ <entry>Bochs Emulator</entry>
+ </row>
+
+ <row>
+ <entry><varname>uml</varname></entry>
+ <entry>User-mode Linux</entry>
+ </row>
+
+ <row>
+ <entry><varname>parallels</varname></entry>
+ <entry>Parallels Desktop, Parallels Server</entry>
+ </row>
+
+ <row>
+ <entry valign="top" morerows="5">Container</entry>
+ <entry><varname>openvz</varname></entry>
+ <entry>OpenVZ/Virtuozzo</entry>
+ </row>
+
+ <row>
+ <entry><varname>lxc</varname></entry>
+ <entry>Linux container implementation by LXC</entry>
+ </row>
+
+ <row>
+ <entry><varname>lxc-libvirt</varname></entry>
+ <entry>Linux container implementation by libvirt</entry>
+ </row>
+
+ <row>
+ <entry><varname>systemd-nspawn</varname></entry>
+ <entry>systemd's minimal container implementation, see <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry></entry>
+ </row>
+
+ <row>
+ <entry><varname>docker</varname></entry>
+ <entry>Docker container manager</entry>
+ </row>
+
+ <row>
+ <entry><varname>rkt</varname></entry>
+ <entry>rkt app container runtime</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>If multiple virtualization solutions are used, only the
+ "innermost" is detected and identified. That means if both
+ machine and container virtualization are used in
+ conjunction, only the latter will be identified (unless
+ <option>--vm</option> is passed).</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--container</option></term>
+
+ <listitem><para>Only detects container virtualization (i.e.
+ shared kernel virtualization).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-v</option></term>
+ <term><option>--vm</option></term>
+
+ <listitem><para>Only detects hardware virtualization).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--chroot</option></term>
+
+ <listitem><para>Detect whether invoked in a
+ <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+ environment. In this mode, no output is written, but the return
+ value indicates whether the process was invoked in a
+ <function>chroot()</function>
+ environment or not.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--quiet</option></term>
+
+ <listitem><para>Suppress output of the virtualization
+ technology identifier.</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>If a virtualization technology is detected, 0 is returned, a
+ non-zero code otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>chroot</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-initprogs/systemd-firstboot/firstboot.c b/src/grp-initprogs/systemd-firstboot/firstboot.c
new file mode 100644
index 0000000000..1e1a592b7c
--- /dev/null
+++ b/src/grp-initprogs/systemd-firstboot/firstboot.c
@@ -0,0 +1,870 @@
+/***
+ 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 <fcntl.h>
+#include <getopt.h>
+#include <shadow.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "copy.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "hostname-util.h"
+#include "locale-util.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "random-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "time-util.h"
+#include "umask-util.h"
+#include "user-util.h"
+
+static char *arg_root = NULL;
+static char *arg_locale = NULL; /* $LANG */
+static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
+static char *arg_timezone = NULL;
+static char *arg_hostname = NULL;
+static sd_id128_t arg_machine_id = {};
+static char *arg_root_password = NULL;
+static bool arg_prompt_locale = false;
+static bool arg_prompt_timezone = false;
+static bool arg_prompt_hostname = false;
+static bool arg_prompt_root_password = false;
+static bool arg_copy_locale = false;
+static bool arg_copy_timezone = false;
+static bool arg_copy_root_password = false;
+
+static bool press_any_key(void) {
+ char k = 0;
+ bool need_nl = true;
+
+ printf("-- Press any key to proceed --");
+ fflush(stdout);
+
+ (void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
+
+ if (need_nl)
+ putchar('\n');
+
+ return k != 'q';
+}
+
+static void print_welcome(void) {
+ _cleanup_free_ char *pretty_name = NULL;
+ const char *os_release = NULL;
+ static bool done = false;
+ int r;
+
+ if (done)
+ return;
+
+ os_release = prefix_roota(arg_root, "/etc/os-release");
+ r = parse_env_file(os_release, NEWLINE,
+ "PRETTY_NAME", &pretty_name,
+ NULL);
+ if (r == -ENOENT) {
+
+ os_release = prefix_roota(arg_root, "/usr/lib/os-release");
+ r = parse_env_file(os_release, NEWLINE,
+ "PRETTY_NAME", &pretty_name,
+ NULL);
+ }
+
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to read os-release file: %m");
+
+ printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
+ isempty(pretty_name) ? "GNU/Linux" : pretty_name);
+
+ press_any_key();
+
+ done = true;
+}
+
+static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) {
+ unsigned n, per_column, i, j;
+ unsigned break_lines, break_modulo;
+
+ assert(n_columns > 0);
+
+ n = strv_length(x);
+ per_column = (n + n_columns - 1) / n_columns;
+
+ break_lines = lines();
+ if (break_lines > 2)
+ break_lines--;
+
+ /* The first page gets two extra lines, since we want to show
+ * a title */
+ break_modulo = break_lines;
+ if (break_modulo > 3)
+ break_modulo -= 3;
+
+ for (i = 0; i < per_column; i++) {
+
+ for (j = 0; j < n_columns; j ++) {
+ _cleanup_free_ char *e = NULL;
+
+ if (j * per_column + i >= n)
+ break;
+
+ e = ellipsize(x[j * per_column + i], width, percentage);
+ if (!e)
+ return log_oom();
+
+ printf("%4u) %-*s", j * per_column + i + 1, width, e);
+ }
+
+ putchar('\n');
+
+ /* on the first screen we reserve 2 extra lines for the title */
+ if (i % break_lines == break_modulo) {
+ if (!press_any_key())
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int prompt_loop(const char *text, char **l, bool (*is_valid)(const char *name), char **ret) {
+ int r;
+
+ assert(text);
+ assert(is_valid);
+ assert(ret);
+
+ for (;;) {
+ _cleanup_free_ char *p = NULL;
+ unsigned u;
+
+ r = ask_string(&p, "%s %s (empty to skip): ", special_glyph(TRIANGULAR_BULLET), text);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query user: %m");
+
+ if (isempty(p)) {
+ log_warning("No data entered, skipping.");
+ return 0;
+ }
+
+ r = safe_atou(p, &u);
+ if (r >= 0) {
+ char *c;
+
+ if (u <= 0 || u > strv_length(l)) {
+ log_error("Specified entry number out of range.");
+ continue;
+ }
+
+ log_info("Selected '%s'.", l[u-1]);
+
+ c = strdup(l[u-1]);
+ if (!c)
+ return log_oom();
+
+ free(*ret);
+ *ret = c;
+ return 0;
+ }
+
+ if (!is_valid(p)) {
+ log_error("Entered data invalid.");
+ continue;
+ }
+
+ free(*ret);
+ *ret = p;
+ p = 0;
+ return 0;
+ }
+}
+
+static int prompt_locale(void) {
+ _cleanup_strv_free_ char **locales = NULL;
+ int r;
+
+ if (arg_locale || arg_locale_messages)
+ return 0;
+
+ if (!arg_prompt_locale)
+ return 0;
+
+ r = get_locales(&locales);
+ if (r < 0)
+ return log_error_errno(r, "Cannot query locales list: %m");
+
+ print_welcome();
+
+ printf("\nAvailable Locales:\n\n");
+ r = show_menu(locales, 3, 22, 60);
+ if (r < 0)
+ return r;
+
+ putchar('\n');
+
+ r = prompt_loop("Please enter system locale name or number", locales, locale_is_valid, &arg_locale);
+ if (r < 0)
+ return r;
+
+ if (isempty(arg_locale))
+ return 0;
+
+ r = prompt_loop("Please enter system message locale name or number", locales, locale_is_valid, &arg_locale_messages);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int process_locale(void) {
+ const char *etc_localeconf;
+ char* locales[3];
+ unsigned i = 0;
+ int r;
+
+ etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
+ if (laccess(etc_localeconf, F_OK) >= 0)
+ return 0;
+
+ if (arg_copy_locale && arg_root) {
+
+ mkdir_parents(etc_localeconf, 0755);
+ r = copy_file("/etc/locale.conf", etc_localeconf, 0, 0644, 0);
+ if (r != -ENOENT) {
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy %s: %m", etc_localeconf);
+
+ log_info("%s copied.", etc_localeconf);
+ return 0;
+ }
+ }
+
+ r = prompt_locale();
+ if (r < 0)
+ return r;
+
+ if (!isempty(arg_locale))
+ locales[i++] = strjoina("LANG=", arg_locale);
+ if (!isempty(arg_locale_messages) && !streq(arg_locale_messages, arg_locale))
+ locales[i++] = strjoina("LC_MESSAGES=", arg_locale_messages);
+
+ if (i == 0)
+ return 0;
+
+ locales[i] = NULL;
+
+ mkdir_parents(etc_localeconf, 0755);
+ r = write_env_file(etc_localeconf, locales);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write %s: %m", etc_localeconf);
+
+ log_info("%s written.", etc_localeconf);
+ return 0;
+}
+
+static int prompt_timezone(void) {
+ _cleanup_strv_free_ char **zones = NULL;
+ int r;
+
+ if (arg_timezone)
+ return 0;
+
+ if (!arg_prompt_timezone)
+ return 0;
+
+ r = get_timezones(&zones);
+ if (r < 0)
+ return log_error_errno(r, "Cannot query timezone list: %m");
+
+ print_welcome();
+
+ printf("\nAvailable Time Zones:\n\n");
+ r = show_menu(zones, 3, 22, 30);
+ if (r < 0)
+ return r;
+
+ putchar('\n');
+
+ r = prompt_loop("Please enter timezone name or number", zones, timezone_is_valid, &arg_timezone);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int process_timezone(void) {
+ const char *etc_localtime, *e;
+ int r;
+
+ etc_localtime = prefix_roota(arg_root, "/etc/localtime");
+ if (laccess(etc_localtime, F_OK) >= 0)
+ return 0;
+
+ if (arg_copy_timezone && arg_root) {
+ _cleanup_free_ char *p = NULL;
+
+ r = readlink_malloc("/etc/localtime", &p);
+ if (r != -ENOENT) {
+ if (r < 0)
+ return log_error_errno(r, "Failed to read host timezone: %m");
+
+ mkdir_parents(etc_localtime, 0755);
+ if (symlink(p, etc_localtime) < 0)
+ return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
+
+ log_info("%s copied.", etc_localtime);
+ return 0;
+ }
+ }
+
+ r = prompt_timezone();
+ if (r < 0)
+ return r;
+
+ if (isempty(arg_timezone))
+ return 0;
+
+ e = strjoina("../usr/share/zoneinfo/", arg_timezone);
+
+ mkdir_parents(etc_localtime, 0755);
+ if (symlink(e, etc_localtime) < 0)
+ return log_error_errno(errno, "Failed to create %s symlink: %m", etc_localtime);
+
+ log_info("%s written", etc_localtime);
+ return 0;
+}
+
+static int prompt_hostname(void) {
+ int r;
+
+ if (arg_hostname)
+ return 0;
+
+ if (!arg_prompt_hostname)
+ return 0;
+
+ print_welcome();
+ putchar('\n');
+
+ for (;;) {
+ _cleanup_free_ char *h = NULL;
+
+ r = ask_string(&h, "%s Please enter hostname for new system (empty to skip): ", special_glyph(TRIANGULAR_BULLET));
+ if (r < 0)
+ return log_error_errno(r, "Failed to query hostname: %m");
+
+ if (isempty(h)) {
+ log_warning("No hostname entered, skipping.");
+ break;
+ }
+
+ if (!hostname_is_valid(h, true)) {
+ log_error("Specified hostname invalid.");
+ continue;
+ }
+
+ /* Get rid of the trailing dot that we allow, but don't want to see */
+ arg_hostname = hostname_cleanup(h);
+ h = NULL;
+ break;
+ }
+
+ return 0;
+}
+
+static int process_hostname(void) {
+ const char *etc_hostname;
+ int r;
+
+ etc_hostname = prefix_roota(arg_root, "/etc/hostname");
+ if (laccess(etc_hostname, F_OK) >= 0)
+ return 0;
+
+ r = prompt_hostname();
+ if (r < 0)
+ return r;
+
+ if (isempty(arg_hostname))
+ return 0;
+
+ mkdir_parents(etc_hostname, 0755);
+ r = write_string_file(etc_hostname, arg_hostname, WRITE_STRING_FILE_CREATE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write %s: %m", etc_hostname);
+
+ log_info("%s written.", etc_hostname);
+ return 0;
+}
+
+static int process_machine_id(void) {
+ const char *etc_machine_id;
+ char id[SD_ID128_STRING_MAX];
+ int r;
+
+ etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
+ if (laccess(etc_machine_id, F_OK) >= 0)
+ return 0;
+
+ if (sd_id128_equal(arg_machine_id, SD_ID128_NULL))
+ return 0;
+
+ mkdir_parents(etc_machine_id, 0755);
+ r = write_string_file(etc_machine_id, sd_id128_to_string(arg_machine_id, id), WRITE_STRING_FILE_CREATE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write machine id: %m");
+
+ log_info("%s written.", etc_machine_id);
+ return 0;
+}
+
+static int prompt_root_password(void) {
+ const char *msg1, *msg2, *etc_shadow;
+ int r;
+
+ if (arg_root_password)
+ return 0;
+
+ if (!arg_prompt_root_password)
+ return 0;
+
+ etc_shadow = prefix_roota(arg_root, "/etc/shadow");
+ if (laccess(etc_shadow, F_OK) >= 0)
+ return 0;
+
+ print_welcome();
+ putchar('\n');
+
+ msg1 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
+ msg2 = strjoina(special_glyph(TRIANGULAR_BULLET), " Please enter new root password again: ");
+
+ for (;;) {
+ _cleanup_string_free_erase_ char *a = NULL, *b = NULL;
+
+ r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query root password: %m");
+
+ if (isempty(a)) {
+ log_warning("No password entered, skipping.");
+ break;
+ }
+
+ r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
+ if (r < 0)
+ return log_error_errno(r, "Failed to query root password: %m");
+
+ if (!streq(a, b)) {
+ log_error("Entered passwords did not match, please try again.");
+ continue;
+ }
+
+ arg_root_password = a;
+ a = NULL;
+ break;
+ }
+
+ return 0;
+}
+
+static int write_root_shadow(const char *path, const struct spwd *p) {
+ _cleanup_fclose_ FILE *f = NULL;
+ assert(path);
+ assert(p);
+
+ RUN_WITH_UMASK(0777)
+ f = fopen(path, "wex");
+ if (!f)
+ return -errno;
+
+ errno = 0;
+ if (putspent(p, f) != 0)
+ return errno > 0 ? -errno : -EIO;
+
+ return fflush_and_check(f);
+}
+
+static int process_root_password(void) {
+
+ static const char table[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "./";
+
+ struct spwd item = {
+ .sp_namp = (char*) "root",
+ .sp_min = -1,
+ .sp_max = -1,
+ .sp_warn = -1,
+ .sp_inact = -1,
+ .sp_expire = -1,
+ .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
+ };
+
+ _cleanup_close_ int lock = -1;
+ char salt[3+16+1+1];
+ uint8_t raw[16];
+ unsigned i;
+ char *j;
+
+ const char *etc_shadow;
+ int r;
+
+ etc_shadow = prefix_roota(arg_root, "/etc/shadow");
+ if (laccess(etc_shadow, F_OK) >= 0)
+ return 0;
+
+ mkdir_parents(etc_shadow, 0755);
+
+ lock = take_etc_passwd_lock(arg_root);
+ if (lock < 0)
+ return log_error_errno(lock, "Failed to take a lock: %m");
+
+ if (arg_copy_root_password && arg_root) {
+ struct spwd *p;
+
+ errno = 0;
+ p = getspnam("root");
+ if (p || errno != ENOENT) {
+ if (!p) {
+ if (!errno)
+ errno = EIO;
+
+ return log_error_errno(errno, "Failed to find shadow entry for root: %m");
+ }
+
+ r = write_root_shadow(etc_shadow, p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
+
+ log_info("%s copied.", etc_shadow);
+ return 0;
+ }
+ }
+
+ r = prompt_root_password();
+ if (r < 0)
+ return r;
+
+ if (!arg_root_password)
+ return 0;
+
+ r = dev_urandom(raw, 16);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get salt: %m");
+
+ /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
+ assert_cc(sizeof(table) == 64 + 1);
+ j = stpcpy(salt, "$6$");
+ for (i = 0; i < 16; i++)
+ j[i] = table[raw[i] & 63];
+ j[i++] = '$';
+ j[i] = 0;
+
+ errno = 0;
+ item.sp_pwdp = crypt(arg_root_password, salt);
+ if (!item.sp_pwdp) {
+ if (!errno)
+ errno = EINVAL;
+
+ return log_error_errno(errno, "Failed to encrypt password: %m");
+ }
+
+ item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
+
+ r = write_root_shadow(etc_shadow, &item);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write %s: %m", etc_shadow);
+
+ log_info("%s written.", etc_shadow);
+ return 0;
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...]\n\n"
+ "Configures basic settings of the system.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ " --locale=LOCALE Set primary locale (LANG=)\n"
+ " --locale-messages=LOCALE Set message locale (LC_MESSAGES=)\n"
+ " --timezone=TIMEZONE Set timezone\n"
+ " --hostname=NAME Set host name\n"
+ " --machine-ID=ID Set machine ID\n"
+ " --root-password=PASSWORD Set root password\n"
+ " --root-password-file=FILE Set root password from file\n"
+ " --prompt-locale Prompt the user for locale settings\n"
+ " --prompt-timezone Prompt the user for timezone\n"
+ " --prompt-hostname Prompt the user for hostname\n"
+ " --prompt-root-password Prompt the user for root password\n"
+ " --prompt Prompt for all of the above\n"
+ " --copy-locale Copy locale from host\n"
+ " --copy-timezone Copy timezone from host\n"
+ " --copy-root-password Copy root password from host\n"
+ " --copy Copy locale, timezone, root password\n"
+ " --setup-machine-id Generate a new random machine ID\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_ROOT,
+ ARG_LOCALE,
+ ARG_LOCALE_MESSAGES,
+ ARG_TIMEZONE,
+ ARG_HOSTNAME,
+ ARG_MACHINE_ID,
+ ARG_ROOT_PASSWORD,
+ ARG_ROOT_PASSWORD_FILE,
+ ARG_PROMPT,
+ ARG_PROMPT_LOCALE,
+ ARG_PROMPT_TIMEZONE,
+ ARG_PROMPT_HOSTNAME,
+ ARG_PROMPT_ROOT_PASSWORD,
+ ARG_COPY,
+ ARG_COPY_LOCALE,
+ ARG_COPY_TIMEZONE,
+ ARG_COPY_ROOT_PASSWORD,
+ ARG_SETUP_MACHINE_ID,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "locale", required_argument, NULL, ARG_LOCALE },
+ { "locale-messages", required_argument, NULL, ARG_LOCALE_MESSAGES },
+ { "timezone", required_argument, NULL, ARG_TIMEZONE },
+ { "hostname", required_argument, NULL, ARG_HOSTNAME },
+ { "machine-id", required_argument, NULL, ARG_MACHINE_ID },
+ { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD },
+ { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE },
+ { "prompt", no_argument, NULL, ARG_PROMPT },
+ { "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
+ { "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
+ { "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
+ { "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
+ { "copy", no_argument, NULL, ARG_COPY },
+ { "copy-locale", no_argument, NULL, ARG_COPY_LOCALE },
+ { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE },
+ { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD },
+ { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID },
+ {}
+ };
+
+ int r, c;
+
+ 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_ROOT:
+ r = parse_path_argument_and_warn(optarg, true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_LOCALE:
+ if (!locale_is_valid(optarg)) {
+ log_error("Locale %s is not valid.", optarg);
+ return -EINVAL;
+ }
+
+ r = free_and_strdup(&arg_locale, optarg);
+ if (r < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_LOCALE_MESSAGES:
+ if (!locale_is_valid(optarg)) {
+ log_error("Locale %s is not valid.", optarg);
+ return -EINVAL;
+ }
+
+ r = free_and_strdup(&arg_locale_messages, optarg);
+ if (r < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_TIMEZONE:
+ if (!timezone_is_valid(optarg)) {
+ log_error("Timezone %s is not valid.", optarg);
+ return -EINVAL;
+ }
+
+ r = free_and_strdup(&arg_timezone, optarg);
+ if (r < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_ROOT_PASSWORD:
+ r = free_and_strdup(&arg_root_password, optarg);
+ if (r < 0)
+ return log_oom();
+ break;
+
+ case ARG_ROOT_PASSWORD_FILE:
+ arg_root_password = mfree(arg_root_password);
+
+ r = read_one_line_file(optarg, &arg_root_password);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", optarg);
+
+ break;
+
+ case ARG_HOSTNAME:
+ if (!hostname_is_valid(optarg, true)) {
+ log_error("Host name %s is not valid.", optarg);
+ return -EINVAL;
+ }
+
+ hostname_cleanup(optarg);
+ r = free_and_strdup(&arg_hostname, optarg);
+ if (r < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_MACHINE_ID:
+ if (sd_id128_from_string(optarg, &arg_machine_id) < 0) {
+ log_error("Failed to parse machine id %s.", optarg);
+ return -EINVAL;
+ }
+
+ break;
+
+ case ARG_PROMPT:
+ arg_prompt_locale = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = true;
+ break;
+
+ case ARG_PROMPT_LOCALE:
+ arg_prompt_locale = true;
+ break;
+
+ case ARG_PROMPT_TIMEZONE:
+ arg_prompt_timezone = true;
+ break;
+
+ case ARG_PROMPT_HOSTNAME:
+ arg_prompt_hostname = true;
+ break;
+
+ case ARG_PROMPT_ROOT_PASSWORD:
+ arg_prompt_root_password = true;
+ break;
+
+ case ARG_COPY:
+ arg_copy_locale = arg_copy_timezone = arg_copy_root_password = true;
+ break;
+
+ case ARG_COPY_LOCALE:
+ arg_copy_locale = true;
+ break;
+
+ case ARG_COPY_TIMEZONE:
+ arg_copy_timezone = true;
+ break;
+
+ case ARG_COPY_ROOT_PASSWORD:
+ arg_copy_root_password = true;
+ break;
+
+ case ARG_SETUP_MACHINE_ID:
+
+ r = sd_id128_randomize(&arg_machine_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate randomized machine ID: %m");
+
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = process_locale();
+ if (r < 0)
+ goto finish;
+
+ r = process_timezone();
+ if (r < 0)
+ goto finish;
+
+ r = process_hostname();
+ if (r < 0)
+ goto finish;
+
+ r = process_machine_id();
+ if (r < 0)
+ goto finish;
+
+ r = process_root_password();
+ if (r < 0)
+ goto finish;
+
+finish:
+ free(arg_root);
+ free(arg_locale);
+ free(arg_locale_messages);
+ free(arg_timezone);
+ free(arg_hostname);
+ string_erase(arg_root_password);
+ free(arg_root_password);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-firstboot/systemd-firstboot.service.in b/src/grp-initprogs/systemd-firstboot/systemd-firstboot.service.in
new file mode 100644
index 0000000000..405c6f3fd2
--- /dev/null
+++ b/src/grp-initprogs/systemd-firstboot/systemd-firstboot.service.in
@@ -0,0 +1,24 @@
+# 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=First Boot Wizard
+Documentation=man:systemd-firstboot(1)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-remount-fs.service
+Before=systemd-sysusers.service sysinit.target shutdown.target
+ConditionPathIsReadWrite=/etc
+ConditionFirstBoot=yes
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/systemd-firstboot --prompt-locale --prompt-timezone --prompt-root-password
+StandardOutput=tty
+StandardInput=tty
+StandardError=tty
diff --git a/src/grp-initprogs/systemd-firstboot/systemd-firstboot.xml b/src/grp-initprogs/systemd-firstboot/systemd-firstboot.xml
new file mode 100644
index 0000000000..b269e48113
--- /dev/null
+++ b/src/grp-initprogs/systemd-firstboot/systemd-firstboot.xml
@@ -0,0 +1,259 @@
+<?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 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/>.
+-->
+
+<refentry id="systemd-firstboot" conditional='ENABLE_FIRSTBOOT'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-firstboot</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-firstboot</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-firstboot</refname>
+ <refname>systemd-firstboot.service</refname>
+ <refpurpose>Initialize basic system settings on or before the first boot-up of a system</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-firstboot</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ </cmdsynopsis>
+
+ <para><filename>systemd-firstboot.service</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-firstboot</command> initializes the most
+ basic system settings interactively on the first boot, or
+ optionally non-interactively when a system image is created. The
+ following settings may be set up:</para>
+
+ <itemizedlist>
+ <listitem><para>The system locale, more specifically the two
+ locale variables <varname>LANG=</varname> and
+ <varname>LC_MESSAGES</varname></para></listitem>
+
+ <listitem><para>The system time zone</para></listitem>
+
+ <listitem><para>The system host name</para></listitem>
+
+ <listitem><para>The machine ID of the system</para></listitem>
+
+ <listitem><para>The root user's password</para></listitem>
+ </itemizedlist>
+
+ <para>Each of the fields may either be queried interactively by
+ users, set non-interactively on the tool's command line, or be
+ copied from a host system that is used to set up the system
+ image.</para>
+
+ <para>If a setting is already initialized, it will not be
+ overwritten and the user will not be prompted for the
+ setting.</para>
+
+ <para>Note that this tool operates directly on the file system and
+ does not involve any running system services, unlike
+ <citerefentry project='man-pages'><refentrytitle>localectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>timedatectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ or
+ <citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+ This allows <command>systemd-firstboot</command> to operate on
+ mounted but not booted disk images and in early boot. It is not
+ recommended to use <command>systemd-firstboot</command> on the
+ running system while it is up.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--root=<replaceable>root</replaceable></option></term>
+ <listitem><para>Takes a directory path as an argument. All
+ paths will be prefixed with the given alternate
+ <replaceable>root</replaceable> path, including config search
+ paths. This is useful to operate on a system image mounted to
+ the specified directory instead of the host system itself.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--locale=<replaceable>LOCALE</replaceable></option></term>
+ <term><option>--locale-messages=<replaceable>LOCALE</replaceable></option></term>
+
+ <listitem><para>Sets the system locale, more specifically the
+ <varname>LANG=</varname> and <varname>LC_MESSAGES</varname>
+ settings. The argument should be a valid locale identifier,
+ such as <literal>de_DE.UTF-8</literal>. This controls the
+ <citerefentry project='man-pages'><refentrytitle>locale.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ configuration file.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--timezone=<replaceable>TIMEZONE</replaceable></option></term>
+
+ <listitem><para>Sets the system time zone. The argument should
+ be a valid time zone identifier, such as
+ <literal>Europe/Berlin</literal>. This controls the
+ <citerefentry><refentrytitle>localtime</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ symlink.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--hostname=<replaceable>HOSTNAME</replaceable></option></term>
+
+ <listitem><para>Sets the system hostname. The argument should
+ be a host name, compatible with DNS. This controls the
+ <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ configuration file.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--machine-id=<replaceable>ID</replaceable></option></term>
+
+ <listitem><para>Sets the system's machine ID. This controls
+ the
+ <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ file.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--root-password=<replaceable>PASSWORD</replaceable></option></term>
+ <term><option>--root-password-file=<replaceable>PATH</replaceable></option></term>
+
+ <listitem><para>Sets the password of the system's root user.
+ This creates a
+ <citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ file. This setting exists in two forms:
+ <option>--root-password=</option> accepts the password to set
+ directly on the command line, and
+ <option>--root-password-file=</option> reads it from a file.
+ Note that it is not recommended to specify passwords on the
+ command line, as other users might be able to see them simply
+ by invoking
+ <citerefentry project='die-net'><refentrytitle>ps</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--prompt-locale</option></term>
+ <term><option>--prompt-timezone</option></term>
+ <term><option>--prompt-hostname</option></term>
+ <term><option>--prompt-root-password</option></term>
+
+ <listitem><para>Prompt the user interactively for a specific
+ basic setting. Note that any explicit configuration settings
+ specified on the command line take precedence, and the user is
+ not prompted for it.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--prompt</option></term>
+
+ <listitem><para>Query the user for locale, timezone, hostname
+ and root password. This is equivalent to specifying
+ <option>--prompt-locale</option>,
+ <option>--prompt-timezone</option>,
+ <option>--prompt-hostname</option>,
+ <option>--prompt-root-password</option> in combination.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--copy-locale</option></term>
+ <term><option>--copy-timezone</option></term>
+ <term><option>--copy-root-password</option></term>
+
+ <listitem><para>Copy a specific basic setting from the host.
+ This only works in combination with <option>--root=</option>
+ (see above).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--copy</option></term>
+
+ <listitem><para>Copy locale, time zone and root password from
+ the host. This is equivalent to specifying
+ <option>--copy-locale</option>,
+ <option>--copy-timezone</option>,
+ <option>--copy-root-password</option> in combination.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--setup-machine-id</option></term>
+
+ <listitem><para>Initialize the system's machine ID to a random
+ ID. This only works in combination with
+ <option>--root=</option>.</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, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry project='man-pages'><refentrytitle>locale.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>localtime</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry project='die-net'><refentrytitle>shadow</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-machine-id-setup</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry project='man-pages'><refentrytitle>localectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>timedatectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-initprogs/systemd-fsck/fsck.c b/src/grp-initprogs/systemd-fsck/fsck.c
new file mode 100644
index 0000000000..d7f0829ffc
--- /dev/null
+++ b/src/grp-initprogs/systemd-fsck/fsck.c
@@ -0,0 +1,487 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2014 Holger Hans Peter Freyther
+
+ 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 <stdbool.h>
+#include <stdio.h>
+#include <sys/file.h>
+#include <sys/prctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-device.h>
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "device-util.h"
+#include "fd-util.h"
+#include "fs-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "proc-cmdline.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "socket-util.h"
+#include "special.h"
+#include "stdio-util.h"
+#include "util.h"
+
+/* exit codes as defined in fsck(8) */
+enum {
+ FSCK_SUCCESS = 0,
+ FSCK_ERROR_CORRECTED = 1,
+ FSCK_SYSTEM_SHOULD_REBOOT = 2,
+ FSCK_ERRORS_LEFT_UNCORRECTED = 4,
+ FSCK_OPERATIONAL_ERROR = 8,
+ FSCK_USAGE_OR_SYNTAX_ERROR = 16,
+ FSCK_USER_CANCELLED = 32,
+ FSCK_SHARED_LIB_ERROR = 128,
+};
+
+static bool arg_skip = false;
+static bool arg_force = false;
+static bool arg_show_progress = false;
+static const char *arg_repair = "-a";
+
+static void start_target(const char *target, const char *mode) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ assert(target);
+
+ r = bus_connect_system_systemd(&bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get D-Bus connection: %m");
+ return;
+ }
+
+ log_info("Running request %s/start/replace", target);
+
+ /* Start these units only if we can replace base.target with it */
+ r = sd_bus_call_method(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "StartUnitReplace",
+ &error,
+ NULL,
+ "sss", "basic.target", target, mode);
+
+ /* Don't print a warning if we aren't called during startup */
+ if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
+ log_error("Failed to start unit: %s", bus_error_message(&error, r));
+}
+
+static int parse_proc_cmdline_item(const char *key, const char *value) {
+ int r;
+
+ assert(key);
+
+ if (streq(key, "fsck.mode") && value) {
+
+ if (streq(value, "auto"))
+ arg_force = arg_skip = false;
+ else if (streq(value, "force"))
+ arg_force = true;
+ else if (streq(value, "skip"))
+ arg_skip = true;
+ else
+ log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value);
+
+ } else if (streq(key, "fsck.repair") && value) {
+
+ if (streq(value, "preen"))
+ arg_repair = "-a";
+ else {
+ r = parse_boolean(value);
+ if (r > 0)
+ arg_repair = "-y";
+ else if (r == 0)
+ arg_repair = "-n";
+ else
+ log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value);
+ }
+ }
+
+#ifdef HAVE_SYSV_COMPAT
+ else if (streq(key, "fastboot") && !value) {
+ log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
+ arg_skip = true;
+
+ } else if (streq(key, "forcefsck") && !value) {
+ log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
+ arg_force = true;
+ }
+#endif
+
+ return 0;
+}
+
+static void test_files(void) {
+
+#ifdef HAVE_SYSV_COMPAT
+ if (access("/fastboot", F_OK) >= 0) {
+ log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
+ arg_skip = true;
+ }
+
+ if (access("/forcefsck", F_OK) >= 0) {
+ log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
+ arg_force = true;
+ }
+#endif
+
+ arg_show_progress = access("/run/systemd/show-status", F_OK) >= 0;
+}
+
+static double percent(int pass, unsigned long cur, unsigned long max) {
+ /* Values stolen from e2fsck */
+
+ static const int pass_table[] = {
+ 0, 70, 90, 92, 95, 100
+ };
+
+ if (pass <= 0)
+ return 0.0;
+
+ if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
+ return 100.0;
+
+ return (double) pass_table[pass-1] +
+ ((double) pass_table[pass] - (double) pass_table[pass-1]) *
+ (double) cur / (double) max;
+}
+
+static int process_progress(int fd) {
+ _cleanup_fclose_ FILE *console = NULL, *f = NULL;
+ usec_t last = 0;
+ bool locked = false;
+ int clear = 0, r;
+
+ /* No progress pipe to process? Then we are a NOP. */
+ if (fd < 0)
+ return 0;
+
+ f = fdopen(fd, "re");
+ if (!f) {
+ safe_close(fd);
+ return -errno;
+ }
+
+ console = fopen("/dev/console", "we");
+ if (!console)
+ return -ENOMEM;
+
+ for (;;) {
+ int pass, m;
+ unsigned long cur, max;
+ _cleanup_free_ char *device = NULL;
+ double p;
+ usec_t t;
+
+ if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4) {
+
+ if (ferror(f))
+ r = log_warning_errno(errno, "Failed to read from progress pipe: %m");
+ else if (feof(f))
+ r = 0;
+ else {
+ log_warning("Failed to parse progress pipe data");
+ r = -EBADMSG;
+ }
+ break;
+ }
+
+ /* Only show one progress counter at max */
+ if (!locked) {
+ if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0)
+ continue;
+
+ locked = true;
+ }
+
+ /* Only update once every 50ms */
+ t = now(CLOCK_MONOTONIC);
+ if (last + 50 * USEC_PER_MSEC > t)
+ continue;
+
+ last = t;
+
+ p = percent(pass, cur, max);
+ fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
+ fflush(console);
+
+ if (m > clear)
+ clear = m;
+ }
+
+ if (clear > 0) {
+ unsigned j;
+
+ fputc('\r', console);
+ for (j = 0; j < (unsigned) clear; j++)
+ fputc(' ', console);
+ fputc('\r', console);
+ fflush(console);
+ }
+
+ return r;
+}
+
+static int fsck_progress_socket(void) {
+ static const union sockaddr_union sa = {
+ .un.sun_family = AF_UNIX,
+ .un.sun_path = "/run/systemd/fsck.progress",
+ };
+
+ int fd, r;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return log_warning_errno(errno, "socket(): %m");
+
+ if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) {
+ r = log_full_errno(errno == ECONNREFUSED || errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
+ errno, "Failed to connect to progress socket %s, ignoring: %m", sa.un.sun_path);
+ safe_close(fd);
+ return r;
+ }
+
+ return fd;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
+ _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+ const char *device, *type;
+ bool root_directory;
+ siginfo_t status;
+ struct stat st;
+ int r;
+ pid_t pid;
+
+ if (argc > 2) {
+ log_error("This program expects one or no arguments.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = parse_proc_cmdline(parse_proc_cmdline_item);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ test_files();
+
+ if (!arg_force && arg_skip) {
+ r = 0;
+ goto finish;
+ }
+
+ if (argc > 1) {
+ device = argv[1];
+
+ if (stat(device, &st) < 0) {
+ r = log_error_errno(errno, "Failed to stat %s: %m", device);
+ goto finish;
+ }
+
+ if (!S_ISBLK(st.st_mode)) {
+ log_error("%s is not a block device.", device);
+ r = -EINVAL;
+ goto finish;
+ }
+
+ r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev);
+ if (r < 0) {
+ log_error_errno(r, "Failed to detect device %s: %m", device);
+ goto finish;
+ }
+
+ root_directory = false;
+ } else {
+ struct timespec times[2];
+
+ /* Find root device */
+
+ if (stat("/", &st) < 0) {
+ r = log_error_errno(errno, "Failed to stat() the root directory: %m");
+ goto finish;
+ }
+
+ /* Virtual root devices don't need an fsck */
+ if (major(st.st_dev) == 0) {
+ log_debug("Root directory is virtual or btrfs, skipping check.");
+ r = 0;
+ goto finish;
+ }
+
+ /* check if we are already writable */
+ times[0] = st.st_atim;
+ times[1] = st.st_mtim;
+
+ if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
+ log_info("Root directory is writable, skipping check.");
+ r = 0;
+ goto finish;
+ }
+
+ r = sd_device_new_from_devnum(&dev, 'b', st.st_dev);
+ if (r < 0) {
+ log_error_errno(r, "Failed to detect root device: %m");
+ goto finish;
+ }
+
+ r = sd_device_get_devname(dev, &device);
+ if (r < 0) {
+ log_error_errno(r, "Failed to detect device node of root directory: %m");
+ goto finish;
+ }
+
+ root_directory = true;
+ }
+
+ r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type);
+ if (r >= 0) {
+ r = fsck_exists(type);
+ if (r < 0)
+ log_warning_errno(r, "Couldn't detect if fsck.%s may be used for %s, proceeding: %m", type, device);
+ else if (r == 0) {
+ log_info("fsck.%s doesn't exist, not checking file system on %s.", type, device);
+ goto finish;
+ }
+ }
+
+ if (arg_show_progress) {
+ if (pipe(progress_pipe) < 0) {
+ r = log_error_errno(errno, "pipe(): %m");
+ goto finish;
+ }
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ r = log_error_errno(errno, "fork(): %m");
+ goto finish;
+ }
+ if (pid == 0) {
+ char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
+ int progress_socket = -1;
+ const char *cmdline[9];
+ int i = 0;
+
+ /* Child */
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+ /* Close the reading side of the progress pipe */
+ progress_pipe[0] = safe_close(progress_pipe[0]);
+
+ /* Try to connect to a progress management daemon, if there is one */
+ progress_socket = fsck_progress_socket();
+ if (progress_socket >= 0) {
+ /* If this worked we close the progress pipe early, and just use the socket */
+ progress_pipe[1] = safe_close(progress_pipe[1]);
+ xsprintf(dash_c, "-C%i", progress_socket);
+ } else if (progress_pipe[1] >= 0) {
+ /* Otherwise if we have the progress pipe to our own local handle, we use it */
+ xsprintf(dash_c, "-C%i", progress_pipe[1]);
+ } else
+ dash_c[0] = 0;
+
+ cmdline[i++] = "/sbin/fsck";
+ cmdline[i++] = arg_repair;
+ cmdline[i++] = "-T";
+
+ /*
+ * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
+ * The previous versions use flock for the device and conflict with
+ * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
+ */
+ cmdline[i++] = "-l";
+
+ if (!root_directory)
+ cmdline[i++] = "-M";
+
+ if (arg_force)
+ cmdline[i++] = "-f";
+
+ if (!isempty(dash_c))
+ cmdline[i++] = dash_c;
+
+ cmdline[i++] = device;
+ cmdline[i++] = NULL;
+
+ execv(cmdline[0], (char**) cmdline);
+ _exit(FSCK_OPERATIONAL_ERROR);
+ }
+
+ progress_pipe[1] = safe_close(progress_pipe[1]);
+ (void) process_progress(progress_pipe[0]);
+ progress_pipe[0] = -1;
+
+ r = wait_for_terminate(pid, &status);
+ if (r < 0) {
+ log_error_errno(r, "waitid(): %m");
+ goto finish;
+ }
+
+ if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
+
+ if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
+ log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
+ else if (status.si_code == CLD_EXITED)
+ log_error("fsck failed with error code %i.", status.si_status);
+ else
+ log_error("fsck failed due to unknown reason.");
+
+ r = -EINVAL;
+
+ if (status.si_code == CLD_EXITED && (status.si_status & FSCK_SYSTEM_SHOULD_REBOOT) && root_directory)
+ /* System should be rebooted. */
+ start_target(SPECIAL_REBOOT_TARGET, "replace-irreversibly");
+ else if (status.si_code == CLD_EXITED && (status.si_status & (FSCK_SYSTEM_SHOULD_REBOOT | FSCK_ERRORS_LEFT_UNCORRECTED)))
+ /* Some other problem */
+ start_target(SPECIAL_EMERGENCY_TARGET, "replace");
+ else {
+ log_warning("Ignoring error.");
+ r = 0;
+ }
+
+ } else
+ r = 0;
+
+ if (status.si_code == CLD_EXITED && (status.si_status & FSCK_ERROR_CORRECTED))
+ (void) touch("/run/systemd/quotacheck");
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-fsck/systemd-fsck@.service.in b/src/grp-initprogs/systemd-fsck/systemd-fsck@.service.in
new file mode 100644
index 0000000000..6ca6b07e9e
--- /dev/null
+++ b/src/grp-initprogs/systemd-fsck/systemd-fsck@.service.in
@@ -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=File System Check on %f
+Documentation=man:systemd-fsck@.service(8)
+DefaultDependencies=no
+BindsTo=%i.device
+After=%i.device systemd-fsck-root.service local-fs-pre.target
+Before=systemd-quotacheck.service shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-fsck %f
+TimeoutSec=0
diff --git a/src/grp-initprogs/systemd-modules-load/modules-load.c b/src/grp-initprogs/systemd-modules-load/modules-load.c
new file mode 100644
index 0000000000..f75015d8c3
--- /dev/null
+++ b/src/grp-initprogs/systemd-modules-load/modules-load.c
@@ -0,0 +1,283 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <getopt.h>
+#include <libkmod.h>
+#include <limits.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "conf-files.h"
+#include "def.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "proc-cmdline.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+static char **arg_proc_cmdline_modules = NULL;
+
+static const char conf_file_dirs[] = CONF_PATHS_NULSTR("modules-load.d");
+
+static void systemd_kmod_log(void *data, int priority, const char *file, int line,
+ const char *fn, const char *format, va_list args) {
+
+ DISABLE_WARNING_FORMAT_NONLITERAL;
+ log_internalv(priority, 0, file, line, fn, format, args);
+ REENABLE_WARNING;
+}
+
+static int add_modules(const char *p) {
+ _cleanup_strv_free_ char **k = NULL;
+
+ k = strv_split(p, ",");
+ if (!k)
+ return log_oom();
+
+ if (strv_extend_strv(&arg_proc_cmdline_modules, k, true) < 0)
+ return log_oom();
+
+ return 0;
+}
+
+static int parse_proc_cmdline_item(const char *key, const char *value) {
+ int r;
+
+ if (STR_IN_SET(key, "modules-load", "rd.modules-load") && value) {
+ r = add_modules(value);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int load_module(struct kmod_ctx *ctx, const char *m) {
+ const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST;
+ struct kmod_list *itr, *modlist = NULL;
+ int r = 0;
+
+ log_debug("load: %s", m);
+
+ r = kmod_module_new_from_lookup(ctx, m, &modlist);
+ if (r < 0)
+ return log_error_errno(r, "Failed to lookup alias '%s': %m", m);
+
+ if (!modlist) {
+ log_error("Failed to find module '%s'", m);
+ return -ENOENT;
+ }
+
+ kmod_list_foreach(itr, modlist) {
+ struct kmod_module *mod;
+ int state, err;
+
+ mod = kmod_module_get_module(itr);
+ state = kmod_module_get_initstate(mod);
+
+ switch (state) {
+ case KMOD_MODULE_BUILTIN:
+ log_info("Module '%s' is builtin", kmod_module_get_name(mod));
+ break;
+
+ case KMOD_MODULE_LIVE:
+ log_debug("Module '%s' is already loaded", kmod_module_get_name(mod));
+ break;
+
+ default:
+ err = kmod_module_probe_insert_module(mod, probe_flags,
+ NULL, NULL, NULL, NULL);
+
+ if (err == 0)
+ log_info("Inserted module '%s'", kmod_module_get_name(mod));
+ else if (err == KMOD_PROBE_APPLY_BLACKLIST)
+ log_info("Module '%s' is blacklisted", kmod_module_get_name(mod));
+ else {
+ log_error_errno(err, "Failed to insert '%s': %m", kmod_module_get_name(mod));
+ r = err;
+ }
+ }
+
+ kmod_module_unref(mod);
+ }
+
+ kmod_module_unref_list(modlist);
+
+ return r;
+}
+
+static int apply_file(struct kmod_ctx *ctx, const char *path, bool ignore_enoent) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(ctx);
+ assert(path);
+
+ r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f);
+ if (r < 0) {
+ if (ignore_enoent && r == -ENOENT)
+ return 0;
+
+ return log_error_errno(r, "Failed to open %s, ignoring: %m", path);
+ }
+
+ log_debug("apply: %s", path);
+ for (;;) {
+ char line[LINE_MAX], *l;
+ int k;
+
+ if (!fgets(line, sizeof(line), f)) {
+ if (feof(f))
+ break;
+
+ return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path);
+ }
+
+ l = strstrip(line);
+ if (!*l)
+ continue;
+ if (strchr(COMMENTS "\n", *l))
+ continue;
+
+ k = load_module(ctx, l);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+ "Loads statically configured kernel modules.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n",
+ program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int c;
+
+ 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 '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int r, k;
+ struct kmod_ctx *ctx;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = parse_proc_cmdline(parse_proc_cmdline_item);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ ctx = kmod_new(NULL, NULL);
+ if (!ctx) {
+ log_error("Failed to allocate memory for kmod.");
+ goto finish;
+ }
+
+ kmod_load_resources(ctx);
+ kmod_set_log_fn(ctx, systemd_kmod_log, NULL);
+
+ r = 0;
+
+ if (argc > optind) {
+ int i;
+
+ for (i = optind; i < argc; i++) {
+ k = apply_file(ctx, argv[i], false);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ } else {
+ _cleanup_strv_free_ char **files = NULL;
+ char **fn, **i;
+
+ STRV_FOREACH(i, arg_proc_cmdline_modules) {
+ k = load_module(ctx, *i);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ k = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
+ if (k < 0) {
+ log_error_errno(k, "Failed to enumerate modules-load.d files: %m");
+ if (r == 0)
+ r = k;
+ goto finish;
+ }
+
+ STRV_FOREACH(fn, files) {
+ k = apply_file(ctx, *fn, true);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ }
+
+finish:
+ kmod_unref(ctx);
+ strv_free(arg_proc_cmdline_modules);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-modules-load/systemd-modules-load.service.in b/src/grp-initprogs/systemd-modules-load/systemd-modules-load.service.in
new file mode 100644
index 0000000000..9de6d31349
--- /dev/null
+++ b/src/grp-initprogs/systemd-modules-load/systemd-modules-load.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=Load Kernel Modules
+Documentation=man:systemd-modules-load.service(8) man:modules-load.d(5)
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=sysinit.target shutdown.target
+ConditionCapability=CAP_SYS_MODULE
+ConditionDirectoryNotEmpty=|/lib/modules-load.d
+ConditionDirectoryNotEmpty=|/usr/lib/modules-load.d
+ConditionDirectoryNotEmpty=|/usr/local/lib/modules-load.d
+ConditionDirectoryNotEmpty=|/etc/modules-load.d
+ConditionDirectoryNotEmpty=|/run/modules-load.d
+ConditionKernelCommandLine=|modules-load
+ConditionKernelCommandLine=|rd.modules-load
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-modules-load
+TimeoutSec=90s
diff --git a/src/grp-initprogs/systemd-quotacheck/quotacheck.c b/src/grp-initprogs/systemd-quotacheck/quotacheck.c
new file mode 100644
index 0000000000..6d8c05f046
--- /dev/null
+++ b/src/grp-initprogs/systemd-quotacheck/quotacheck.c
@@ -0,0 +1,124 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "proc-cmdline.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "string-util.h"
+#include "util.h"
+
+static bool arg_skip = false;
+static bool arg_force = false;
+
+static int parse_proc_cmdline_item(const char *key, const char *value) {
+
+ if (streq(key, "quotacheck.mode") && value) {
+
+ if (streq(value, "auto"))
+ arg_force = arg_skip = false;
+ else if (streq(value, "force"))
+ arg_force = true;
+ else if (streq(value, "skip"))
+ arg_skip = true;
+ else
+ log_warning("Invalid quotacheck.mode= parameter '%s'. Ignoring.", value);
+ }
+
+#ifdef HAVE_SYSV_COMPAT
+ else if (streq(key, "forcequotacheck") && !value) {
+ log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line.");
+ arg_force = true;
+ }
+#endif
+
+ return 0;
+}
+
+static void test_files(void) {
+
+#ifdef HAVE_SYSV_COMPAT
+ if (access("/forcequotacheck", F_OK) >= 0) {
+ log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system.");
+ arg_force = true;
+ }
+#endif
+}
+
+int main(int argc, char *argv[]) {
+
+ static const char * const cmdline[] = {
+ QUOTACHECK,
+ "-anug",
+ NULL
+ };
+
+ pid_t pid;
+ int r;
+
+ if (argc > 1) {
+ log_error("This program takes no arguments.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = parse_proc_cmdline(parse_proc_cmdline_item);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+
+ test_files();
+
+ if (!arg_force) {
+ if (arg_skip)
+ return EXIT_SUCCESS;
+
+ if (access("/run/systemd/quotacheck", F_OK) < 0)
+ return EXIT_SUCCESS;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_error_errno(errno, "fork(): %m");
+ return EXIT_FAILURE;
+ } else if (pid == 0) {
+
+ /* Child */
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+
+ execv(cmdline[0], (char**) cmdline);
+ _exit(1); /* Operational error */
+ }
+
+ r = wait_for_terminate_and_warn("quotacheck", pid, true);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-quotacheck/systemd-quotacheck.service.in b/src/grp-initprogs/systemd-quotacheck/systemd-quotacheck.service.in
new file mode 100644
index 0000000000..5cb9bc3bc9
--- /dev/null
+++ b/src/grp-initprogs/systemd-quotacheck/systemd-quotacheck.service.in
@@ -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=File System Quota Check
+Documentation=man:systemd-quotacheck.service(8)
+DefaultDependencies=no
+After=systemd-remount-fs.service
+Before=local-fs.target shutdown.target
+ConditionPathExists=@QUOTACHECK@
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-quotacheck
+TimeoutSec=0
diff --git a/src/grp-initprogs/systemd-random-seed/random-seed.c b/src/grp-initprogs/systemd-random-seed/random-seed.c
new file mode 100644
index 0000000000..6748bb9dd3
--- /dev/null
+++ b/src/grp-initprogs/systemd-random-seed/random-seed.c
@@ -0,0 +1,176 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "io-util.h"
+#include "log.h"
+#include "mkdir.h"
+#include "string-util.h"
+#include "util.h"
+
+#define POOL_SIZE_MIN 512
+
+int main(int argc, char *argv[]) {
+ _cleanup_close_ int seed_fd = -1, random_fd = -1;
+ _cleanup_free_ void* buf = NULL;
+ size_t buf_size = 0;
+ ssize_t k;
+ int r, open_rw_error;
+ FILE *f;
+ bool refresh_seed_file = true;
+
+ if (argc != 2) {
+ log_error("This program requires one argument.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ /* Read pool size, if possible */
+ f = fopen("/proc/sys/kernel/random/poolsize", "re");
+ if (f) {
+ if (fscanf(f, "%zu", &buf_size) > 0)
+ /* poolsize is in bits on 2.6, but we want bytes */
+ buf_size /= 8;
+
+ fclose(f);
+ }
+
+ if (buf_size <= POOL_SIZE_MIN)
+ buf_size = POOL_SIZE_MIN;
+
+ buf = malloc(buf_size);
+ if (!buf) {
+ r = log_oom();
+ goto finish;
+ }
+
+ r = mkdir_parents_label(RANDOM_SEED, 0755);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m");
+ goto finish;
+ }
+
+ /* When we load the seed we read it and write it to the device
+ * and then immediately update the saved seed with new data,
+ * to make sure the next boot gets seeded differently. */
+
+ if (streq(argv[1], "load")) {
+
+ seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
+ open_rw_error = -errno;
+ if (seed_fd < 0) {
+ refresh_seed_file = false;
+
+ seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (seed_fd < 0) {
+ bool missing = errno == ENOENT;
+
+ log_full_errno(missing ? LOG_DEBUG : LOG_ERR,
+ open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m");
+ r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR,
+ errno, "Failed to open " RANDOM_SEED " for reading: %m");
+ if (missing)
+ r = 0;
+
+ goto finish;
+ }
+ }
+
+ random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
+ if (random_fd < 0) {
+ random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600);
+ if (random_fd < 0) {
+ r = log_error_errno(errno, "Failed to open /dev/urandom: %m");
+ goto finish;
+ }
+ }
+
+ k = loop_read(seed_fd, buf, buf_size, false);
+ if (k < 0)
+ r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
+ else if (k == 0) {
+ r = 0;
+ log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding.");
+ } else {
+ (void) lseek(seed_fd, 0, SEEK_SET);
+
+ r = loop_write(random_fd, buf, (size_t) k, false);
+ if (r < 0)
+ log_error_errno(r, "Failed to write seed to /dev/urandom: %m");
+ }
+
+ } else if (streq(argv[1], "save")) {
+
+ seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
+ if (seed_fd < 0) {
+ r = log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m");
+ goto finish;
+ }
+
+ random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (random_fd < 0) {
+ r = log_error_errno(errno, "Failed to open /dev/urandom: %m");
+ goto finish;
+ }
+
+ } else {
+ log_error("Unknown verb '%s'.", argv[1]);
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if (refresh_seed_file) {
+
+ /* This is just a safety measure. Given that we are root and
+ * most likely created the file ourselves the mode and owner
+ * should be correct anyway. */
+ (void) fchmod(seed_fd, 0600);
+ (void) fchown(seed_fd, 0, 0);
+
+ k = loop_read(random_fd, buf, buf_size, false);
+ if (k < 0) {
+ r = log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
+ goto finish;
+ }
+ if (k == 0) {
+ log_error("Got EOF while reading from /dev/urandom.");
+ r = -EIO;
+ goto finish;
+ }
+
+ r = loop_write(seed_fd, buf, (size_t) k, false);
+ if (r < 0)
+ log_error_errno(r, "Failed to write new random seed file: %m");
+ }
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-random-seed/systemd-random-seed.service.in b/src/grp-initprogs/systemd-random-seed/systemd-random-seed.service.in
new file mode 100644
index 0000000000..115233268d
--- /dev/null
+++ b/src/grp-initprogs/systemd-random-seed/systemd-random-seed.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=Load/Save Random Seed
+Documentation=man:systemd-random-seed.service(8) man:random(4)
+DefaultDependencies=no
+RequiresMountsFor=@RANDOM_SEED@
+Conflicts=shutdown.target
+After=systemd-remount-fs.service
+Before=sysinit.target shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-random-seed load
+ExecStop=@rootlibexecdir@/systemd-random-seed save
+TimeoutSec=30s
diff --git a/src/grp-initprogs/systemd-rfkill/rfkill.c b/src/grp-initprogs/systemd-rfkill/rfkill.c
new file mode 100644
index 0000000000..f0b0ad9275
--- /dev/null
+++ b/src/grp-initprogs/systemd-rfkill/rfkill.c
@@ -0,0 +1,426 @@
+/***
+ 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 <linux/rfkill.h>
+#include <poll.h>
+
+#include "libudev.h"
+#include <systemd/sd-daemon.h>
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "io-util.h"
+#include "mkdir.h"
+#include "parse-util.h"
+#include "proc-cmdline.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "udev-util.h"
+#include "util.h"
+
+#define EXIT_USEC (5 * USEC_PER_SEC)
+
+static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = {
+ [RFKILL_TYPE_ALL] = "all",
+ [RFKILL_TYPE_WLAN] = "wlan",
+ [RFKILL_TYPE_BLUETOOTH] = "bluetooth",
+ [RFKILL_TYPE_UWB] = "uwb",
+ [RFKILL_TYPE_WIMAX] = "wimax",
+ [RFKILL_TYPE_WWAN] = "wwan",
+ [RFKILL_TYPE_GPS] = "gps",
+ [RFKILL_TYPE_FM] = "fm",
+ [RFKILL_TYPE_NFC] = "nfc",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int);
+
+static int find_device(
+ struct udev *udev,
+ const struct rfkill_event *event,
+ struct udev_device **ret) {
+
+ _cleanup_free_ char *sysname = NULL;
+ struct udev_device *device;
+ const char *name;
+
+ assert(udev);
+ assert(event);
+ assert(ret);
+
+ if (asprintf(&sysname, "rfkill%i", event->idx) < 0)
+ return log_oom();
+
+ device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
+ if (!device)
+ return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
+
+ name = udev_device_get_sysattr_value(device, "name");
+ if (!name) {
+ log_debug("Device has no name, ignoring.");
+ udev_device_unref(device);
+ return -ENOENT;
+ }
+
+ log_debug("Operating on rfkill device '%s'.", name);
+
+ *ret = device;
+ return 0;
+}
+
+static int wait_for_initialized(
+ struct udev *udev,
+ struct udev_device *device,
+ struct udev_device **ret) {
+
+ _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL;
+ struct udev_device *d;
+ const char *sysname;
+ int watch_fd, r;
+
+ assert(udev);
+ assert(device);
+ assert(ret);
+
+ if (udev_device_get_is_initialized(device) != 0) {
+ *ret = udev_device_ref(device);
+ return 0;
+ }
+
+ assert_se(sysname = udev_device_get_sysname(device));
+
+ /* Wait until the device is initialized, so that we can get
+ * access to the ID_PATH property */
+
+ monitor = udev_monitor_new_from_netlink(udev, "udev");
+ if (!monitor)
+ return log_error_errno(errno, "Failed to acquire monitor: %m");
+
+ r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m");
+
+ r = udev_monitor_enable_receiving(monitor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable udev receiving: %m");
+
+ watch_fd = udev_monitor_get_fd(monitor);
+ if (watch_fd < 0)
+ return log_error_errno(watch_fd, "Failed to get watch fd: %m");
+
+ /* Check again, maybe things changed */
+ d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname);
+ if (!d)
+ return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m");
+
+ if (udev_device_get_is_initialized(d) != 0) {
+ *ret = d;
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_udev_device_unref_ struct udev_device *t = NULL;
+
+ r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY);
+ if (r == -EINTR)
+ continue;
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch udev monitor: %m");
+
+ t = udev_monitor_receive_device(monitor);
+ if (!t)
+ continue;
+
+ if (streq_ptr(udev_device_get_sysname(device), sysname)) {
+ *ret = udev_device_ref(t);
+ return 0;
+ }
+ }
+}
+
+static int determine_state_file(
+ struct udev *udev,
+ const struct rfkill_event *event,
+ struct udev_device *d,
+ char **ret) {
+
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ const char *path_id, *type;
+ char *state_file;
+ int r;
+
+ assert(event);
+ assert(d);
+ assert(ret);
+
+ r = wait_for_initialized(udev, d, &device);
+ if (r < 0)
+ return r;
+
+ assert_se(type = rfkill_type_to_string(event->type));
+
+ path_id = udev_device_get_property_value(device, "ID_PATH");
+ if (path_id) {
+ _cleanup_free_ char *escaped_path_id = NULL;
+
+ escaped_path_id = cescape(path_id);
+ if (!escaped_path_id)
+ return log_oom();
+
+ state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL);
+ } else
+ state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL);
+
+ if (!state_file)
+ return log_oom();
+
+ *ret = state_file;
+ return 0;
+}
+
+static int load_state(
+ int rfkill_fd,
+ struct udev *udev,
+ const struct rfkill_event *event) {
+
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ _cleanup_free_ char *state_file = NULL, *value = NULL;
+ struct rfkill_event we;
+ ssize_t l;
+ int b, r;
+
+ assert(rfkill_fd >= 0);
+ assert(udev);
+ assert(event);
+
+ if (shall_restore_state() == 0)
+ return 0;
+
+ r = find_device(udev, event, &device);
+ if (r < 0)
+ return r;
+
+ r = determine_state_file(udev, event, device, &state_file);
+ if (r < 0)
+ return r;
+
+ r = read_one_line_file(state_file, &value);
+ if (r == -ENOENT) {
+ /* No state file? Then save the current state */
+
+ r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write state file %s: %m", state_file);
+
+ log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to read state file %s: %m", state_file);
+
+ b = parse_boolean(value);
+ if (b < 0)
+ return log_error_errno(b, "Failed to parse state file %s: %m", state_file);
+
+ we = (struct rfkill_event) {
+ .op = RFKILL_OP_CHANGE,
+ .idx = event->idx,
+ .soft = b,
+ };
+
+ l = write(rfkill_fd, &we, sizeof(we));
+ if (l < 0)
+ return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx);
+ if (l != sizeof(we)) {
+ log_error("Couldn't write rfkill event structure, too short.");
+ return -EIO;
+ }
+
+ log_debug("Loaded state '%s' from %s.", one_zero(b), state_file);
+ return 0;
+}
+
+static int save_state(
+ int rfkill_fd,
+ struct udev *udev,
+ const struct rfkill_event *event) {
+
+ _cleanup_udev_device_unref_ struct udev_device *device = NULL;
+ _cleanup_free_ char *state_file = NULL;
+ int r;
+
+ assert(rfkill_fd >= 0);
+ assert(udev);
+ assert(event);
+
+ r = find_device(udev, event, &device);
+ if (r < 0)
+ return r;
+
+ r = determine_state_file(udev, event, device, &state_file);
+ if (r < 0)
+ return r;
+
+ r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write state file %s: %m", state_file);
+
+ log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file);
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ _cleanup_udev_unref_ struct udev *udev = NULL;
+ _cleanup_close_ int rfkill_fd = -1;
+ bool ready = false;
+ int r, n;
+
+ if (argc > 1) {
+ log_error("This program requires no arguments.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ udev = udev_new();
+ if (!udev) {
+ r = log_oom();
+ goto finish;
+ }
+
+ r = mkdir_p("/var/lib/systemd/rfkill", 0755);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create rfkill directory: %m");
+ goto finish;
+ }
+
+ n = sd_listen_fds(false);
+ if (n < 0) {
+ r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m");
+ goto finish;
+ }
+ if (n > 1) {
+ log_error("Got too many file descriptors.");
+ r = -EINVAL;
+ goto finish;
+ }
+
+ if (n == 0) {
+ rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (rfkill_fd < 0) {
+ if (errno == ENOENT) {
+ log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting.");
+ r = 0;
+ goto finish;
+ }
+
+ r = log_error_errno(errno, "Failed to open /dev/rfkill: %m");
+ goto finish;
+ }
+ } else {
+ rfkill_fd = SD_LISTEN_FDS_START;
+
+ r = fd_nonblock(rfkill_fd, 1);
+ if (r < 0) {
+ log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m");
+ goto finish;
+ }
+ }
+
+ for (;;) {
+ struct rfkill_event event;
+ const char *type;
+ ssize_t l;
+
+ l = read(rfkill_fd, &event, sizeof(event));
+ if (l < 0) {
+ if (errno == EAGAIN) {
+
+ if (!ready) {
+ /* Notify manager that we are
+ * now finished with
+ * processing whatever was
+ * queued */
+ (void) sd_notify(false, "READY=1");
+ ready = true;
+ }
+
+ /* Hang around for a bit, maybe there's more coming */
+
+ r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC);
+ if (r == -EINTR)
+ continue;
+ if (r < 0) {
+ log_error_errno(r, "Failed to poll() on device: %m");
+ goto finish;
+ }
+ if (r > 0)
+ continue;
+
+ log_debug("All events read and idle, exiting.");
+ break;
+ }
+
+ log_error_errno(errno, "Failed to read from /dev/rfkill: %m");
+ }
+
+ if (l != RFKILL_EVENT_SIZE_V1) {
+ log_error("Read event structure of invalid size.");
+ r = -EIO;
+ goto finish;
+ }
+
+ type = rfkill_type_to_string(event.type);
+ if (!type) {
+ log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type);
+ continue;
+ }
+
+ switch (event.op) {
+
+ case RFKILL_OP_ADD:
+ log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type);
+ (void) load_state(rfkill_fd, udev, &event);
+ break;
+
+ case RFKILL_OP_DEL:
+ log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type);
+ break;
+
+ case RFKILL_OP_CHANGE:
+ log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type);
+ (void) save_state(rfkill_fd, udev, &event);
+ break;
+
+ default:
+ log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type);
+ break;
+ }
+ }
+
+ r = 0;
+
+finish:
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-rfkill/systemd-rfkill.service.in b/src/grp-initprogs/systemd-rfkill/systemd-rfkill.service.in
new file mode 100644
index 0000000000..780a19b996
--- /dev/null
+++ b/src/grp-initprogs/systemd-rfkill/systemd-rfkill.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=Load/Save RF Kill Switch Status
+Documentation=man:systemd-rfkill.service(8)
+DefaultDependencies=no
+RequiresMountsFor=/var/lib/systemd/rfkill
+BindsTo=sys-devices-virtual-misc-rfkill.device
+Conflicts=shutdown.target
+After=sys-devices-virtual-misc-rfkill.device systemd-remount-fs.service
+Before=shutdown.target
+
+[Service]
+Type=notify
+ExecStart=@rootlibexecdir@/systemd-rfkill
+TimeoutSec=30s
diff --git a/src/grp-initprogs/systemd-rfkill/systemd-rfkill.socket b/src/grp-initprogs/systemd-rfkill/systemd-rfkill.socket
new file mode 100644
index 0000000000..20ae2f8adb
--- /dev/null
+++ b/src/grp-initprogs/systemd-rfkill/systemd-rfkill.socket
@@ -0,0 +1,19 @@
+# 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=Load/Save RF Kill Switch Status /dev/rfkill Watch
+Documentation=man:systemd-rfkill.socket(8)
+DefaultDependencies=no
+BindsTo=sys-devices-virtual-misc-rfkill.device
+After=sys-devices-virtual-misc-rfkill.device
+Conflicts=shutdown.target
+Before=shutdown.target
+
+[Socket]
+ListenSpecial=/dev/rfkill
+Writable=yes
diff --git a/src/grp-initprogs/systemd-sysctl/sysctl.c b/src/grp-initprogs/systemd-sysctl/sysctl.c
new file mode 100644
index 0000000000..ce7c26e7d3
--- /dev/null
+++ b/src/grp-initprogs/systemd-sysctl/sysctl.c
@@ -0,0 +1,287 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "conf-files.h"
+#include "def.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "log.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "sysctl-util.h"
+#include "util.h"
+
+static char **arg_prefixes = NULL;
+
+static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysctl.d");
+
+static int apply_all(Hashmap *sysctl_options) {
+ char *property, *value;
+ Iterator i;
+ int r = 0;
+
+ HASHMAP_FOREACH_KEY(value, property, sysctl_options, i) {
+ int k;
+
+ k = sysctl_write(property, value);
+ if (k < 0) {
+ log_full_errno(k == -ENOENT ? LOG_INFO : LOG_WARNING, k,
+ "Couldn't write '%s' to '%s', ignoring: %m", value, property);
+
+ if (r == 0 && k != -ENOENT)
+ r = k;
+ }
+ }
+
+ return r;
+}
+
+static int parse_file(Hashmap *sysctl_options, const char *path, bool ignore_enoent) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ assert(path);
+
+ r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f);
+ if (r < 0) {
+ if (ignore_enoent && r == -ENOENT)
+ return 0;
+
+ return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path);
+ }
+
+ log_debug("Parsing %s", path);
+ while (!feof(f)) {
+ char l[LINE_MAX], *p, *value, *new_value, *property, *existing;
+ void *v;
+ int k;
+
+ if (!fgets(l, sizeof(l), f)) {
+ if (feof(f))
+ break;
+
+ return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path);
+ }
+
+ p = strstrip(l);
+ if (!*p)
+ continue;
+
+ if (strchr(COMMENTS "\n", *p))
+ continue;
+
+ value = strchr(p, '=');
+ if (!value) {
+ log_error("Line is not an assignment in file '%s': %s", path, value);
+
+ if (r == 0)
+ r = -EINVAL;
+ continue;
+ }
+
+ *value = 0;
+ value++;
+
+ p = sysctl_normalize(strstrip(p));
+ value = strstrip(value);
+
+ if (!strv_isempty(arg_prefixes)) {
+ char **i, *t;
+ STRV_FOREACH(i, arg_prefixes) {
+ t = path_startswith(*i, "/proc/sys/");
+ if (t == NULL)
+ t = *i;
+ if (path_startswith(p, t))
+ goto found;
+ }
+ /* not found */
+ continue;
+ }
+
+found:
+ existing = hashmap_get2(sysctl_options, p, &v);
+ if (existing) {
+ if (streq(value, existing))
+ continue;
+
+ log_debug("Overwriting earlier assignment of %s in file '%s'.", p, path);
+ free(hashmap_remove(sysctl_options, p));
+ free(v);
+ }
+
+ property = strdup(p);
+ if (!property)
+ return log_oom();
+
+ new_value = strdup(value);
+ if (!new_value) {
+ free(property);
+ return log_oom();
+ }
+
+ k = hashmap_put(sysctl_options, property, new_value);
+ if (k < 0) {
+ log_error_errno(k, "Failed to add sysctl variable %s to hashmap: %m", property);
+ free(property);
+ free(new_value);
+ return k;
+ }
+ }
+
+ return r;
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+ "Applies kernel sysctl settings.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --prefix=PATH Only apply rules with the specified prefix\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_PREFIX
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "prefix", required_argument, NULL, ARG_PREFIX },
+ {}
+ };
+
+ int c;
+
+ 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_PREFIX: {
+ char *p;
+
+ /* We used to require people to specify absolute paths
+ * in /proc/sys in the past. This is kinda useless, but
+ * we need to keep compatibility. We now support any
+ * sysctl name available. */
+ sysctl_normalize(optarg);
+
+ if (startswith(optarg, "/proc/sys"))
+ p = strdup(optarg);
+ else
+ p = strappend("/proc/sys/", optarg);
+ if (!p)
+ return log_oom();
+
+ if (strv_consume(&arg_prefixes, p) < 0)
+ return log_oom();
+
+ break;
+ }
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ int r = 0, k;
+ Hashmap *sysctl_options;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ sysctl_options = hashmap_new(&string_hash_ops);
+ if (!sysctl_options) {
+ r = log_oom();
+ goto finish;
+ }
+
+ r = 0;
+
+ if (argc > optind) {
+ int i;
+
+ for (i = optind; i < argc; i++) {
+ k = parse_file(sysctl_options, argv[i], false);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ } else {
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+
+ r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs);
+ if (r < 0) {
+ log_error_errno(r, "Failed to enumerate sysctl.d files: %m");
+ goto finish;
+ }
+
+ STRV_FOREACH(f, files) {
+ k = parse_file(sysctl_options, *f, true);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ }
+
+ k = apply_all(sysctl_options);
+ if (k < 0 && r == 0)
+ r = k;
+
+finish:
+ hashmap_free_free_free(sysctl_options);
+ strv_free(arg_prefixes);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-sysctl/systemd-sysctl.service.in b/src/grp-initprogs/systemd-sysctl/systemd-sysctl.service.in
new file mode 100644
index 0000000000..d784c6426d
--- /dev/null
+++ b/src/grp-initprogs/systemd-sysctl/systemd-sysctl.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=Apply Kernel Variables
+Documentation=man:systemd-sysctl.service(8) man:sysctl.d(5)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-modules-load.service
+Before=sysinit.target shutdown.target
+ConditionPathIsReadWrite=/proc/sys/
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-sysctl
+TimeoutSec=90s
diff --git a/src/grp-initprogs/systemd-sysusers/systemd-sysusers.service.in b/src/grp-initprogs/systemd-sysusers/systemd-sysusers.service.in
new file mode 100644
index 0000000000..4d8309ab6b
--- /dev/null
+++ b/src/grp-initprogs/systemd-sysusers/systemd-sysusers.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=Create System Users
+Documentation=man:sysusers.d(5) man:systemd-sysusers.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-remount-fs.service
+Before=sysinit.target shutdown.target systemd-update-done.service
+ConditionNeedsUpdate=/etc
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/systemd-sysusers
+TimeoutSec=90s
diff --git a/src/grp-initprogs/systemd-sysusers/systemd-sysusers.xml b/src/grp-initprogs/systemd-sysusers/systemd-sysusers.xml
new file mode 100644
index 0000000000..4892caad12
--- /dev/null
+++ b/src/grp-initprogs/systemd-sysusers/systemd-sysusers.xml
@@ -0,0 +1,116 @@
+<?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 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/>.
+-->
+
+<refentry id="systemd-sysusers"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-sysusers</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-sysusers</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-sysusers</refname>
+ <refname>systemd-sysusers.service</refname>
+ <refpurpose>Allocate system users and groups</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-sysusers</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="repeat"><replaceable>CONFIGFILE</replaceable></arg>
+ </cmdsynopsis>
+
+ <para><filename>systemd-sysusers.service</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-sysusers</command> creates system users and
+ groups, based on the file format and location specified in
+ <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para>
+
+ <para>If invoked with no arguments, it applies all directives from
+ all files found. If one or more filenames are passed on the
+ command line, only the directives in these files are applied. If
+ only the basename of a file is specified, all directories as
+ specified in
+ <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ are searched for a matching file. If the string
+ <filename>-</filename> is specified as filename, entries from the
+ standard input of the process are read.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--root=<replaceable>root</replaceable></option></term>
+ <listitem><para>Takes a directory path as an argument. All
+ paths will be prefixed with the given alternate
+ <replaceable>root</replaceable> path, including config search
+ paths. </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, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-initprogs/systemd-sysusers/sysusers.c b/src/grp-initprogs/systemd-sysusers/sysusers.c
new file mode 100644
index 0000000000..4377f1b910
--- /dev/null
+++ b/src/grp-initprogs/systemd-sysusers/sysusers.c
@@ -0,0 +1,1919 @@
+/***
+ 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 <getopt.h>
+#include <grp.h>
+#include <gshadow.h>
+#include <pwd.h>
+#include <shadow.h>
+#include <utmp.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "copy.h"
+#include "def.h"
+#include "fd-util.h"
+#include "fileio-label.h"
+#include "formats-util.h"
+#include "hashmap.h"
+#include "path-util.h"
+#include "selinux-util.h"
+#include "smack-util.h"
+#include "specifier.h"
+#include "string-util.h"
+#include "strv.h"
+#include "uid-range.h"
+#include "user-util.h"
+#include "utf8.h"
+#include "util.h"
+
+typedef enum ItemType {
+ ADD_USER = 'u',
+ ADD_GROUP = 'g',
+ ADD_MEMBER = 'm',
+ ADD_RANGE = 'r',
+} ItemType;
+typedef struct Item {
+ ItemType type;
+
+ char *name;
+ char *uid_path;
+ char *gid_path;
+ char *description;
+ char *home;
+
+ gid_t gid;
+ uid_t uid;
+
+ bool gid_set:1;
+ bool uid_set:1;
+
+ bool todo_user:1;
+ bool todo_group:1;
+} Item;
+
+static char *arg_root = NULL;
+
+static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d");
+
+static Hashmap *users = NULL, *groups = NULL;
+static Hashmap *todo_uids = NULL, *todo_gids = NULL;
+static Hashmap *members = NULL;
+
+static Hashmap *database_uid = NULL, *database_user = NULL;
+static Hashmap *database_gid = NULL, *database_group = NULL;
+
+static uid_t search_uid = UID_INVALID;
+static UidRange *uid_range = NULL;
+static unsigned n_uid_range = 0;
+
+static int load_user_database(void) {
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *passwd_path;
+ struct passwd *pw;
+ int r;
+
+ passwd_path = prefix_roota(arg_root, "/etc/passwd");
+ f = fopen(passwd_path, "re");
+ if (!f)
+ return errno == ENOENT ? 0 : -errno;
+
+ r = hashmap_ensure_allocated(&database_user, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_allocated(&database_uid, NULL);
+ if (r < 0)
+ return r;
+
+ errno = 0;
+ while ((pw = fgetpwent(f))) {
+ char *n;
+ int k, q;
+
+ n = strdup(pw->pw_name);
+ if (!n)
+ return -ENOMEM;
+
+ k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid));
+ if (k < 0 && k != -EEXIST) {
+ free(n);
+ return k;
+ }
+
+ q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
+ if (q < 0 && q != -EEXIST) {
+ if (k < 0)
+ free(n);
+ return q;
+ }
+
+ if (q < 0 && k < 0)
+ free(n);
+
+ errno = 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT))
+ return -errno;
+
+ return 0;
+}
+
+static int load_group_database(void) {
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *group_path;
+ struct group *gr;
+ int r;
+
+ group_path = prefix_roota(arg_root, "/etc/group");
+ f = fopen(group_path, "re");
+ if (!f)
+ return errno == ENOENT ? 0 : -errno;
+
+ r = hashmap_ensure_allocated(&database_group, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_allocated(&database_gid, NULL);
+ if (r < 0)
+ return r;
+
+ errno = 0;
+ while ((gr = fgetgrent(f))) {
+ char *n;
+ int k, q;
+
+ n = strdup(gr->gr_name);
+ if (!n)
+ return -ENOMEM;
+
+ k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid));
+ if (k < 0 && k != -EEXIST) {
+ free(n);
+ return k;
+ }
+
+ q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
+ if (q < 0 && q != -EEXIST) {
+ if (k < 0)
+ free(n);
+ return q;
+ }
+
+ if (q < 0 && k < 0)
+ free(n);
+
+ errno = 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT))
+ return -errno;
+
+ return 0;
+}
+
+static int make_backup(const char *target, const char *x) {
+ _cleanup_close_ int src = -1;
+ _cleanup_fclose_ FILE *dst = NULL;
+ char *backup, *temp;
+ struct timespec ts[2];
+ struct stat st;
+ int r;
+
+ src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (src < 0) {
+ if (errno == ENOENT) /* No backup necessary... */
+ return 0;
+
+ return -errno;
+ }
+
+ if (fstat(src, &st) < 0)
+ return -errno;
+
+ r = fopen_temporary_label(target, x, &dst, &temp);
+ if (r < 0)
+ return r;
+
+ r = copy_bytes(src, fileno(dst), (uint64_t) -1, true);
+ if (r < 0)
+ goto fail;
+
+ /* Don't fail on chmod() or chown(). If it stays owned by us
+ * and/or unreadable by others, then it isn't too bad... */
+
+ backup = strjoina(x, "-");
+
+ /* Copy over the access mask */
+ if (fchmod(fileno(dst), st.st_mode & 07777) < 0)
+ log_warning_errno(errno, "Failed to change mode on %s: %m", backup);
+
+ if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0)
+ log_warning_errno(errno, "Failed to change ownership of %s: %m", backup);
+
+ ts[0] = st.st_atim;
+ ts[1] = st.st_mtim;
+ if (futimens(fileno(dst), ts) < 0)
+ log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup);
+
+ if (rename(temp, backup) < 0)
+ goto fail;
+
+ return 0;
+
+fail:
+ unlink(temp);
+ return r;
+}
+
+static int putgrent_with_members(const struct group *gr, FILE *group) {
+ char **a;
+
+ assert(gr);
+ assert(group);
+
+ a = hashmap_get(members, gr->gr_name);
+ if (a) {
+ _cleanup_strv_free_ char **l = NULL;
+ bool added = false;
+ char **i;
+
+ l = strv_copy(gr->gr_mem);
+ if (!l)
+ return -ENOMEM;
+
+ STRV_FOREACH(i, a) {
+ if (strv_find(l, *i))
+ continue;
+
+ if (strv_extend(&l, *i) < 0)
+ return -ENOMEM;
+
+ added = true;
+ }
+
+ if (added) {
+ struct group t;
+
+ strv_uniq(l);
+ strv_sort(l);
+
+ t = *gr;
+ t.gr_mem = l;
+
+ errno = 0;
+ if (putgrent(&t, group) != 0)
+ return errno > 0 ? -errno : -EIO;
+
+ return 1;
+ }
+ }
+
+ errno = 0;
+ if (putgrent(gr, group) != 0)
+ return errno > 0 ? -errno : -EIO;
+
+ return 0;
+}
+
+static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
+ char **a;
+
+ assert(sg);
+ assert(gshadow);
+
+ a = hashmap_get(members, sg->sg_namp);
+ if (a) {
+ _cleanup_strv_free_ char **l = NULL;
+ bool added = false;
+ char **i;
+
+ l = strv_copy(sg->sg_mem);
+ if (!l)
+ return -ENOMEM;
+
+ STRV_FOREACH(i, a) {
+ if (strv_find(l, *i))
+ continue;
+
+ if (strv_extend(&l, *i) < 0)
+ return -ENOMEM;
+
+ added = true;
+ }
+
+ if (added) {
+ struct sgrp t;
+
+ strv_uniq(l);
+ strv_sort(l);
+
+ t = *sg;
+ t.sg_mem = l;
+
+ errno = 0;
+ if (putsgent(&t, gshadow) != 0)
+ return errno > 0 ? -errno : -EIO;
+
+ return 1;
+ }
+ }
+
+ errno = 0;
+ if (putsgent(sg, gshadow) != 0)
+ return errno > 0 ? -errno : -EIO;
+
+ return 0;
+}
+
+static int sync_rights(FILE *from, FILE *to) {
+ struct stat st;
+
+ if (fstat(fileno(from), &st) < 0)
+ return -errno;
+
+ if (fchmod(fileno(to), st.st_mode & 07777) < 0)
+ return -errno;
+
+ if (fchown(fileno(to), st.st_uid, st.st_gid) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int rename_and_apply_smack(const char *temp_path, const char *dest_path) {
+ int r = 0;
+ if (rename(temp_path, dest_path) < 0)
+ return -errno;
+
+#ifdef SMACK_RUN_LABEL
+ r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
+ if (r < 0)
+ return r;
+#endif
+ return r;
+}
+
+static int write_files(void) {
+
+ _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL;
+ _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL;
+ const char *passwd_path = NULL, *group_path = NULL, *shadow_path = NULL, *gshadow_path = NULL;
+ bool group_changed = false;
+ Iterator iterator;
+ Item *i;
+ int r;
+
+ if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) {
+ _cleanup_fclose_ FILE *original = NULL;
+
+ /* First we update the actual group list file */
+ group_path = prefix_roota(arg_root, "/etc/group");
+ r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
+ if (r < 0)
+ goto finish;
+
+ original = fopen(group_path, "re");
+ if (original) {
+ struct group *gr;
+
+ r = sync_rights(original, group);
+ if (r < 0)
+ goto finish;
+
+ errno = 0;
+ while ((gr = fgetgrent(original))) {
+ /* Safety checks against name and GID
+ * collisions. Normally, this should
+ * be unnecessary, but given that we
+ * look at the entries anyway here,
+ * let's make an extra verification
+ * step that we don't generate
+ * duplicate entries. */
+
+ i = hashmap_get(groups, gr->gr_name);
+ if (i && i->todo_group) {
+ log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
+ log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ r = putgrent_with_members(gr, group);
+ if (r < 0)
+ goto finish;
+ if (r > 0)
+ group_changed = true;
+
+ errno = 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT)) {
+ r = -errno;
+ goto finish;
+ }
+
+ } else if (errno != ENOENT) {
+ r = -errno;
+ goto finish;
+ } else if (fchmod(fileno(group), 0644) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ HASHMAP_FOREACH(i, todo_gids, iterator) {
+ struct group n = {
+ .gr_name = i->name,
+ .gr_gid = i->gid,
+ .gr_passwd = (char*) "x",
+ };
+
+ r = putgrent_with_members(&n, group);
+ if (r < 0)
+ goto finish;
+
+ group_changed = true;
+ }
+
+ r = fflush_and_check(group);
+ if (r < 0)
+ goto finish;
+
+ if (original) {
+ fclose(original);
+ original = NULL;
+ }
+
+ /* OK, now also update the shadow file for the group list */
+ gshadow_path = prefix_roota(arg_root, "/etc/gshadow");
+ r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
+ if (r < 0)
+ goto finish;
+
+ original = fopen(gshadow_path, "re");
+ if (original) {
+ struct sgrp *sg;
+
+ r = sync_rights(original, gshadow);
+ if (r < 0)
+ goto finish;
+
+ errno = 0;
+ while ((sg = fgetsgent(original))) {
+
+ i = hashmap_get(groups, sg->sg_namp);
+ if (i && i->todo_group) {
+ log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ r = putsgent_with_members(sg, gshadow);
+ if (r < 0)
+ goto finish;
+ if (r > 0)
+ group_changed = true;
+
+ errno = 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT)) {
+ r = -errno;
+ goto finish;
+ }
+
+ } else if (errno != ENOENT) {
+ r = -errno;
+ goto finish;
+ } else if (fchmod(fileno(gshadow), 0000) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ HASHMAP_FOREACH(i, todo_gids, iterator) {
+ struct sgrp n = {
+ .sg_namp = i->name,
+ .sg_passwd = (char*) "!!",
+ };
+
+ r = putsgent_with_members(&n, gshadow);
+ if (r < 0)
+ goto finish;
+
+ group_changed = true;
+ }
+
+ r = fflush_and_check(gshadow);
+ if (r < 0)
+ goto finish;
+ }
+
+ if (hashmap_size(todo_uids) > 0) {
+ _cleanup_fclose_ FILE *original = NULL;
+ long lstchg;
+
+ /* First we update the user database itself */
+ passwd_path = prefix_roota(arg_root, "/etc/passwd");
+ r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
+ if (r < 0)
+ goto finish;
+
+ original = fopen(passwd_path, "re");
+ if (original) {
+ struct passwd *pw;
+
+ r = sync_rights(original, passwd);
+ if (r < 0)
+ goto finish;
+
+ errno = 0;
+ while ((pw = fgetpwent(original))) {
+
+ i = hashmap_get(users, pw->pw_name);
+ if (i && i->todo_user) {
+ log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
+ log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid);
+ r = -EEXIST;
+ goto finish;
+ }
+
+ errno = 0;
+ if (putpwent(pw, passwd) < 0) {
+ r = errno ? -errno : -EIO;
+ goto finish;
+ }
+
+ errno = 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT)) {
+ r = -errno;
+ goto finish;
+ }
+
+ } else if (errno != ENOENT) {
+ r = -errno;
+ goto finish;
+ } else if (fchmod(fileno(passwd), 0644) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ HASHMAP_FOREACH(i, todo_uids, iterator) {
+ struct passwd n = {
+ .pw_name = i->name,
+ .pw_uid = i->uid,
+ .pw_gid = i->gid,
+ .pw_gecos = i->description,
+
+ /* "x" means the password is stored in
+ * the shadow file */
+ .pw_passwd = (char*) "x",
+
+ /* We default to the root directory as home */
+ .pw_dir = i->home ? i->home : (char*) "/",
+
+ /* Initialize the shell to nologin,
+ * with one exception: for root we
+ * patch in something special */
+ .pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
+ };
+
+ errno = 0;
+ if (putpwent(&n, passwd) != 0) {
+ r = errno ? -errno : -EIO;
+ goto finish;
+ }
+ }
+
+ r = fflush_and_check(passwd);
+ if (r < 0)
+ goto finish;
+
+ if (original) {
+ fclose(original);
+ original = NULL;
+ }
+
+ /* The we update the shadow database */
+ shadow_path = prefix_roota(arg_root, "/etc/shadow");
+ r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
+ if (r < 0)
+ goto finish;
+
+ lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
+
+ original = fopen(shadow_path, "re");
+ if (original) {
+ struct spwd *sp;
+
+ r = sync_rights(original, shadow);
+ if (r < 0)
+ goto finish;
+
+ errno = 0;
+ while ((sp = fgetspent(original))) {
+
+ i = hashmap_get(users, sp->sp_namp);
+ if (i && i->todo_user) {
+ /* we will update the existing entry */
+ sp->sp_lstchg = lstchg;
+
+ /* only the /etc/shadow stage is left, so we can
+ * safely remove the item from the todo set */
+ i->todo_user = false;
+ hashmap_remove(todo_uids, UID_TO_PTR(i->uid));
+ }
+
+ errno = 0;
+ if (putspent(sp, shadow) < 0) {
+ r = errno ? -errno : -EIO;
+ goto finish;
+ }
+
+ errno = 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT)) {
+ r = -errno;
+ goto finish;
+ }
+ } else if (errno != ENOENT) {
+ r = -errno;
+ goto finish;
+ } else if (fchmod(fileno(shadow), 0000) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ HASHMAP_FOREACH(i, todo_uids, iterator) {
+ struct spwd n = {
+ .sp_namp = i->name,
+ .sp_pwdp = (char*) "!!",
+ .sp_lstchg = lstchg,
+ .sp_min = -1,
+ .sp_max = -1,
+ .sp_warn = -1,
+ .sp_inact = -1,
+ .sp_expire = -1,
+ .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
+ };
+
+ errno = 0;
+ if (putspent(&n, shadow) != 0) {
+ r = errno ? -errno : -EIO;
+ goto finish;
+ }
+ }
+
+ r = fflush_and_check(shadow);
+ if (r < 0)
+ goto finish;
+ }
+
+ /* Make a backup of the old files */
+ if (group_changed) {
+ if (group) {
+ r = make_backup("/etc/group", group_path);
+ if (r < 0)
+ goto finish;
+ }
+ if (gshadow) {
+ r = make_backup("/etc/gshadow", gshadow_path);
+ if (r < 0)
+ goto finish;
+ }
+ }
+
+ if (passwd) {
+ r = make_backup("/etc/passwd", passwd_path);
+ if (r < 0)
+ goto finish;
+ }
+ if (shadow) {
+ r = make_backup("/etc/shadow", shadow_path);
+ if (r < 0)
+ goto finish;
+ }
+
+ /* And make the new files count */
+ if (group_changed) {
+ if (group) {
+ r = rename_and_apply_smack(group_tmp, group_path);
+ if (r < 0)
+ goto finish;
+
+ group_tmp = mfree(group_tmp);
+ }
+ if (gshadow) {
+ r = rename_and_apply_smack(gshadow_tmp, gshadow_path);
+ if (r < 0)
+ goto finish;
+
+ gshadow_tmp = mfree(gshadow_tmp);
+ }
+ }
+
+ if (passwd) {
+ r = rename_and_apply_smack(passwd_tmp, passwd_path);
+ if (r < 0)
+ goto finish;
+
+ passwd_tmp = mfree(passwd_tmp);
+ }
+ if (shadow) {
+ r = rename_and_apply_smack(shadow_tmp, shadow_path);
+ if (r < 0)
+ goto finish;
+
+ shadow_tmp = mfree(shadow_tmp);
+ }
+
+ r = 0;
+
+finish:
+ if (passwd_tmp)
+ unlink(passwd_tmp);
+ if (shadow_tmp)
+ unlink(shadow_tmp);
+ if (group_tmp)
+ unlink(group_tmp);
+ if (gshadow_tmp)
+ unlink(gshadow_tmp);
+
+ return r;
+}
+
+static int uid_is_ok(uid_t uid, const char *name) {
+ struct passwd *p;
+ struct group *g;
+ const char *n;
+ Item *i;
+
+ /* Let's see if we already have assigned the UID a second time */
+ if (hashmap_get(todo_uids, UID_TO_PTR(uid)))
+ return 0;
+
+ /* Try to avoid using uids that are already used by a group
+ * that doesn't have the same name as our new user. */
+ i = hashmap_get(todo_gids, GID_TO_PTR(uid));
+ if (i && !streq(i->name, name))
+ return 0;
+
+ /* Let's check the files directly */
+ if (hashmap_contains(database_uid, UID_TO_PTR(uid)))
+ return 0;
+
+ n = hashmap_get(database_gid, GID_TO_PTR(uid));
+ if (n && !streq(n, name))
+ return 0;
+
+ /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
+ if (!arg_root) {
+ errno = 0;
+ p = getpwuid(uid);
+ if (p)
+ return 0;
+ if (!IN_SET(errno, 0, ENOENT))
+ return -errno;
+
+ errno = 0;
+ g = getgrgid((gid_t) uid);
+ if (g) {
+ if (!streq(g->gr_name, name))
+ return 0;
+ } else if (!IN_SET(errno, 0, ENOENT))
+ return -errno;
+ }
+
+ return 1;
+}
+
+static int root_stat(const char *p, struct stat *st) {
+ const char *fix;
+
+ fix = prefix_roota(arg_root, p);
+ if (stat(fix, st) < 0)
+ return -errno;
+
+ return 0;
+}
+
+static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
+ struct stat st;
+ bool found_uid = false, found_gid = false;
+ uid_t uid = 0;
+ gid_t gid = 0;
+
+ assert(i);
+
+ /* First, try to get the gid directly */
+ if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
+ gid = st.st_gid;
+ found_gid = true;
+ }
+
+ /* Then, try to get the uid directly */
+ if ((_uid || (_gid && !found_gid))
+ && i->uid_path
+ && root_stat(i->uid_path, &st) >= 0) {
+
+ uid = st.st_uid;
+ found_uid = true;
+
+ /* If we need the gid, but had no success yet, also derive it from the uid path */
+ if (_gid && !found_gid) {
+ gid = st.st_gid;
+ found_gid = true;
+ }
+ }
+
+ /* If that didn't work yet, then let's reuse the gid as uid */
+ if (_uid && !found_uid && i->gid_path) {
+
+ if (found_gid) {
+ uid = (uid_t) gid;
+ found_uid = true;
+ } else if (root_stat(i->gid_path, &st) >= 0) {
+ uid = (uid_t) st.st_gid;
+ found_uid = true;
+ }
+ }
+
+ if (_uid) {
+ if (!found_uid)
+ return 0;
+
+ *_uid = uid;
+ }
+
+ if (_gid) {
+ if (!found_gid)
+ return 0;
+
+ *_gid = gid;
+ }
+
+ return 1;
+}
+
+static int add_user(Item *i) {
+ void *z;
+ int r;
+
+ assert(i);
+
+ /* Check the database directly */
+ z = hashmap_get(database_user, i->name);
+ if (z) {
+ log_debug("User %s already exists.", i->name);
+ i->uid = PTR_TO_UID(z);
+ i->uid_set = true;
+ return 0;
+ }
+
+ if (!arg_root) {
+ struct passwd *p;
+
+ /* Also check NSS */
+ errno = 0;
+ p = getpwnam(i->name);
+ if (p) {
+ log_debug("User %s already exists.", i->name);
+ i->uid = p->pw_uid;
+ i->uid_set = true;
+
+ r = free_and_strdup(&i->description, p->pw_gecos);
+ if (r < 0)
+ return log_oom();
+
+ return 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT))
+ return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name);
+ }
+
+ /* Try to use the suggested numeric uid */
+ if (i->uid_set) {
+ r = uid_is_ok(i->uid, i->name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
+ if (r == 0) {
+ log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
+ i->uid_set = false;
+ }
+ }
+
+ /* If that didn't work, try to read it from the specified path */
+ if (!i->uid_set) {
+ uid_t c;
+
+ if (read_id_from_file(i, &c, NULL) > 0) {
+
+ if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
+ log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
+ else {
+ r = uid_is_ok(c, i->name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
+ else if (r > 0) {
+ i->uid = c;
+ i->uid_set = true;
+ } else
+ log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
+ }
+ }
+ }
+
+ /* Otherwise, try to reuse the group ID */
+ if (!i->uid_set && i->gid_set) {
+ r = uid_is_ok((uid_t) i->gid, i->name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
+ if (r > 0) {
+ i->uid = (uid_t) i->gid;
+ i->uid_set = true;
+ }
+ }
+
+ /* And if that didn't work either, let's try to find a free one */
+ if (!i->uid_set) {
+ for (;;) {
+ r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
+ if (r < 0) {
+ log_error("No free user ID available for %s.", i->name);
+ return r;
+ }
+
+ r = uid_is_ok(search_uid, i->name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
+ else if (r > 0)
+ break;
+ }
+
+ i->uid_set = true;
+ i->uid = search_uid;
+ }
+
+ r = hashmap_ensure_allocated(&todo_uids, NULL);
+ if (r < 0)
+ return log_oom();
+
+ r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
+ if (r < 0)
+ return log_oom();
+
+ i->todo_user = true;
+ log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
+
+ return 0;
+}
+
+static int gid_is_ok(gid_t gid) {
+ struct group *g;
+ struct passwd *p;
+
+ if (hashmap_get(todo_gids, GID_TO_PTR(gid)))
+ return 0;
+
+ /* Avoid reusing gids that are already used by a different user */
+ if (hashmap_get(todo_uids, UID_TO_PTR(gid)))
+ return 0;
+
+ if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
+ return 0;
+
+ if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
+ return 0;
+
+ if (!arg_root) {
+ errno = 0;
+ g = getgrgid(gid);
+ if (g)
+ return 0;
+ if (!IN_SET(errno, 0, ENOENT))
+ return -errno;
+
+ errno = 0;
+ p = getpwuid((uid_t) gid);
+ if (p)
+ return 0;
+ if (!IN_SET(errno, 0, ENOENT))
+ return -errno;
+ }
+
+ return 1;
+}
+
+static int add_group(Item *i) {
+ void *z;
+ int r;
+
+ assert(i);
+
+ /* Check the database directly */
+ z = hashmap_get(database_group, i->name);
+ if (z) {
+ log_debug("Group %s already exists.", i->name);
+ i->gid = PTR_TO_GID(z);
+ i->gid_set = true;
+ return 0;
+ }
+
+ /* Also check NSS */
+ if (!arg_root) {
+ struct group *g;
+
+ errno = 0;
+ g = getgrnam(i->name);
+ if (g) {
+ log_debug("Group %s already exists.", i->name);
+ i->gid = g->gr_gid;
+ i->gid_set = true;
+ return 0;
+ }
+ if (!IN_SET(errno, 0, ENOENT))
+ return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name);
+ }
+
+ /* Try to use the suggested numeric gid */
+ if (i->gid_set) {
+ r = gid_is_ok(i->gid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
+ if (r == 0) {
+ log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
+ i->gid_set = false;
+ }
+ }
+
+ /* Try to reuse the numeric uid, if there's one */
+ if (!i->gid_set && i->uid_set) {
+ r = gid_is_ok((gid_t) i->uid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
+ if (r > 0) {
+ i->gid = (gid_t) i->uid;
+ i->gid_set = true;
+ }
+ }
+
+ /* If that didn't work, try to read it from the specified path */
+ if (!i->gid_set) {
+ gid_t c;
+
+ if (read_id_from_file(i, NULL, &c) > 0) {
+
+ if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
+ log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
+ else {
+ r = gid_is_ok(c);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
+ else if (r > 0) {
+ i->gid = c;
+ i->gid_set = true;
+ } else
+ log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
+ }
+ }
+ }
+
+ /* And if that didn't work either, let's try to find a free one */
+ if (!i->gid_set) {
+ for (;;) {
+ /* We look for new GIDs in the UID pool! */
+ r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
+ if (r < 0) {
+ log_error("No free group ID available for %s.", i->name);
+ return r;
+ }
+
+ r = gid_is_ok(search_uid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
+ else if (r > 0)
+ break;
+ }
+
+ i->gid_set = true;
+ i->gid = search_uid;
+ }
+
+ r = hashmap_ensure_allocated(&todo_gids, NULL);
+ if (r < 0)
+ return log_oom();
+
+ r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
+ if (r < 0)
+ return log_oom();
+
+ i->todo_group = true;
+ log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
+
+ return 0;
+}
+
+static int process_item(Item *i) {
+ int r;
+
+ assert(i);
+
+ switch (i->type) {
+
+ case ADD_USER:
+ r = add_group(i);
+ if (r < 0)
+ return r;
+
+ return add_user(i);
+
+ case ADD_GROUP: {
+ Item *j;
+
+ j = hashmap_get(users, i->name);
+ if (j) {
+ /* There's already user to be created for this
+ * name, let's process that in one step */
+
+ if (i->gid_set) {
+ j->gid = i->gid;
+ j->gid_set = true;
+ }
+
+ if (i->gid_path) {
+ r = free_and_strdup(&j->gid_path, i->gid_path);
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+ }
+
+ return add_group(i);
+ }
+
+ default:
+ assert_not_reached("Unknown item type");
+ }
+}
+
+static void item_free(Item *i) {
+
+ if (!i)
+ return;
+
+ free(i->name);
+ free(i->uid_path);
+ free(i->gid_path);
+ free(i->description);
+ free(i);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
+
+static int add_implicit(void) {
+ char *g, **l;
+ Iterator iterator;
+ int r;
+
+ /* Implicitly create additional users and groups, if they were listed in "m" lines */
+
+ HASHMAP_FOREACH_KEY(l, g, members, iterator) {
+ Item *i;
+ char **m;
+
+ i = hashmap_get(groups, g);
+ if (!i) {
+ _cleanup_(item_freep) Item *j = NULL;
+
+ r = hashmap_ensure_allocated(&groups, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ j = new0(Item, 1);
+ if (!j)
+ return log_oom();
+
+ j->type = ADD_GROUP;
+ j->name = strdup(g);
+ if (!j->name)
+ return log_oom();
+
+ r = hashmap_put(groups, j->name, j);
+ if (r < 0)
+ return log_oom();
+
+ log_debug("Adding implicit group '%s' due to m line", j->name);
+ j = NULL;
+ }
+
+ STRV_FOREACH(m, l) {
+
+ i = hashmap_get(users, *m);
+ if (!i) {
+ _cleanup_(item_freep) Item *j = NULL;
+
+ r = hashmap_ensure_allocated(&users, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ j = new0(Item, 1);
+ if (!j)
+ return log_oom();
+
+ j->type = ADD_USER;
+ j->name = strdup(*m);
+ if (!j->name)
+ return log_oom();
+
+ r = hashmap_put(users, j->name, j);
+ if (r < 0)
+ return log_oom();
+
+ log_debug("Adding implicit user '%s' due to m line", j->name);
+ j = NULL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static bool item_equal(Item *a, Item *b) {
+ assert(a);
+ assert(b);
+
+ if (a->type != b->type)
+ return false;
+
+ if (!streq_ptr(a->name, b->name))
+ return false;
+
+ if (!streq_ptr(a->uid_path, b->uid_path))
+ return false;
+
+ if (!streq_ptr(a->gid_path, b->gid_path))
+ return false;
+
+ if (!streq_ptr(a->description, b->description))
+ return false;
+
+ if (a->uid_set != b->uid_set)
+ return false;
+
+ if (a->uid_set && a->uid != b->uid)
+ return false;
+
+ if (a->gid_set != b->gid_set)
+ return false;
+
+ if (a->gid_set && a->gid != b->gid)
+ return false;
+
+ if (!streq_ptr(a->home, b->home))
+ return false;
+
+ return true;
+}
+
+static bool valid_user_group_name(const char *u) {
+ const char *i;
+ long sz;
+
+ if (isempty(u))
+ return false;
+
+ if (!(u[0] >= 'a' && u[0] <= 'z') &&
+ !(u[0] >= 'A' && u[0] <= 'Z') &&
+ u[0] != '_')
+ return false;
+
+ for (i = u+1; *i; i++) {
+ if (!(*i >= 'a' && *i <= 'z') &&
+ !(*i >= 'A' && *i <= 'Z') &&
+ !(*i >= '0' && *i <= '9') &&
+ *i != '_' &&
+ *i != '-')
+ return false;
+ }
+
+ sz = sysconf(_SC_LOGIN_NAME_MAX);
+ assert_se(sz > 0);
+
+ if ((size_t) (i-u) > (size_t) sz)
+ return false;
+
+ if ((size_t) (i-u) > UT_NAMESIZE - 1)
+ return false;
+
+ return true;
+}
+
+static bool valid_gecos(const char *d) {
+
+ if (!d)
+ return false;
+
+ if (!utf8_is_valid(d))
+ return false;
+
+ if (string_has_cc(d, NULL))
+ return false;
+
+ /* Colons are used as field separators, and hence not OK */
+ if (strchr(d, ':'))
+ return false;
+
+ return true;
+}
+
+static bool valid_home(const char *p) {
+
+ if (isempty(p))
+ return false;
+
+ if (!utf8_is_valid(p))
+ return false;
+
+ if (string_has_cc(p, NULL))
+ return false;
+
+ if (!path_is_absolute(p))
+ return false;
+
+ if (!path_is_safe(p))
+ return false;
+
+ /* Colons are used as field separators, and hence not OK */
+ if (strchr(p, ':'))
+ return false;
+
+ return true;
+}
+
+static int parse_line(const char *fname, unsigned line, const char *buffer) {
+
+ static const Specifier specifier_table[] = {
+ { 'm', specifier_machine_id, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'v', specifier_kernel_release, NULL },
+ {}
+ };
+
+ _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
+ _cleanup_(item_freep) Item *i = NULL;
+ Item *existing;
+ Hashmap *h;
+ int r;
+ const char *p;
+
+ assert(fname);
+ assert(line >= 1);
+ assert(buffer);
+
+ /* Parse columns */
+ p = buffer;
+ r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL);
+ if (r < 0) {
+ log_error("[%s:%u] Syntax error.", fname, line);
+ return r;
+ }
+ if (r < 2) {
+ log_error("[%s:%u] Missing action and name columns.", fname, line);
+ return -EINVAL;
+ }
+ if (!isempty(p)) {
+ log_error("[%s:%u] Trailing garbage.", fname, line);
+ return -EINVAL;
+ }
+
+ /* Verify action */
+ if (strlen(action) != 1) {
+ log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
+ return -EINVAL;
+ }
+
+ if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) {
+ log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
+ return -EBADMSG;
+ }
+
+ /* Verify name */
+ if (isempty(name) || streq(name, "-"))
+ name = mfree(name);
+
+ if (name) {
+ r = specifier_printf(name, specifier_table, NULL, &resolved_name);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
+ return r;
+ }
+
+ if (!valid_user_group_name(resolved_name)) {
+ log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
+ return -EINVAL;
+ }
+ }
+
+ /* Verify id */
+ if (isempty(id) || streq(id, "-"))
+ id = mfree(id);
+
+ if (id) {
+ r = specifier_printf(id, specifier_table, NULL, &resolved_id);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
+ return r;
+ }
+ }
+
+ /* Verify description */
+ if (isempty(description) || streq(description, "-"))
+ description = mfree(description);
+
+ if (description) {
+ if (!valid_gecos(description)) {
+ log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description);
+ return -EINVAL;
+ }
+ }
+
+ /* Verify home */
+ if (isempty(home) || streq(home, "-"))
+ home = mfree(home);
+
+ if (home) {
+ if (!valid_home(home)) {
+ log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home);
+ return -EINVAL;
+ }
+ }
+
+ switch (action[0]) {
+
+ case ADD_RANGE:
+ if (resolved_name) {
+ log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (!resolved_id) {
+ log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (description) {
+ log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (home) {
+ log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line);
+ return -EINVAL;
+ }
+
+ r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id);
+ if (r < 0) {
+ log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
+ return -EINVAL;
+ }
+
+ return 0;
+
+ case ADD_MEMBER: {
+ char **l;
+
+ /* Try to extend an existing member or group item */
+ if (!name) {
+ log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (!resolved_id) {
+ log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (!valid_user_group_name(resolved_id)) {
+ log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
+ return -EINVAL;
+ }
+
+ if (description) {
+ log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (home) {
+ log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line);
+ return -EINVAL;
+ }
+
+ r = hashmap_ensure_allocated(&members, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ l = hashmap_get(members, resolved_id);
+ if (l) {
+ /* A list for this group name already exists, let's append to it */
+ r = strv_push(&l, resolved_name);
+ if (r < 0)
+ return log_oom();
+
+ resolved_name = NULL;
+
+ assert_se(hashmap_update(members, resolved_id, l) >= 0);
+ } else {
+ /* No list for this group name exists yet, create one */
+
+ l = new0(char *, 2);
+ if (!l)
+ return -ENOMEM;
+
+ l[0] = resolved_name;
+ l[1] = NULL;
+
+ r = hashmap_put(members, resolved_id, l);
+ if (r < 0) {
+ free(l);
+ return log_oom();
+ }
+
+ resolved_id = resolved_name = NULL;
+ }
+
+ return 0;
+ }
+
+ case ADD_USER:
+ if (!name) {
+ log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line);
+ return -EINVAL;
+ }
+
+ r = hashmap_ensure_allocated(&users, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ i = new0(Item, 1);
+ if (!i)
+ return log_oom();
+
+ if (resolved_id) {
+ if (path_is_absolute(resolved_id)) {
+ i->uid_path = resolved_id;
+ resolved_id = NULL;
+
+ path_kill_slashes(i->uid_path);
+ } else {
+ r = parse_uid(resolved_id, &i->uid);
+ if (r < 0) {
+ log_error("Failed to parse UID: %s", id);
+ return -EBADMSG;
+ }
+
+ i->uid_set = true;
+ }
+ }
+
+ i->description = description;
+ description = NULL;
+
+ i->home = home;
+ home = NULL;
+
+ h = users;
+ break;
+
+ case ADD_GROUP:
+ if (!name) {
+ log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (description) {
+ log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
+ return -EINVAL;
+ }
+
+ if (home) {
+ log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line);
+ return -EINVAL;
+ }
+
+ r = hashmap_ensure_allocated(&groups, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ i = new0(Item, 1);
+ if (!i)
+ return log_oom();
+
+ if (resolved_id) {
+ if (path_is_absolute(resolved_id)) {
+ i->gid_path = resolved_id;
+ resolved_id = NULL;
+
+ path_kill_slashes(i->gid_path);
+ } else {
+ r = parse_gid(resolved_id, &i->gid);
+ if (r < 0) {
+ log_error("Failed to parse GID: %s", id);
+ return -EBADMSG;
+ }
+
+ i->gid_set = true;
+ }
+ }
+
+ h = groups;
+ break;
+
+ default:
+ return -EBADMSG;
+ }
+
+ i->type = action[0];
+ i->name = resolved_name;
+ resolved_name = NULL;
+
+ existing = hashmap_get(h, i->name);
+ if (existing) {
+
+ /* Two identical items are fine */
+ if (!item_equal(existing, i))
+ log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
+
+ return 0;
+ }
+
+ r = hashmap_put(h, i->name, i);
+ if (r < 0)
+ return log_oom();
+
+ i = NULL;
+ return 0;
+}
+
+static int read_config_file(const char *fn, bool ignore_enoent) {
+ _cleanup_fclose_ FILE *rf = NULL;
+ FILE *f = NULL;
+ char line[LINE_MAX];
+ unsigned v = 0;
+ int r = 0;
+
+ assert(fn);
+
+ if (streq(fn, "-"))
+ f = stdin;
+ else {
+ r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf);
+ if (r < 0) {
+ if (ignore_enoent && r == -ENOENT)
+ return 0;
+
+ return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn);
+ }
+
+ f = rf;
+ }
+
+ FOREACH_LINE(line, f, break) {
+ char *l;
+ int k;
+
+ v++;
+
+ l = strstrip(line);
+ if (*l == '#' || *l == 0)
+ continue;
+
+ k = parse_line(fn, v, l);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ if (ferror(f)) {
+ log_error_errno(errno, "Failed to read from file %s: %m", fn);
+ if (r == 0)
+ r = -EIO;
+ }
+
+ return r;
+}
+
+static void free_database(Hashmap *by_name, Hashmap *by_id) {
+ char *name;
+
+ for (;;) {
+ name = hashmap_first(by_id);
+ if (!name)
+ break;
+
+ hashmap_remove(by_name, name);
+
+ hashmap_steal_first_key(by_id);
+ free(name);
+ }
+
+ while ((name = hashmap_steal_first_key(by_name)))
+ free(name);
+
+ hashmap_free(by_name);
+ hashmap_free(by_id);
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+ "Creates system user accounts.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --root=PATH Operate on an alternate filesystem root\n"
+ , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_ROOT,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "root", required_argument, NULL, ARG_ROOT },
+ {}
+ };
+
+ int c, r;
+
+ 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_ROOT:
+ r = parse_path_argument_and_warn(optarg, true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+
+ _cleanup_close_ int lock = -1;
+ Iterator iterator;
+ int r, k;
+ Item *i;
+ char *n;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ r = mac_selinux_init();
+ if (r < 0) {
+ log_error_errno(r, "SELinux setup failed: %m");
+ goto finish;
+ }
+
+ if (optind < argc) {
+ int j;
+
+ for (j = optind; j < argc; j++) {
+ k = read_config_file(argv[j], false);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ } else {
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+
+ r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
+ if (r < 0) {
+ log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
+ goto finish;
+ }
+
+ STRV_FOREACH(f, files) {
+ k = read_config_file(*f, true);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ }
+
+ if (!uid_range) {
+ /* Default to default range of 1..SYSTEMD_UID_MAX */
+ r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX);
+ if (r < 0) {
+ log_oom();
+ goto finish;
+ }
+ }
+
+ r = add_implicit();
+ if (r < 0)
+ goto finish;
+
+ lock = take_etc_passwd_lock(arg_root);
+ if (lock < 0) {
+ log_error_errno(lock, "Failed to take lock: %m");
+ goto finish;
+ }
+
+ r = load_user_database();
+ if (r < 0) {
+ log_error_errno(r, "Failed to load user database: %m");
+ goto finish;
+ }
+
+ r = load_group_database();
+ if (r < 0) {
+ log_error_errno(r, "Failed to read group database: %m");
+ goto finish;
+ }
+
+ HASHMAP_FOREACH(i, groups, iterator)
+ process_item(i);
+
+ HASHMAP_FOREACH(i, users, iterator)
+ process_item(i);
+
+ r = write_files();
+ if (r < 0)
+ log_error_errno(r, "Failed to write files: %m");
+
+finish:
+ while ((i = hashmap_steal_first(groups)))
+ item_free(i);
+
+ while ((i = hashmap_steal_first(users)))
+ item_free(i);
+
+ while ((n = hashmap_first_key(members))) {
+ strv_free(hashmap_steal_first(members));
+ free(n);
+ }
+
+ hashmap_free(groups);
+ hashmap_free(users);
+ hashmap_free(members);
+ hashmap_free(todo_uids);
+ hashmap_free(todo_gids);
+
+ free_database(database_user, database_uid);
+ free_database(database_group, database_gid);
+
+ free(arg_root);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.completion.zsh b/src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.completion.zsh
new file mode 100644
index 0000000000..6ff02e5d98
--- /dev/null
+++ b/src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.completion.zsh
@@ -0,0 +1,13 @@
+#compdef systemd-tmpfiles
+
+_arguments \
+ {-h,--help}'[Show help]' \
+ '--version[Show package version]' \
+ '--create[Create, set ownership/permissions based on the config files.]' \
+ '--clean[Clean up all files and directories with an age parameter configured.]' \
+ '--remove[All files and directories marked with r, R in the configuration files are removed.]' \
+ '--boot[Execute actions only safe at boot]' \
+ '--prefix=[Only apply rules that apply to paths with the specified prefix.]' \
+ '--exclude-prefix=[Ignore rules that apply to paths with the specified prefix.]' \
+ '--root=[Operate on an alternate filesystem root]:directory:_directories' \
+ '*::files:_files'
diff --git a/src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.xml b/src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.xml
new file mode 100644
index 0000000000..c1aab51551
--- /dev/null
+++ b/src/grp-initprogs/systemd-tmpfiles/systemd-tmpfiles.xml
@@ -0,0 +1,200 @@
+<?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-tmpfiles"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-tmpfiles</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-tmpfiles</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-tmpfiles</refname>
+ <refname>systemd-tmpfiles-setup.service</refname>
+ <refname>systemd-tmpfiles-setup-dev.service</refname>
+ <refname>systemd-tmpfiles-clean.service</refname>
+ <refname>systemd-tmpfiles-clean.timer</refname>
+ <refpurpose>Creates, deletes and cleans up volatile
+ and temporary files and directories</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-tmpfiles</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="repeat"><replaceable>CONFIGFILE</replaceable></arg>
+ </cmdsynopsis>
+
+ <para><filename>systemd-tmpfiles-setup.service</filename></para>
+ <para><filename>systemd-tmpfiles-setup-dev.service</filename></para>
+ <para><filename>systemd-tmpfiles-clean.service</filename></para>
+ <para><filename>systemd-tmpfiles-clean.timer</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-tmpfiles</command> creates, deletes, and
+ cleans up volatile and temporary files and directories, based on
+ the configuration file format and location specified in
+ <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+ </para>
+
+ <para>If invoked with no arguments, it applies all directives from all configuration
+ files. If one or more absolute filenames are passed on the command line, only the
+ directives in these files are applied. If <literal>-</literal> is specified instead
+ of a filename, directives are read from standard input. If only the basename of a
+ configuration file is specified, all configuration directories as specified in
+ <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ are searched for a matching file.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--create</option></term>
+ <listitem><para>If this option is passed, all files and
+ directories marked with
+ <varname>f</varname>,
+ <varname>F</varname>,
+ <varname>w</varname>,
+ <varname>d</varname>,
+ <varname>D</varname>,
+ <varname>v</varname>,
+ <varname>p</varname>,
+ <varname>L</varname>,
+ <varname>c</varname>,
+ <varname>b</varname>,
+ <varname>m</varname>
+ in the configuration files are created or written to. Files
+ and directories marked with
+ <varname>z</varname>,
+ <varname>Z</varname>,
+ <varname>t</varname>,
+ <varname>T</varname>,
+ <varname>a</varname>, and
+ <varname>A</varname> have their ownership, access mode and
+ security labels set. </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--clean</option></term>
+ <listitem><para>If this option is passed, all files and
+ directories with an age parameter configured will be cleaned
+ up.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--remove</option></term>
+ <listitem><para>If this option is passed, the contents of
+ directories marked with <varname>D</varname> or
+ <varname>R</varname>, and files or directories themselves
+ marked with <varname>r</varname> or <varname>R</varname> are
+ removed.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--boot</option></term>
+ <listitem><para>Also execute lines with an exclamation mark.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--prefix=<replaceable>path</replaceable></option></term>
+ <listitem><para>Only apply rules with paths that start with
+ the specified prefix. This option can be specified multiple
+ times.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--exclude-prefix=<replaceable>path</replaceable></option></term>
+ <listitem><para>Ignore rules with paths that start with the
+ specified prefix. This option can be specified multiple
+ times.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--root=<replaceable>root</replaceable></option></term>
+ <listitem><para>Takes a directory path as an argument. All
+ paths will be prefixed with the given alternate
+ <replaceable>root</replaceable> path, including config search
+ paths. </para></listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+
+ <para>It is possible to combine <option>--create</option>,
+ <option>--clean</option>, and <option>--remove</option> in one
+ invocation. For example, during boot the following command line is
+ executed to ensure that all temporary and volatile directories are
+ removed and created according to the configuration file:</para>
+
+ <programlisting>systemd-tmpfiles --remove --create</programlisting>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Unprivileged --cleanup operation</title>
+
+ <para><command>systemd-tmpfiles</command> tries to avoid changing
+ the access and modification times on the directories it accesses,
+ which requires <constant>CAP_ADMIN</constant> privileges. When
+ running as non-root, directories which are checked for files to
+ clean up will have their access time bumped, which might prevent
+ their cleanup.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-initprogs/systemd-tmpfiles/tmpfiles.c b/src/grp-initprogs/systemd-tmpfiles/tmpfiles.c
new file mode 100644
index 0000000000..2053d35a67
--- /dev/null
+++ b/src/grp-initprogs/systemd-tmpfiles/tmpfiles.c
@@ -0,0 +1,2343 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering, Kay Sievers
+ 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 <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <glob.h>
+#include <limits.h>
+#include <linux/fs.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "acl-util.h"
+#include "alloc-util.h"
+#include "btrfs-util.h"
+#include "capability-util.h"
+#include "chattr-util.h"
+#include "conf-files.h"
+#include "copy.h"
+#include "def.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "formats-util.h"
+#include "fs-util.h"
+#include "glob-util.h"
+#include "io-util.h"
+#include "label.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "selinux-util.h"
+#include "set.h"
+#include "specifier.h"
+#include "stat-util.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "umask-util.h"
+#include "user-util.h"
+#include "util.h"
+
+/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates
+ * them in the file system. This is intended to be used to create
+ * properly owned directories beneath /tmp, /var/tmp, /run, which are
+ * volatile and hence need to be recreated on bootup. */
+
+typedef enum ItemType {
+ /* These ones take file names */
+ CREATE_FILE = 'f',
+ TRUNCATE_FILE = 'F',
+ CREATE_DIRECTORY = 'd',
+ TRUNCATE_DIRECTORY = 'D',
+ CREATE_SUBVOLUME = 'v',
+ CREATE_SUBVOLUME_INHERIT_QUOTA = 'q',
+ CREATE_SUBVOLUME_NEW_QUOTA = 'Q',
+ CREATE_FIFO = 'p',
+ CREATE_SYMLINK = 'L',
+ CREATE_CHAR_DEVICE = 'c',
+ CREATE_BLOCK_DEVICE = 'b',
+ COPY_FILES = 'C',
+
+ /* These ones take globs */
+ WRITE_FILE = 'w',
+ EMPTY_DIRECTORY = 'e',
+ SET_XATTR = 't',
+ RECURSIVE_SET_XATTR = 'T',
+ SET_ACL = 'a',
+ RECURSIVE_SET_ACL = 'A',
+ SET_ATTRIBUTE = 'h',
+ RECURSIVE_SET_ATTRIBUTE = 'H',
+ IGNORE_PATH = 'x',
+ IGNORE_DIRECTORY_PATH = 'X',
+ REMOVE_PATH = 'r',
+ RECURSIVE_REMOVE_PATH = 'R',
+ RELABEL_PATH = 'z',
+ RECURSIVE_RELABEL_PATH = 'Z',
+ ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */
+} ItemType;
+
+typedef struct Item {
+ ItemType type;
+
+ char *path;
+ char *argument;
+ char **xattrs;
+#ifdef HAVE_ACL
+ acl_t acl_access;
+ acl_t acl_default;
+#endif
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+ usec_t age;
+
+ dev_t major_minor;
+ unsigned attribute_value;
+ unsigned attribute_mask;
+
+ bool uid_set:1;
+ bool gid_set:1;
+ bool mode_set:1;
+ bool age_set:1;
+ bool mask_perms:1;
+ bool attribute_set:1;
+
+ bool keep_first_level:1;
+
+ bool force:1;
+
+ bool done:1;
+} Item;
+
+typedef struct ItemArray {
+ Item *items;
+ size_t count;
+ size_t size;
+} ItemArray;
+
+static bool arg_create = false;
+static bool arg_clean = false;
+static bool arg_remove = false;
+static bool arg_boot = false;
+
+static char **arg_include_prefixes = NULL;
+static char **arg_exclude_prefixes = NULL;
+static char *arg_root = NULL;
+
+static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d");
+
+#define MAX_DEPTH 256
+
+static OrderedHashmap *items = NULL, *globs = NULL;
+static Set *unix_sockets = NULL;
+
+static const Specifier specifier_table[] = {
+ { 'm', specifier_machine_id, NULL },
+ { 'b', specifier_boot_id, NULL },
+ { 'H', specifier_host_name, NULL },
+ { 'v', specifier_kernel_release, NULL },
+ {}
+};
+
+static bool needs_glob(ItemType t) {
+ return IN_SET(t,
+ WRITE_FILE,
+ IGNORE_PATH,
+ IGNORE_DIRECTORY_PATH,
+ REMOVE_PATH,
+ RECURSIVE_REMOVE_PATH,
+ EMPTY_DIRECTORY,
+ ADJUST_MODE,
+ RELABEL_PATH,
+ RECURSIVE_RELABEL_PATH,
+ SET_XATTR,
+ RECURSIVE_SET_XATTR,
+ SET_ACL,
+ RECURSIVE_SET_ACL,
+ SET_ATTRIBUTE,
+ RECURSIVE_SET_ATTRIBUTE);
+}
+
+static bool takes_ownership(ItemType t) {
+ return IN_SET(t,
+ CREATE_FILE,
+ TRUNCATE_FILE,
+ CREATE_DIRECTORY,
+ EMPTY_DIRECTORY,
+ TRUNCATE_DIRECTORY,
+ CREATE_SUBVOLUME,
+ CREATE_SUBVOLUME_INHERIT_QUOTA,
+ CREATE_SUBVOLUME_NEW_QUOTA,
+ CREATE_FIFO,
+ CREATE_SYMLINK,
+ CREATE_CHAR_DEVICE,
+ CREATE_BLOCK_DEVICE,
+ COPY_FILES,
+ WRITE_FILE,
+ IGNORE_PATH,
+ IGNORE_DIRECTORY_PATH,
+ REMOVE_PATH,
+ RECURSIVE_REMOVE_PATH);
+}
+
+static struct Item* find_glob(OrderedHashmap *h, const char *match) {
+ ItemArray *j;
+ Iterator i;
+
+ ORDERED_HASHMAP_FOREACH(j, h, i) {
+ unsigned n;
+
+ for (n = 0; n < j->count; n++) {
+ Item *item = j->items + n;
+
+ if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0)
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+static void load_unix_sockets(void) {
+ _cleanup_fclose_ FILE *f = NULL;
+ char line[LINE_MAX];
+
+ if (unix_sockets)
+ return;
+
+ /* We maintain a cache of the sockets we found in
+ * /proc/net/unix to speed things up a little. */
+
+ unix_sockets = set_new(&string_hash_ops);
+ if (!unix_sockets)
+ return;
+
+ f = fopen("/proc/net/unix", "re");
+ if (!f)
+ return;
+
+ /* Skip header */
+ if (!fgets(line, sizeof(line), f))
+ goto fail;
+
+ for (;;) {
+ char *p, *s;
+ int k;
+
+ if (!fgets(line, sizeof(line), f))
+ break;
+
+ truncate_nl(line);
+
+ p = strchr(line, ':');
+ if (!p)
+ continue;
+
+ if (strlen(p) < 37)
+ continue;
+
+ p += 37;
+ p += strspn(p, WHITESPACE);
+ p += strcspn(p, WHITESPACE); /* skip one more word */
+ p += strspn(p, WHITESPACE);
+
+ if (*p != '/')
+ continue;
+
+ s = strdup(p);
+ if (!s)
+ goto fail;
+
+ path_kill_slashes(s);
+
+ k = set_consume(unix_sockets, s);
+ if (k < 0 && k != -EEXIST)
+ goto fail;
+ }
+
+ return;
+
+fail:
+ set_free_free(unix_sockets);
+ unix_sockets = NULL;
+}
+
+static bool unix_socket_alive(const char *fn) {
+ assert(fn);
+
+ load_unix_sockets();
+
+ if (unix_sockets)
+ return !!set_get(unix_sockets, (char*) fn);
+
+ /* We don't know, so assume yes */
+ return true;
+}
+
+static int dir_is_mount_point(DIR *d, const char *subdir) {
+
+ union file_handle_union h = FILE_HANDLE_INIT;
+ int mount_id_parent, mount_id;
+ int r_p, r;
+
+ r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0);
+ if (r_p < 0)
+ r_p = -errno;
+
+ h.handle.handle_bytes = MAX_HANDLE_SZ;
+ r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0);
+ if (r < 0)
+ r = -errno;
+
+ /* got no handle; make no assumptions, return error */
+ if (r_p < 0 && r < 0)
+ return r_p;
+
+ /* got both handles; if they differ, it is a mount point */
+ if (r_p >= 0 && r >= 0)
+ return mount_id_parent != mount_id;
+
+ /* got only one handle; assume different mount points if one
+ * of both queries was not supported by the filesystem */
+ if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP)
+ return true;
+
+ /* return error */
+ if (r_p < 0)
+ return r_p;
+ return r;
+}
+
+static DIR* xopendirat_nomod(int dirfd, const char *path) {
+ DIR *dir;
+
+ dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME);
+ if (dir)
+ return dir;
+
+ log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path);
+ if (errno != EPERM)
+ return NULL;
+
+ dir = xopendirat(dirfd, path, O_NOFOLLOW);
+ if (!dir)
+ log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path);
+
+ return dir;
+}
+
+static DIR* opendir_nomod(const char *path) {
+ return xopendirat_nomod(AT_FDCWD, path);
+}
+
+static int dir_cleanup(
+ Item *i,
+ const char *p,
+ DIR *d,
+ const struct stat *ds,
+ usec_t cutoff,
+ dev_t rootdev,
+ bool mountpoint,
+ int maxdepth,
+ bool keep_this_level) {
+
+ struct dirent *dent;
+ struct timespec times[2];
+ bool deleted = false;
+ int r = 0;
+
+ while ((dent = readdir(d))) {
+ struct stat s;
+ usec_t age;
+ _cleanup_free_ char *sub_path = NULL;
+
+ if (STR_IN_SET(dent->d_name, ".", ".."))
+ continue;
+
+ if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno == ENOENT)
+ continue;
+
+ /* FUSE, NFS mounts, SELinux might return EACCES */
+ if (errno == EACCES)
+ log_debug_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name);
+ else
+ log_error_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name);
+ r = -errno;
+ continue;
+ }
+
+ /* Stay on the same filesystem */
+ if (s.st_dev != rootdev) {
+ log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name);
+ continue;
+ }
+
+ /* Try to detect bind mounts of the same filesystem instance; they
+ * do not differ in device major/minors. This type of query is not
+ * supported on all kernels or filesystem types though. */
+ if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0) {
+ log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.",
+ p, dent->d_name);
+ continue;
+ }
+
+ /* Do not delete read-only files owned by root */
+ if (s.st_uid == 0 && !(s.st_mode & S_IWUSR)) {
+ log_debug("Ignoring \"%s/%s\": read-only and owner by root.", p, dent->d_name);
+ continue;
+ }
+
+ sub_path = strjoin(p, "/", dent->d_name, NULL);
+ if (!sub_path) {
+ r = log_oom();
+ goto finish;
+ }
+
+ /* Is there an item configured for this path? */
+ if (ordered_hashmap_get(items, sub_path)) {
+ log_debug("Ignoring \"%s\": a separate entry exists.", sub_path);
+ continue;
+ }
+
+ if (find_glob(globs, sub_path)) {
+ log_debug("Ignoring \"%s\": a separate glob exists.", sub_path);
+ continue;
+ }
+
+ if (S_ISDIR(s.st_mode)) {
+
+ if (mountpoint &&
+ streq(dent->d_name, "lost+found") &&
+ s.st_uid == 0) {
+ log_debug("Ignoring \"%s\".", sub_path);
+ continue;
+ }
+
+ if (maxdepth <= 0)
+ log_warning("Reached max depth on \"%s\".", sub_path);
+ else {
+ _cleanup_closedir_ DIR *sub_dir;
+ int q;
+
+ sub_dir = xopendirat_nomod(dirfd(d), dent->d_name);
+ if (!sub_dir) {
+ if (errno != ENOENT)
+ r = log_error_errno(errno, "opendir(%s) failed: %m", sub_path);
+
+ continue;
+ }
+
+ q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false);
+ if (q < 0)
+ r = q;
+ }
+
+ /* Note: if you are wondering why we don't
+ * support the sticky bit for excluding
+ * directories from cleaning like we do it for
+ * other file system objects: well, the sticky
+ * bit already has a meaning for directories,
+ * so we don't want to overload that. */
+
+ if (keep_this_level) {
+ log_debug("Keeping \"%s\".", sub_path);
+ continue;
+ }
+
+ /* Ignore ctime, we change it when deleting */
+ age = timespec_load(&s.st_mtim);
+ if (age >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ /* Follows spelling in stat(1). */
+ log_debug("Directory \"%s\": modify time %s is too new.",
+ sub_path,
+ format_timestamp_us(a, sizeof(a), age));
+ continue;
+ }
+
+ age = timespec_load(&s.st_atim);
+ if (age >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ log_debug("Directory \"%s\": access time %s is too new.",
+ sub_path,
+ format_timestamp_us(a, sizeof(a), age));
+ continue;
+ }
+
+ log_debug("Removing directory \"%s\".", sub_path);
+ if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0)
+ if (errno != ENOENT && errno != ENOTEMPTY) {
+ log_error_errno(errno, "rmdir(%s): %m", sub_path);
+ r = -errno;
+ }
+
+ } else {
+ /* Skip files for which the sticky bit is
+ * set. These are semantics we define, and are
+ * unknown elsewhere. See XDG_RUNTIME_DIR
+ * specification for details. */
+ if (s.st_mode & S_ISVTX) {
+ log_debug("Skipping \"%s\": sticky bit set.", sub_path);
+ continue;
+ }
+
+ if (mountpoint && S_ISREG(s.st_mode))
+ if (s.st_uid == 0 && STR_IN_SET(dent->d_name,
+ ".journal",
+ "aquota.user",
+ "aquota.group")) {
+ log_debug("Skipping \"%s\".", sub_path);
+ continue;
+ }
+
+ /* Ignore sockets that are listed in /proc/net/unix */
+ if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) {
+ log_debug("Skipping \"%s\": live socket.", sub_path);
+ continue;
+ }
+
+ /* Ignore device nodes */
+ if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) {
+ log_debug("Skipping \"%s\": a device.", sub_path);
+ continue;
+ }
+
+ /* Keep files on this level around if this is
+ * requested */
+ if (keep_this_level) {
+ log_debug("Keeping \"%s\".", sub_path);
+ continue;
+ }
+
+ age = timespec_load(&s.st_mtim);
+ if (age >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ /* Follows spelling in stat(1). */
+ log_debug("File \"%s\": modify time %s is too new.",
+ sub_path,
+ format_timestamp_us(a, sizeof(a), age));
+ continue;
+ }
+
+ age = timespec_load(&s.st_atim);
+ if (age >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ log_debug("File \"%s\": access time %s is too new.",
+ sub_path,
+ format_timestamp_us(a, sizeof(a), age));
+ continue;
+ }
+
+ age = timespec_load(&s.st_ctim);
+ if (age >= cutoff) {
+ char a[FORMAT_TIMESTAMP_MAX];
+ log_debug("File \"%s\": change time %s is too new.",
+ sub_path,
+ format_timestamp_us(a, sizeof(a), age));
+ continue;
+ }
+
+ log_debug("unlink \"%s\"", sub_path);
+
+ if (unlinkat(dirfd(d), dent->d_name, 0) < 0)
+ if (errno != ENOENT)
+ r = log_error_errno(errno, "unlink(%s): %m", sub_path);
+
+ deleted = true;
+ }
+ }
+
+finish:
+ if (deleted) {
+ usec_t age1, age2;
+ char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX];
+
+ /* Restore original directory timestamps */
+ times[0] = ds->st_atim;
+ times[1] = ds->st_mtim;
+
+ age1 = timespec_load(&ds->st_atim);
+ age2 = timespec_load(&ds->st_mtim);
+ log_debug("Restoring access and modification time on \"%s\": %s, %s",
+ p,
+ format_timestamp_us(a, sizeof(a), age1),
+ format_timestamp_us(b, sizeof(b), age2));
+ if (futimens(dirfd(d), times) < 0)
+ log_error_errno(errno, "utimensat(%s): %m", p);
+ }
+
+ return r;
+}
+
+static int path_set_perms(Item *i, const char *path) {
+ _cleanup_close_ int fd = -1;
+ struct stat st;
+
+ assert(i);
+ assert(path);
+
+ /* We open the file with O_PATH here, to make the operation
+ * somewhat atomic. Also there's unfortunately no fchmodat()
+ * with AT_SYMLINK_NOFOLLOW, hence we emulate it here via
+ * O_PATH. */
+
+ fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0)
+ return log_error_errno(errno, "Adjusting owner and mode for %s failed: %m", path);
+
+ if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
+ return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
+
+ if (S_ISLNK(st.st_mode))
+ log_debug("Skipping mode an owner fix for symlink %s.", path);
+ else {
+ char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+ xsprintf(fn, "/proc/self/fd/%i", fd);
+
+ /* not using i->path directly because it may be a glob */
+ if (i->mode_set) {
+ mode_t m = i->mode;
+
+ if (i->mask_perms) {
+ if (!(st.st_mode & 0111))
+ m &= ~0111;
+ if (!(st.st_mode & 0222))
+ m &= ~0222;
+ if (!(st.st_mode & 0444))
+ m &= ~0444;
+ if (!S_ISDIR(st.st_mode))
+ m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */
+ }
+
+ if (m == (st.st_mode & 07777))
+ log_debug("\"%s\" has right mode %o", path, st.st_mode);
+ else {
+ log_debug("chmod \"%s\" to mode %o", path, m);
+ if (chmod(fn, m) < 0)
+ return log_error_errno(errno, "chmod(%s) failed: %m", path);
+ }
+ }
+
+ if ((i->uid != st.st_uid || i->gid != st.st_gid) &&
+ (i->uid_set || i->gid_set)) {
+ log_debug("chown \"%s\" to "UID_FMT"."GID_FMT,
+ path,
+ i->uid_set ? i->uid : UID_INVALID,
+ i->gid_set ? i->gid : GID_INVALID);
+ if (chown(fn,
+ i->uid_set ? i->uid : UID_INVALID,
+ i->gid_set ? i->gid : GID_INVALID) < 0)
+ return log_error_errno(errno, "chown(%s) failed: %m", path);
+ }
+ }
+
+ fd = safe_close(fd);
+
+ return label_fix(path, false, false);
+}
+
+static int parse_xattrs_from_arg(Item *i) {
+ const char *p;
+ int r;
+
+ assert(i);
+ assert(i->argument);
+
+ p = i->argument;
+
+ for (;;) {
+ _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL;
+
+ r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p);
+ if (r <= 0)
+ break;
+
+ r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced);
+ if (r < 0)
+ return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr);
+
+ r = split_pair(xattr_replaced, "=", &name, &value);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr);
+ continue;
+ }
+
+ if (isempty(name) || isempty(value)) {
+ log_warning("Malformed extended attribute found, ignoring: %s", xattr);
+ continue;
+ }
+
+ if (strv_push_pair(&i->xattrs, name, value) < 0)
+ return log_oom();
+
+ name = value = NULL;
+ }
+
+ return 0;
+}
+
+static int path_set_xattrs(Item *i, const char *path) {
+ char **name, **value;
+
+ assert(i);
+ assert(path);
+
+ STRV_FOREACH_PAIR(name, value, i->xattrs) {
+ int n;
+
+ n = strlen(*value);
+ log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path);
+ if (lsetxattr(path, *name, *value, n, 0) < 0) {
+ log_error("Setting extended attribute %s=%s on %s failed: %m", *name, *value, path);
+ return -errno;
+ }
+ }
+ return 0;
+}
+
+static int parse_acls_from_arg(Item *item) {
+#ifdef HAVE_ACL
+ int r;
+
+ assert(item);
+
+ /* If force (= modify) is set, we will not modify the acl
+ * afterwards, so the mask can be added now if necessary. */
+
+ r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->force);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse ACL \"%s\": %m. Ignoring", item->argument);
+#else
+ log_warning_errno(ENOSYS, "ACLs are not supported. Ignoring");
+#endif
+
+ return 0;
+}
+
+#ifdef HAVE_ACL
+static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) {
+ _cleanup_(acl_free_charpp) char *t = NULL;
+ _cleanup_(acl_freep) acl_t dup = NULL;
+ int r;
+
+ /* Returns 0 for success, positive error if already warned,
+ * negative error otherwise. */
+
+ if (modify) {
+ r = acls_for_file(path, type, acl, &dup);
+ if (r < 0)
+ return r;
+
+ r = calc_acl_mask_if_needed(&dup);
+ if (r < 0)
+ return r;
+ } else {
+ dup = acl_dup(acl);
+ if (!dup)
+ return -errno;
+
+ /* the mask was already added earlier if needed */
+ }
+
+ r = add_base_acls_if_needed(&dup, path);
+ if (r < 0)
+ return r;
+
+ t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE);
+ log_debug("Setting %s ACL %s on %s.",
+ type == ACL_TYPE_ACCESS ? "access" : "default",
+ strna(t), pretty);
+
+ r = acl_set_file(path, type, dup);
+ if (r < 0)
+ /* Return positive to indicate we already warned */
+ return -log_error_errno(errno,
+ "Setting %s ACL \"%s\" on %s failed: %m",
+ type == ACL_TYPE_ACCESS ? "access" : "default",
+ strna(t), pretty);
+
+ return 0;
+}
+#endif
+
+static int path_set_acls(Item *item, const char *path) {
+ int r = 0;
+#ifdef HAVE_ACL
+ char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+ _cleanup_close_ int fd = -1;
+ struct stat st;
+
+ assert(item);
+ assert(path);
+
+ fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
+ if (fd < 0)
+ return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path);
+
+ if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
+ return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
+
+ if (S_ISLNK(st.st_mode)) {
+ log_debug("Skipping ACL fix for symlink %s.", path);
+ return 0;
+ }
+
+ xsprintf(fn, "/proc/self/fd/%i", fd);
+
+ if (item->acl_access)
+ r = path_set_acl(fn, path, ACL_TYPE_ACCESS, item->acl_access, item->force);
+
+ if (r == 0 && item->acl_default)
+ r = path_set_acl(fn, path, ACL_TYPE_DEFAULT, item->acl_default, item->force);
+
+ if (r > 0)
+ return -r; /* already warned */
+ else if (r == -EOPNOTSUPP) {
+ log_debug_errno(r, "ACLs not supported by file system at %s", path);
+ return 0;
+ } else if (r < 0)
+ log_error_errno(r, "ACL operation on \"%s\" failed: %m", path);
+#endif
+ return r;
+}
+
+#define ATTRIBUTES_ALL \
+ (FS_NOATIME_FL | \
+ FS_SYNC_FL | \
+ FS_DIRSYNC_FL | \
+ FS_APPEND_FL | \
+ FS_COMPR_FL | \
+ FS_NODUMP_FL | \
+ FS_EXTENT_FL | \
+ FS_IMMUTABLE_FL | \
+ FS_JOURNAL_DATA_FL | \
+ FS_SECRM_FL | \
+ FS_UNRM_FL | \
+ FS_NOTAIL_FL | \
+ FS_TOPDIR_FL | \
+ FS_NOCOW_FL)
+
+static int parse_attribute_from_arg(Item *item) {
+
+ static const struct {
+ char character;
+ unsigned value;
+ } attributes[] = {
+ { 'A', FS_NOATIME_FL }, /* do not update atime */
+ { 'S', FS_SYNC_FL }, /* Synchronous updates */
+ { 'D', FS_DIRSYNC_FL }, /* dirsync behaviour (directories only) */
+ { 'a', FS_APPEND_FL }, /* writes to file may only append */
+ { 'c', FS_COMPR_FL }, /* Compress file */
+ { 'd', FS_NODUMP_FL }, /* do not dump file */
+ { 'e', FS_EXTENT_FL }, /* Top of directory hierarchies*/
+ { 'i', FS_IMMUTABLE_FL }, /* Immutable file */
+ { 'j', FS_JOURNAL_DATA_FL }, /* Reserved for ext3 */
+ { 's', FS_SECRM_FL }, /* Secure deletion */
+ { 'u', FS_UNRM_FL }, /* Undelete */
+ { 't', FS_NOTAIL_FL }, /* file tail should not be merged */
+ { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies*/
+ { 'C', FS_NOCOW_FL }, /* Do not cow file */
+ };
+
+ enum {
+ MODE_ADD,
+ MODE_DEL,
+ MODE_SET
+ } mode = MODE_ADD;
+
+ unsigned value = 0, mask = 0;
+ const char *p;
+
+ assert(item);
+
+ p = item->argument;
+ if (p) {
+ if (*p == '+') {
+ mode = MODE_ADD;
+ p++;
+ } else if (*p == '-') {
+ mode = MODE_DEL;
+ p++;
+ } else if (*p == '=') {
+ mode = MODE_SET;
+ p++;
+ }
+ }
+
+ if (isempty(p) && mode != MODE_SET) {
+ log_error("Setting file attribute on '%s' needs an attribute specification.", item->path);
+ return -EINVAL;
+ }
+
+ for (; p && *p ; p++) {
+ unsigned i, v;
+
+ for (i = 0; i < ELEMENTSOF(attributes); i++)
+ if (*p == attributes[i].character)
+ break;
+
+ if (i >= ELEMENTSOF(attributes)) {
+ log_error("Unknown file attribute '%c' on '%s'.", *p, item->path);
+ return -EINVAL;
+ }
+
+ v = attributes[i].value;
+
+ SET_FLAG(value, v, (mode == MODE_ADD || mode == MODE_SET));
+
+ mask |= v;
+ }
+
+ if (mode == MODE_SET)
+ mask |= ATTRIBUTES_ALL;
+
+ assert(mask != 0);
+
+ item->attribute_mask = mask;
+ item->attribute_value = value;
+ item->attribute_set = true;
+
+ return 0;
+}
+
+static int path_set_attribute(Item *item, const char *path) {
+ _cleanup_close_ int fd = -1;
+ struct stat st;
+ unsigned f;
+ int r;
+
+ if (!item->attribute_set || item->attribute_mask == 0)
+ return 0;
+
+ fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOATIME|O_NOFOLLOW);
+ if (fd < 0) {
+ if (errno == ELOOP)
+ return log_error_errno(errno, "Skipping file attributes adjustment on symlink %s.", path);
+
+ return log_error_errno(errno, "Cannot open '%s': %m", path);
+ }
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(errno, "Cannot stat '%s': %m", path);
+
+ /* Issuing the file attribute ioctls on device nodes is not
+ * safe, as that will be delivered to the drivers, not the
+ * file system containing the device node. */
+ if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) {
+ log_error("Setting file flags is only supported on regular files and directories, cannot set on '%s'.", path);
+ return -EINVAL;
+ }
+
+ f = item->attribute_value & item->attribute_mask;
+
+ /* Mask away directory-specific flags */
+ if (!S_ISDIR(st.st_mode))
+ f &= ~FS_DIRSYNC_FL;
+
+ r = chattr_fd(fd, f, item->attribute_mask);
+ if (r < 0)
+ log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING,
+ r,
+ "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m",
+ path, item->attribute_value, item->attribute_mask);
+
+ return 0;
+}
+
+static int write_one_file(Item *i, const char *path) {
+ _cleanup_close_ int fd = -1;
+ int flags, r = 0;
+ struct stat st;
+
+ assert(i);
+ assert(path);
+
+ flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND|O_NOFOLLOW :
+ i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0;
+
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(path, S_IFREG);
+ fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode);
+ mac_selinux_create_file_clear();
+ }
+
+ if (fd < 0) {
+ if (i->type == WRITE_FILE && errno == ENOENT) {
+ log_debug_errno(errno, "Not writing \"%s\": %m", path);
+ return 0;
+ }
+
+ r = -errno;
+ if (!i->argument && errno == EROFS && stat(path, &st) == 0 &&
+ (i->type == CREATE_FILE || st.st_size == 0))
+ goto check_mode;
+
+ return log_error_errno(r, "Failed to create file %s: %m", path);
+ }
+
+ if (i->argument) {
+ _cleanup_free_ char *unescaped = NULL, *replaced = NULL;
+
+ log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path);
+
+ r = cunescape(i->argument, 0, &unescaped);
+ if (r < 0)
+ return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument);
+
+ r = specifier_printf(unescaped, specifier_table, NULL, &replaced);
+ if (r < 0)
+ return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped);
+
+ r = loop_write(fd, replaced, strlen(replaced), false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to write file \"%s\": %m", path);
+ } else
+ log_debug("\"%s\" has been created.", path);
+
+ fd = safe_close(fd);
+
+ if (stat(path, &st) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", path);
+
+ check_mode:
+ if (!S_ISREG(st.st_mode)) {
+ log_error("%s is not a file.", path);
+ return -EEXIST;
+ }
+
+ r = path_set_perms(i, path);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+typedef int (*action_t)(Item *, const char *);
+
+static int item_do_children(Item *i, const char *path, action_t action) {
+ _cleanup_closedir_ DIR *d;
+ int r = 0;
+
+ assert(i);
+ assert(path);
+
+ /* This returns the first error we run into, but nevertheless
+ * tries to go on */
+
+ d = opendir_nomod(path);
+ if (!d)
+ return errno == ENOENT || errno == ENOTDIR ? 0 : -errno;
+
+ for (;;) {
+ _cleanup_free_ char *p = NULL;
+ struct dirent *de;
+ int q;
+
+ errno = 0;
+ de = readdir(d);
+ if (!de) {
+ if (errno > 0 && r == 0)
+ r = -errno;
+
+ break;
+ }
+
+ if (STR_IN_SET(de->d_name, ".", ".."))
+ continue;
+
+ p = strjoin(path, "/", de->d_name, NULL);
+ if (!p)
+ return -ENOMEM;
+
+ q = action(i, p);
+ if (q < 0 && q != -ENOENT && r == 0)
+ r = q;
+
+ if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) {
+ q = item_do_children(i, p, action);
+ if (q < 0 && r == 0)
+ r = q;
+ }
+ }
+
+ return r;
+}
+
+static int glob_item(Item *i, action_t action, bool recursive) {
+ _cleanup_globfree_ glob_t g = {
+ .gl_closedir = (void (*)(void *)) closedir,
+ .gl_readdir = (struct dirent *(*)(void *)) readdir,
+ .gl_opendir = (void *(*)(const char *)) opendir_nomod,
+ .gl_lstat = lstat,
+ .gl_stat = stat,
+ };
+ int r = 0, k;
+ char **fn;
+
+ errno = 0;
+ k = glob(i->path, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g);
+ if (k != 0 && k != GLOB_NOMATCH)
+ return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path);
+
+ STRV_FOREACH(fn, g.gl_pathv) {
+ k = action(i, *fn);
+ if (k < 0 && r == 0)
+ r = k;
+
+ if (recursive) {
+ k = item_do_children(i, *fn, action);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ }
+
+ return r;
+}
+
+typedef enum {
+ CREATION_NORMAL,
+ CREATION_EXISTING,
+ CREATION_FORCE,
+ _CREATION_MODE_MAX,
+ _CREATION_MODE_INVALID = -1
+} CreationMode;
+
+static const char *creation_mode_verb_table[_CREATION_MODE_MAX] = {
+ [CREATION_NORMAL] = "Created",
+ [CREATION_EXISTING] = "Found existing",
+ [CREATION_FORCE] = "Created replacement",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode);
+
+static int create_item(Item *i) {
+ _cleanup_free_ char *resolved = NULL;
+ struct stat st;
+ int r = 0;
+ int q = 0;
+ CreationMode creation;
+
+ assert(i);
+
+ log_debug("Running create action for entry %c %s", (char) i->type, i->path);
+
+ switch (i->type) {
+
+ case IGNORE_PATH:
+ case IGNORE_DIRECTORY_PATH:
+ case REMOVE_PATH:
+ case RECURSIVE_REMOVE_PATH:
+ return 0;
+
+ case CREATE_FILE:
+ case TRUNCATE_FILE:
+ r = write_one_file(i, i->path);
+ if (r < 0)
+ return r;
+ break;
+
+ case COPY_FILES: {
+ r = specifier_printf(i->argument, specifier_table, NULL, &resolved);
+ if (r < 0)
+ return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument);
+
+ log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path);
+ r = copy_tree(resolved, i->path, false);
+
+ if (r == -EROFS && stat(i->path, &st) == 0)
+ r = -EEXIST;
+
+ if (r < 0) {
+ struct stat a, b;
+
+ if (r != -EEXIST)
+ return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
+
+ if (stat(resolved, &a) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", resolved);
+
+ if (stat(i->path, &b) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", i->path);
+
+ if ((a.st_mode ^ b.st_mode) & S_IFMT) {
+ log_debug("Can't copy to %s, file exists already and is of different type", i->path);
+ return 0;
+ }
+ }
+
+ r = path_set_perms(i, i->path);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case WRITE_FILE:
+ r = glob_item(i, write_one_file, false);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case CREATE_DIRECTORY:
+ case TRUNCATE_DIRECTORY:
+ case CREATE_SUBVOLUME:
+ case CREATE_SUBVOLUME_INHERIT_QUOTA:
+ case CREATE_SUBVOLUME_NEW_QUOTA:
+ RUN_WITH_UMASK(0000)
+ mkdir_parents_label(i->path, 0755);
+
+ if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) {
+
+ if (btrfs_is_subvol(isempty(arg_root) ? "/" : arg_root) <= 0)
+
+ /* Don't create a subvolume unless the
+ * root directory is one, too. We do
+ * this under the assumption that if
+ * the root directory is just a plain
+ * directory (i.e. very light-weight),
+ * we shouldn't try to split it up
+ * into subvolumes (i.e. more
+ * heavy-weight). Thus, chroot()
+ * environments and suchlike will get
+ * a full brtfs subvolume set up below
+ * their tree only if they
+ * specifically set up a btrfs
+ * subvolume for the root dir too. */
+
+ r = -ENOTTY;
+ else {
+ RUN_WITH_UMASK((~i->mode) & 0777)
+ r = btrfs_subvol_make(i->path);
+ }
+ } else
+ r = 0;
+
+ if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY)
+ RUN_WITH_UMASK(0000)
+ r = mkdir_label(i->path, i->mode);
+
+ if (r < 0) {
+ int k;
+
+ if (r != -EEXIST && r != -EROFS)
+ return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path);
+
+ k = is_dir(i->path, false);
+ if (k == -ENOENT && r == -EROFS)
+ return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", i->path);
+ if (k < 0)
+ return log_error_errno(k, "Failed to check if %s exists: %m", i->path);
+ if (!k) {
+ log_warning("\"%s\" already exists and is not a directory.", i->path);
+ return 0;
+ }
+
+ creation = CREATION_EXISTING;
+ } else
+ creation = CREATION_NORMAL;
+
+ log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path);
+
+ if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) {
+ r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA);
+ if (r == -ENOTTY)
+ log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (unsupported fs or dir not a subvolume): %m", i->path);
+ else if (r == -EROFS)
+ log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (fs is read-only).", i->path);
+ else if (r == -ENOPROTOOPT)
+ log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" (quota support is disabled).", i->path);
+ else if (r < 0)
+ q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path);
+ else if (r > 0)
+ log_debug("Adjusted quota for subvolume \"%s\".", i->path);
+ else if (r == 0)
+ log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path);
+ }
+
+ /* fall through */
+
+ case EMPTY_DIRECTORY:
+ r = path_set_perms(i, i->path);
+ if (q < 0)
+ return q;
+ if (r < 0)
+ return r;
+
+ break;
+
+ case CREATE_FIFO:
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(i->path, S_IFIFO);
+ r = mkfifo(i->path, i->mode);
+ mac_selinux_create_file_clear();
+ }
+
+ if (r < 0) {
+ if (errno != EEXIST)
+ return log_error_errno(errno, "Failed to create fifo %s: %m", i->path);
+
+ if (lstat(i->path, &st) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", i->path);
+
+ if (!S_ISFIFO(st.st_mode)) {
+
+ if (i->force) {
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(i->path, S_IFIFO);
+ r = mkfifo_atomic(i->path, i->mode);
+ mac_selinux_create_file_clear();
+ }
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to create fifo %s: %m", i->path);
+ creation = CREATION_FORCE;
+ } else {
+ log_warning("\"%s\" already exists and is not a fifo.", i->path);
+ return 0;
+ }
+ } else
+ creation = CREATION_EXISTING;
+ } else
+ creation = CREATION_NORMAL;
+ log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path);
+
+ r = path_set_perms(i, i->path);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case CREATE_SYMLINK: {
+ r = specifier_printf(i->argument, specifier_table, NULL, &resolved);
+ if (r < 0)
+ return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument);
+
+ mac_selinux_create_file_prepare(i->path, S_IFLNK);
+ r = symlink(resolved, i->path);
+ mac_selinux_create_file_clear();
+
+ if (r < 0) {
+ _cleanup_free_ char *x = NULL;
+
+ if (errno != EEXIST)
+ return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path);
+
+ r = readlink_malloc(i->path, &x);
+ if (r < 0 || !streq(resolved, x)) {
+
+ if (i->force) {
+ mac_selinux_create_file_prepare(i->path, S_IFLNK);
+ r = symlink_atomic(resolved, i->path);
+ mac_selinux_create_file_clear();
+
+ if (r < 0)
+ return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path);
+
+ creation = CREATION_FORCE;
+ } else {
+ log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path);
+ return 0;
+ }
+ } else
+ creation = CREATION_EXISTING;
+ } else
+
+ creation = CREATION_NORMAL;
+ log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path);
+ break;
+ }
+
+ case CREATE_BLOCK_DEVICE:
+ case CREATE_CHAR_DEVICE: {
+ mode_t file_type;
+
+ if (have_effective_cap(CAP_MKNOD) == 0) {
+ /* In a container we lack CAP_MKNOD. We
+ shouldn't attempt to create the device node in
+ that case to avoid noise, and we don't support
+ virtualized devices in containers anyway. */
+
+ log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path);
+ return 0;
+ }
+
+ file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR;
+
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(i->path, file_type);
+ r = mknod(i->path, i->mode | file_type, i->major_minor);
+ mac_selinux_create_file_clear();
+ }
+
+ if (r < 0) {
+ if (errno == EPERM) {
+ log_debug("We lack permissions, possibly because of cgroup configuration; "
+ "skipping creation of device node %s.", i->path);
+ return 0;
+ }
+
+ if (errno != EEXIST)
+ return log_error_errno(errno, "Failed to create device node %s: %m", i->path);
+
+ if (lstat(i->path, &st) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", i->path);
+
+ if ((st.st_mode & S_IFMT) != file_type) {
+
+ if (i->force) {
+
+ RUN_WITH_UMASK(0000) {
+ mac_selinux_create_file_prepare(i->path, file_type);
+ r = mknod_atomic(i->path, i->mode | file_type, i->major_minor);
+ mac_selinux_create_file_clear();
+ }
+
+ if (r < 0)
+ return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path);
+ creation = CREATION_FORCE;
+ } else {
+ log_debug("%s is not a device node.", i->path);
+ return 0;
+ }
+ } else
+ creation = CREATION_EXISTING;
+ } else
+ creation = CREATION_NORMAL;
+
+ log_debug("%s %s device node \"%s\" %u:%u.",
+ creation_mode_verb_to_string(creation),
+ i->type == CREATE_BLOCK_DEVICE ? "block" : "char",
+ i->path, major(i->mode), minor(i->mode));
+
+ r = path_set_perms(i, i->path);
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case ADJUST_MODE:
+ case RELABEL_PATH:
+ r = glob_item(i, path_set_perms, false);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_RELABEL_PATH:
+ r = glob_item(i, path_set_perms, true);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_XATTR:
+ r = glob_item(i, path_set_xattrs, false);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_SET_XATTR:
+ r = glob_item(i, path_set_xattrs, true);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_ACL:
+ r = glob_item(i, path_set_acls, false);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_SET_ACL:
+ r = glob_item(i, path_set_acls, true);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_ATTRIBUTE:
+ r = glob_item(i, path_set_attribute, false);
+ if (r < 0)
+ return r;
+ break;
+
+ case RECURSIVE_SET_ATTRIBUTE:
+ r = glob_item(i, path_set_attribute, true);
+ if (r < 0)
+ return r;
+ break;
+ }
+
+ return 0;
+}
+
+static int remove_item_instance(Item *i, const char *instance) {
+ int r;
+
+ assert(i);
+
+ switch (i->type) {
+
+ case REMOVE_PATH:
+ if (remove(instance) < 0 && errno != ENOENT)
+ return log_error_errno(errno, "rm(%s): %m", instance);
+
+ break;
+
+ case TRUNCATE_DIRECTORY:
+ case RECURSIVE_REMOVE_PATH:
+ /* FIXME: we probably should use dir_cleanup() here
+ * instead of rm_rf() so that 'x' is honoured. */
+ log_debug("rm -rf \"%s\"", instance);
+ r = rm_rf(instance, (i->type == RECURSIVE_REMOVE_PATH ? REMOVE_ROOT|REMOVE_SUBVOLUME : 0) | REMOVE_PHYSICAL);
+ if (r < 0 && r != -ENOENT)
+ return log_error_errno(r, "rm_rf(%s): %m", instance);
+
+ break;
+
+ default:
+ assert_not_reached("wut?");
+ }
+
+ return 0;
+}
+
+static int remove_item(Item *i) {
+ assert(i);
+
+ log_debug("Running remove action for entry %c %s", (char) i->type, i->path);
+
+ switch (i->type) {
+
+ case REMOVE_PATH:
+ case TRUNCATE_DIRECTORY:
+ case RECURSIVE_REMOVE_PATH:
+ return glob_item(i, remove_item_instance, false);
+
+ default:
+ return 0;
+ }
+}
+
+static int clean_item_instance(Item *i, const char* instance) {
+ _cleanup_closedir_ DIR *d = NULL;
+ struct stat s, ps;
+ bool mountpoint;
+ usec_t cutoff, n;
+ char timestamp[FORMAT_TIMESTAMP_MAX];
+
+ assert(i);
+
+ if (!i->age_set)
+ return 0;
+
+ n = now(CLOCK_REALTIME);
+ if (n < i->age)
+ return 0;
+
+ cutoff = n - i->age;
+
+ d = opendir_nomod(instance);
+ if (!d) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ log_debug_errno(errno, "Directory \"%s\": %m", instance);
+ return 0;
+ }
+
+ log_error_errno(errno, "Failed to open directory %s: %m", instance);
+ return -errno;
+ }
+
+ if (fstat(dirfd(d), &s) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", i->path);
+
+ if (!S_ISDIR(s.st_mode)) {
+ log_error("%s is not a directory.", i->path);
+ return -ENOTDIR;
+ }
+
+ if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0)
+ return log_error_errno(errno, "stat(%s/..) failed: %m", i->path);
+
+ mountpoint = s.st_dev != ps.st_dev || s.st_ino == ps.st_ino;
+
+ log_debug("Cleanup threshold for %s \"%s\" is %s",
+ mountpoint ? "mount point" : "directory",
+ instance,
+ format_timestamp_us(timestamp, sizeof(timestamp), cutoff));
+
+ return dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint,
+ MAX_DEPTH, i->keep_first_level);
+}
+
+static int clean_item(Item *i) {
+ assert(i);
+
+ log_debug("Running clean action for entry %c %s", (char) i->type, i->path);
+
+ switch (i->type) {
+ case CREATE_DIRECTORY:
+ case CREATE_SUBVOLUME:
+ case CREATE_SUBVOLUME_INHERIT_QUOTA:
+ case CREATE_SUBVOLUME_NEW_QUOTA:
+ case EMPTY_DIRECTORY:
+ case TRUNCATE_DIRECTORY:
+ case IGNORE_PATH:
+ case COPY_FILES:
+ clean_item_instance(i, i->path);
+ return 0;
+ case IGNORE_DIRECTORY_PATH:
+ return glob_item(i, clean_item_instance, false);
+ default:
+ return 0;
+ }
+}
+
+static int process_item_array(ItemArray *array);
+
+static int process_item(Item *i) {
+ int r, q, p, t = 0;
+ _cleanup_free_ char *prefix = NULL;
+
+ assert(i);
+
+ if (i->done)
+ return 0;
+
+ i->done = true;
+
+ prefix = malloc(strlen(i->path) + 1);
+ if (!prefix)
+ return log_oom();
+
+ PATH_FOREACH_PREFIX(prefix, i->path) {
+ ItemArray *j;
+
+ j = ordered_hashmap_get(items, prefix);
+ if (j) {
+ int s;
+
+ s = process_item_array(j);
+ if (s < 0 && t == 0)
+ t = s;
+ }
+ }
+
+ r = arg_create ? create_item(i) : 0;
+ q = arg_remove ? remove_item(i) : 0;
+ p = arg_clean ? clean_item(i) : 0;
+
+ return t < 0 ? t :
+ r < 0 ? r :
+ q < 0 ? q :
+ p;
+}
+
+static int process_item_array(ItemArray *array) {
+ unsigned n;
+ int r = 0, k;
+
+ assert(array);
+
+ for (n = 0; n < array->count; n++) {
+ k = process_item(array->items + n);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static void item_free_contents(Item *i) {
+ assert(i);
+ free(i->path);
+ free(i->argument);
+ strv_free(i->xattrs);
+
+#ifdef HAVE_ACL
+ acl_free(i->acl_access);
+ acl_free(i->acl_default);
+#endif
+}
+
+static void item_array_free(ItemArray *a) {
+ unsigned n;
+
+ if (!a)
+ return;
+
+ for (n = 0; n < a->count; n++)
+ item_free_contents(a->items + n);
+ free(a->items);
+ free(a);
+}
+
+static int item_compare(const void *a, const void *b) {
+ const Item *x = a, *y = b;
+
+ /* Make sure that the ownership taking item is put first, so
+ * that we first create the node, and then can adjust it */
+
+ if (takes_ownership(x->type) && !takes_ownership(y->type))
+ return -1;
+ if (!takes_ownership(x->type) && takes_ownership(y->type))
+ return 1;
+
+ return (int) x->type - (int) y->type;
+}
+
+static bool item_compatible(Item *a, Item *b) {
+ assert(a);
+ assert(b);
+ assert(streq(a->path, b->path));
+
+ if (takes_ownership(a->type) && takes_ownership(b->type))
+ /* check if the items are the same */
+ return streq_ptr(a->argument, b->argument) &&
+
+ a->uid_set == b->uid_set &&
+ a->uid == b->uid &&
+
+ a->gid_set == b->gid_set &&
+ a->gid == b->gid &&
+
+ a->mode_set == b->mode_set &&
+ a->mode == b->mode &&
+
+ a->age_set == b->age_set &&
+ a->age == b->age &&
+
+ a->mask_perms == b->mask_perms &&
+
+ a->keep_first_level == b->keep_first_level &&
+
+ a->major_minor == b->major_minor;
+
+ return true;
+}
+
+static bool should_include_path(const char *path) {
+ char **prefix;
+
+ STRV_FOREACH(prefix, arg_exclude_prefixes)
+ if (path_startswith(path, *prefix)) {
+ log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.",
+ path, *prefix);
+ return false;
+ }
+
+ STRV_FOREACH(prefix, arg_include_prefixes)
+ if (path_startswith(path, *prefix)) {
+ log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix);
+ return true;
+ }
+
+ /* no matches, so we should include this path only if we
+ * have no whitelist at all */
+ if (strv_length(arg_include_prefixes) == 0)
+ return true;
+
+ log_debug("Entry \"%s\" does not match any include prefix, skipping.", path);
+ return false;
+}
+
+static int parse_line(const char *fname, unsigned line, const char *buffer) {
+
+ _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
+ _cleanup_(item_free_contents) Item i = {};
+ ItemArray *existing;
+ OrderedHashmap *h;
+ int r, pos;
+ bool force = false, boot = false;
+
+ assert(fname);
+ assert(line >= 1);
+ assert(buffer);
+
+ r = extract_many_words(
+ &buffer,
+ NULL,
+ EXTRACT_QUOTES,
+ &action,
+ &path,
+ &mode,
+ &user,
+ &group,
+ &age,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line);
+ else if (r < 2) {
+ log_error("[%s:%u] Syntax error.", fname, line);
+ return -EIO;
+ }
+
+ if (!isempty(buffer) && !streq(buffer, "-")) {
+ i.argument = strdup(buffer);
+ if (!i.argument)
+ return log_oom();
+ }
+
+ if (isempty(action)) {
+ log_error("[%s:%u] Command too short '%s'.", fname, line, action);
+ return -EINVAL;
+ }
+
+ for (pos = 1; action[pos]; pos++) {
+ if (action[pos] == '!' && !boot)
+ boot = true;
+ else if (action[pos] == '+' && !force)
+ force = true;
+ else {
+ log_error("[%s:%u] Unknown modifiers in command '%s'",
+ fname, line, action);
+ return -EINVAL;
+ }
+ }
+
+ if (boot && !arg_boot) {
+ log_debug("Ignoring entry %s \"%s\" because --boot is not specified.",
+ action, path);
+ return 0;
+ }
+
+ i.type = action[0];
+ i.force = force;
+
+ r = specifier_printf(path, specifier_table, NULL, &i.path);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path);
+ return r;
+ }
+
+ switch (i.type) {
+
+ case CREATE_DIRECTORY:
+ case CREATE_SUBVOLUME:
+ case CREATE_SUBVOLUME_INHERIT_QUOTA:
+ case CREATE_SUBVOLUME_NEW_QUOTA:
+ case EMPTY_DIRECTORY:
+ case TRUNCATE_DIRECTORY:
+ case CREATE_FIFO:
+ case IGNORE_PATH:
+ case IGNORE_DIRECTORY_PATH:
+ case REMOVE_PATH:
+ case RECURSIVE_REMOVE_PATH:
+ case ADJUST_MODE:
+ case RELABEL_PATH:
+ case RECURSIVE_RELABEL_PATH:
+ if (i.argument)
+ log_warning("[%s:%u] %c lines don't take argument fields, ignoring.", fname, line, i.type);
+
+ break;
+
+ case CREATE_FILE:
+ case TRUNCATE_FILE:
+ break;
+
+ case CREATE_SYMLINK:
+ if (!i.argument) {
+ i.argument = strappend("/usr/share/factory/", i.path);
+ if (!i.argument)
+ return log_oom();
+ }
+ break;
+
+ case WRITE_FILE:
+ if (!i.argument) {
+ log_error("[%s:%u] Write file requires argument.", fname, line);
+ return -EBADMSG;
+ }
+ break;
+
+ case COPY_FILES:
+ if (!i.argument) {
+ i.argument = strappend("/usr/share/factory/", i.path);
+ if (!i.argument)
+ return log_oom();
+ } else if (!path_is_absolute(i.argument)) {
+ log_error("[%s:%u] Source path is not absolute.", fname, line);
+ return -EBADMSG;
+ }
+
+ path_kill_slashes(i.argument);
+ break;
+
+ case CREATE_CHAR_DEVICE:
+ case CREATE_BLOCK_DEVICE: {
+ unsigned major, minor;
+
+ if (!i.argument) {
+ log_error("[%s:%u] Device file requires argument.", fname, line);
+ return -EBADMSG;
+ }
+
+ if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
+ log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
+ return -EBADMSG;
+ }
+
+ i.major_minor = makedev(major, minor);
+ break;
+ }
+
+ case SET_XATTR:
+ case RECURSIVE_SET_XATTR:
+ if (!i.argument) {
+ log_error("[%s:%u] Set extended attribute requires argument.", fname, line);
+ return -EBADMSG;
+ }
+ r = parse_xattrs_from_arg(&i);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_ACL:
+ case RECURSIVE_SET_ACL:
+ if (!i.argument) {
+ log_error("[%s:%u] Set ACLs requires argument.", fname, line);
+ return -EBADMSG;
+ }
+ r = parse_acls_from_arg(&i);
+ if (r < 0)
+ return r;
+ break;
+
+ case SET_ATTRIBUTE:
+ case RECURSIVE_SET_ATTRIBUTE:
+ if (!i.argument) {
+ log_error("[%s:%u] Set file attribute requires argument.", fname, line);
+ return -EBADMSG;
+ }
+ r = parse_attribute_from_arg(&i);
+ if (r < 0)
+ return r;
+ break;
+
+ default:
+ log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type);
+ return -EBADMSG;
+ }
+
+ if (!path_is_absolute(i.path)) {
+ log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path);
+ return -EBADMSG;
+ }
+
+ path_kill_slashes(i.path);
+
+ if (!should_include_path(i.path))
+ return 0;
+
+ if (arg_root) {
+ char *p;
+
+ p = prefix_root(arg_root, i.path);
+ if (!p)
+ return log_oom();
+
+ free(i.path);
+ i.path = p;
+ }
+
+ if (!isempty(user) && !streq(user, "-")) {
+ const char *u = user;
+
+ r = get_user_creds(&u, &i.uid, NULL, NULL, NULL);
+ if (r < 0) {
+ log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
+ return r;
+ }
+
+ i.uid_set = true;
+ }
+
+ if (!isempty(group) && !streq(group, "-")) {
+ const char *g = group;
+
+ r = get_group_creds(&g, &i.gid);
+ if (r < 0) {
+ log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
+ return r;
+ }
+
+ i.gid_set = true;
+ }
+
+ if (!isempty(mode) && !streq(mode, "-")) {
+ const char *mm = mode;
+ unsigned m;
+
+ if (*mm == '~') {
+ i.mask_perms = true;
+ mm++;
+ }
+
+ if (parse_mode(mm, &m) < 0) {
+ log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
+ return -EBADMSG;
+ }
+
+ i.mode = m;
+ i.mode_set = true;
+ } else
+ i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644;
+
+ if (!isempty(age) && !streq(age, "-")) {
+ const char *a = age;
+
+ if (*a == '~') {
+ i.keep_first_level = true;
+ a++;
+ }
+
+ if (parse_sec(a, &i.age) < 0) {
+ log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
+ return -EBADMSG;
+ }
+
+ i.age_set = true;
+ }
+
+ h = needs_glob(i.type) ? globs : items;
+
+ existing = ordered_hashmap_get(h, i.path);
+ if (existing) {
+ unsigned n;
+
+ for (n = 0; n < existing->count; n++) {
+ if (!item_compatible(existing->items + n, &i)) {
+ log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.",
+ fname, line, i.path);
+ return 0;
+ }
+ }
+ } else {
+ existing = new0(ItemArray, 1);
+ r = ordered_hashmap_put(h, i.path, existing);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1))
+ return log_oom();
+
+ memcpy(existing->items + existing->count++, &i, sizeof(i));
+
+ /* Sort item array, to enforce stable ordering of application */
+ qsort_safe(existing->items, existing->count, sizeof(Item), item_compare);
+
+ zero(i);
+ return 0;
+}
+
+static void help(void) {
+ printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
+ "Creates, deletes and cleans up volatile and temporary files and directories.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --create Create marked files/directories\n"
+ " --clean Clean up marked directories\n"
+ " --remove Remove marked files/directories\n"
+ " --boot Execute actions only safe at boot\n"
+ " --prefix=PATH Only apply rules with the specified prefix\n"
+ " --exclude-prefix=PATH Ignore rules with the specified prefix\n"
+ " --root=PATH Operate on an alternate filesystem root\n",
+ program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_CREATE,
+ ARG_CLEAN,
+ ARG_REMOVE,
+ ARG_BOOT,
+ ARG_PREFIX,
+ ARG_EXCLUDE_PREFIX,
+ ARG_ROOT,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "create", no_argument, NULL, ARG_CREATE },
+ { "clean", no_argument, NULL, ARG_CLEAN },
+ { "remove", no_argument, NULL, ARG_REMOVE },
+ { "boot", no_argument, NULL, ARG_BOOT },
+ { "prefix", required_argument, NULL, ARG_PREFIX },
+ { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX },
+ { "root", required_argument, NULL, ARG_ROOT },
+ {}
+ };
+
+ int c, r;
+
+ 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_CREATE:
+ arg_create = true;
+ break;
+
+ case ARG_CLEAN:
+ arg_clean = true;
+ break;
+
+ case ARG_REMOVE:
+ arg_remove = true;
+ break;
+
+ case ARG_BOOT:
+ arg_boot = true;
+ break;
+
+ case ARG_PREFIX:
+ if (strv_push(&arg_include_prefixes, optarg) < 0)
+ return log_oom();
+ break;
+
+ case ARG_EXCLUDE_PREFIX:
+ if (strv_push(&arg_exclude_prefixes, optarg) < 0)
+ return log_oom();
+ break;
+
+ case ARG_ROOT:
+ r = parse_path_argument_and_warn(optarg, true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ if (!arg_clean && !arg_create && !arg_remove) {
+ log_error("You need to specify at least one of --clean, --create or --remove.");
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+static int read_config_file(const char *fn, bool ignore_enoent) {
+ _cleanup_fclose_ FILE *_f = NULL;
+ FILE *f;
+ char line[LINE_MAX];
+ Iterator iterator;
+ unsigned v = 0;
+ Item *i;
+ int r;
+
+ assert(fn);
+
+ if (streq(fn, "-")) {
+ log_debug("Reading config from stdin.");
+ fn = "<stdin>";
+ f = stdin;
+ } else {
+ r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &_f);
+ if (r < 0) {
+ if (ignore_enoent && r == -ENOENT) {
+ log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn);
+ return 0;
+ }
+
+ return log_error_errno(r, "Failed to open '%s': %m", fn);
+ }
+ log_debug("Reading config file \"%s\".", fn);
+ f = _f;
+ }
+
+ FOREACH_LINE(line, f, break) {
+ char *l;
+ int k;
+
+ v++;
+
+ l = strstrip(line);
+ if (*l == '#' || *l == 0)
+ continue;
+
+ k = parse_line(fn, v, l);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ /* we have to determine age parameter for each entry of type X */
+ ORDERED_HASHMAP_FOREACH(i, globs, iterator) {
+ Iterator iter;
+ Item *j, *candidate_item = NULL;
+
+ if (i->type != IGNORE_DIRECTORY_PATH)
+ continue;
+
+ ORDERED_HASHMAP_FOREACH(j, items, iter) {
+ if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA))
+ continue;
+
+ if (path_equal(j->path, i->path)) {
+ candidate_item = j;
+ break;
+ }
+
+ if ((!candidate_item && path_startswith(i->path, j->path)) ||
+ (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0)))
+ candidate_item = j;
+ }
+
+ if (candidate_item && candidate_item->age_set) {
+ i->age = candidate_item->age;
+ i->age_set = true;
+ }
+ }
+
+ if (ferror(f)) {
+ log_error_errno(errno, "Failed to read from file %s: %m", fn);
+ if (r == 0)
+ r = -EIO;
+ }
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ int r, k;
+ ItemArray *a;
+ Iterator iterator;
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ mac_selinux_init();
+
+ items = ordered_hashmap_new(&string_hash_ops);
+ globs = ordered_hashmap_new(&string_hash_ops);
+
+ if (!items || !globs) {
+ r = log_oom();
+ goto finish;
+ }
+
+ r = 0;
+
+ if (optind < argc) {
+ int j;
+
+ for (j = optind; j < argc; j++) {
+ k = read_config_file(argv[j], false);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ } else {
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+
+ r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
+ if (r < 0) {
+ log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m");
+ goto finish;
+ }
+
+ STRV_FOREACH(f, files) {
+ k = read_config_file(*f, true);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+ }
+
+ /* The non-globbing ones usually create things, hence we apply
+ * them first */
+ ORDERED_HASHMAP_FOREACH(a, items, iterator) {
+ k = process_item_array(a);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ /* The globbing ones usually alter things, hence we apply them
+ * second. */
+ ORDERED_HASHMAP_FOREACH(a, globs, iterator) {
+ k = process_item_array(a);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+finish:
+ while ((a = ordered_hashmap_steal_first(items)))
+ item_array_free(a);
+
+ while ((a = ordered_hashmap_steal_first(globs)))
+ item_array_free(a);
+
+ ordered_hashmap_free(items);
+ ordered_hashmap_free(globs);
+
+ free(arg_include_prefixes);
+ free(arg_exclude_prefixes);
+ free(arg_root);
+
+ set_free_free(unix_sockets);
+
+ mac_selinux_finish();
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-update-done/systemd-update-done.service.in b/src/grp-initprogs/systemd-update-done/systemd-update-done.service.in
new file mode 100644
index 0000000000..ec7d906392
--- /dev/null
+++ b/src/grp-initprogs/systemd-update-done/systemd-update-done.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=Update is Completed
+Documentation=man:systemd-update-done.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=local-fs.target
+Before=sysinit.target shutdown.target
+ConditionNeedsUpdate=|/etc
+ConditionNeedsUpdate=|/var
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-update-done
diff --git a/src/grp-initprogs/systemd-update-done/update-done.c b/src/grp-initprogs/systemd-update-done/update-done.c
new file mode 100644
index 0000000000..da306a4444
--- /dev/null
+++ b/src/grp-initprogs/systemd-update-done/update-done.c
@@ -0,0 +1,115 @@
+/***
+ 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 "fd-util.h"
+#include "io-util.h"
+#include "selinux-util.h"
+#include "util.h"
+
+#define MESSAGE \
+ "This file was created by systemd-update-done. Its only \n" \
+ "purpose is to hold a timestamp of the time this directory\n" \
+ "was updated. See systemd-update-done.service(8).\n"
+
+static int apply_timestamp(const char *path, struct timespec *ts) {
+ struct timespec twice[2] = {
+ *ts,
+ *ts
+ };
+ struct stat st;
+
+ assert(path);
+ assert(ts);
+
+ if (stat(path, &st) >= 0) {
+ /* Is the timestamp file already newer than the OS? If
+ * so, there's nothing to do. We ignore the nanosecond
+ * component of the timestamp, since some file systems
+ * do not support any better accuracy than 1s and we
+ * have no way to identify the accuracy
+ * available. Most notably ext4 on small disks (where
+ * 128 byte inodes are used) does not support better
+ * accuracy than 1s. */
+ if (st.st_mtim.tv_sec > ts->tv_sec)
+ return 0;
+
+ /* It is older? Then let's update it */
+ if (utimensat(AT_FDCWD, path, twice, AT_SYMLINK_NOFOLLOW) < 0) {
+
+ if (errno == EROFS)
+ return log_debug("Can't update timestamp file %s, file system is read-only.", path);
+
+ return log_error_errno(errno, "Failed to update timestamp on %s: %m", path);
+ }
+
+ } else if (errno == ENOENT) {
+ _cleanup_close_ int fd = -1;
+ int r;
+
+ /* The timestamp file doesn't exist yet? Then let's create it. */
+
+ r = mac_selinux_create_file_prepare(path, S_IFREG);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set SELinux context for %s: %m", path);
+
+ fd = open(path, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644);
+ mac_selinux_create_file_clear();
+
+ if (fd < 0) {
+ if (errno == EROFS)
+ return log_debug("Can't create timestamp file %s, file system is read-only.", path);
+
+ return log_error_errno(errno, "Failed to create timestamp file %s: %m", path);
+ }
+
+ (void) loop_write(fd, MESSAGE, strlen(MESSAGE), false);
+
+ if (futimens(fd, twice) < 0)
+ return log_error_errno(errno, "Failed to update timestamp on %s: %m", path);
+ } else
+ log_error_errno(errno, "Failed to stat() timestamp file %s: %m", path);
+
+ return 0;
+}
+
+int main(int argc, char *argv[]) {
+ struct stat st;
+ int r, q = 0;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ if (stat("/usr", &st) < 0) {
+ log_error_errno(errno, "Failed to stat /usr: %m");
+ return EXIT_FAILURE;
+ }
+
+ r = mac_selinux_init();
+ if (r < 0) {
+ log_error_errno(r, "SELinux setup failed: %m");
+ goto finish;
+ }
+
+ r = apply_timestamp("/etc/.updated", &st.st_mtim);
+ q = apply_timestamp("/var/.updated", &st.st_mtim);
+
+finish:
+ return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-update-utmp/systemd-update-utmp.service.in b/src/grp-initprogs/systemd-update-utmp/systemd-update-utmp.service.in
new file mode 100644
index 0000000000..163eccd91f
--- /dev/null
+++ b/src/grp-initprogs/systemd-update-utmp/systemd-update-utmp.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=Update UTMP about System Boot/Shutdown
+Documentation=man:systemd-update-utmp.service(8) man:utmp(5)
+DefaultDependencies=no
+RequiresMountsFor=/var/log/wtmp
+Conflicts=shutdown.target
+After=systemd-remount-fs.service systemd-tmpfiles-setup.service auditd.service
+Before=sysinit.target shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-update-utmp reboot
+ExecStop=@rootlibexecdir@/systemd-update-utmp shutdown
diff --git a/src/grp-initprogs/systemd-update-utmp/update-utmp.c b/src/grp-initprogs/systemd-update-utmp/update-utmp.c
new file mode 100644
index 0000000000..fedcaef91c
--- /dev/null
+++ b/src/grp-initprogs/systemd-update-utmp/update-utmp.c
@@ -0,0 +1,283 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_AUDIT
+#include <libaudit.h>
+#endif
+
+#include <systemd/sd-bus.h>
+
+#include "alloc-util.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "formats-util.h"
+#include "log.h"
+#include "macro.h"
+#include "special.h"
+#include "unit-name.h"
+#include "util.h"
+#include "utmp-wtmp.h"
+
+typedef struct Context {
+ sd_bus *bus;
+#ifdef HAVE_AUDIT
+ int audit_fd;
+#endif
+} Context;
+
+static usec_t get_startup_time(Context *c) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ usec_t t = 0;
+ int r;
+
+ assert(c);
+
+ r = sd_bus_get_property_trivial(
+ c->bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "UserspaceTimestamp",
+ &error,
+ 't', &t);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get timestamp: %s", bus_error_message(&error, r));
+ return 0;
+ }
+
+ return t;
+}
+
+static int get_current_runlevel(Context *c) {
+ static const struct {
+ const int runlevel;
+ const char *special;
+ } table[] = {
+ /* The first target of this list that is active or has
+ * a job scheduled wins. We prefer runlevels 5 and 3
+ * here over the others, since these are the main
+ * runlevels used on Fedora. It might make sense to
+ * change the order on some distributions. */
+ { '5', SPECIAL_GRAPHICAL_TARGET },
+ { '3', SPECIAL_MULTI_USER_TARGET },
+ { '1', SPECIAL_RESCUE_TARGET },
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+ unsigned i;
+
+ assert(c);
+
+ for (i = 0; i < ELEMENTSOF(table); i++) {
+ _cleanup_free_ char *state = NULL, *path = NULL;
+
+ path = unit_dbus_path_from_name(table[i].special);
+ if (!path)
+ return log_oom();
+
+ r = sd_bus_get_property_string(
+ c->bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit",
+ "ActiveState",
+ &error,
+ &state);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r));
+
+ if (streq(state, "active") || streq(state, "reloading"))
+ return table[i].runlevel;
+ }
+
+ return 0;
+}
+
+static int on_reboot(Context *c) {
+ int r = 0, q;
+ usec_t t;
+
+ assert(c);
+
+ /* We finished start-up, so let's write the utmp
+ * record and send the audit msg */
+
+#ifdef HAVE_AUDIT
+ if (c->audit_fd >= 0)
+ if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 &&
+ errno != EPERM) {
+ r = log_error_errno(errno, "Failed to send audit message: %m");
+ }
+#endif
+
+ /* If this call fails it will return 0, which
+ * utmp_put_reboot() will then fix to the current time */
+ t = get_startup_time(c);
+
+ q = utmp_put_reboot(t);
+ if (q < 0) {
+ log_error_errno(q, "Failed to write utmp record: %m");
+ r = q;
+ }
+
+ return r;
+}
+
+static int on_shutdown(Context *c) {
+ int r = 0, q;
+
+ assert(c);
+
+ /* We started shut-down, so let's write the utmp
+ * record and send the audit msg */
+
+#ifdef HAVE_AUDIT
+ if (c->audit_fd >= 0)
+ if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 &&
+ errno != EPERM) {
+ r = log_error_errno(errno, "Failed to send audit message: %m");
+ }
+#endif
+
+ q = utmp_put_shutdown();
+ if (q < 0) {
+ log_error_errno(q, "Failed to write utmp record: %m");
+ r = q;
+ }
+
+ return r;
+}
+
+static int on_runlevel(Context *c) {
+ int r = 0, q, previous, runlevel;
+
+ assert(c);
+
+ /* We finished changing runlevel, so let's write the
+ * utmp record and send the audit msg */
+
+ /* First, get last runlevel */
+ q = utmp_get_runlevel(&previous, NULL);
+
+ if (q < 0) {
+ if (q != -ESRCH && q != -ENOENT)
+ return log_error_errno(q, "Failed to get current runlevel: %m");
+
+ previous = 0;
+ }
+
+ /* Secondly, get new runlevel */
+ runlevel = get_current_runlevel(c);
+
+ if (runlevel < 0)
+ return runlevel;
+
+ if (previous == runlevel)
+ return 0;
+
+#ifdef HAVE_AUDIT
+ if (c->audit_fd >= 0) {
+ _cleanup_free_ char *s = NULL;
+
+ if (asprintf(&s, "old-level=%c new-level=%c",
+ previous > 0 ? previous : 'N',
+ runlevel > 0 ? runlevel : 'N') < 0)
+ return log_oom();
+
+ if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM)
+ r = log_error_errno(errno, "Failed to send audit message: %m");
+ }
+#endif
+
+ q = utmp_put_runlevel(runlevel, previous);
+ if (q < 0 && q != -ESRCH && q != -ENOENT) {
+ log_error_errno(q, "Failed to write utmp record: %m");
+ r = q;
+ }
+
+ return r;
+}
+
+int main(int argc, char *argv[]) {
+ Context c = {
+#ifdef HAVE_AUDIT
+ .audit_fd = -1
+#endif
+ };
+ int r;
+
+ if (getppid() != 1) {
+ log_error("This program should be invoked by init only.");
+ return EXIT_FAILURE;
+ }
+
+ if (argc != 2) {
+ log_error("This program requires one argument.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+#ifdef HAVE_AUDIT
+ /* If the kernel lacks netlink or audit support,
+ * don't worry about it. */
+ c.audit_fd = audit_open();
+ if (c.audit_fd < 0 && errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT)
+ log_error_errno(errno, "Failed to connect to audit log: %m");
+#endif
+ r = bus_connect_system_systemd(&c.bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to get D-Bus connection: %m");
+ r = -EIO;
+ goto finish;
+ }
+
+ log_debug("systemd-update-utmp running as pid "PID_FMT, getpid());
+
+ if (streq(argv[1], "reboot"))
+ r = on_reboot(&c);
+ else if (streq(argv[1], "shutdown"))
+ r = on_shutdown(&c);
+ else if (streq(argv[1], "runlevel"))
+ r = on_runlevel(&c);
+ else {
+ log_error("Unknown command %s", argv[1]);
+ r = -EINVAL;
+ }
+
+ log_debug("systemd-update-utmp stopped as pid "PID_FMT, getpid());
+
+finish:
+#ifdef HAVE_AUDIT
+ if (c.audit_fd >= 0)
+ audit_close(c.audit_fd);
+#endif
+
+ sd_bus_flush_close_unref(c.bus);
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-user-sessions/systemd-user-sessions.service.in b/src/grp-initprogs/systemd-user-sessions/systemd-user-sessions.service.in
new file mode 100644
index 0000000000..b4ea5a134b
--- /dev/null
+++ b/src/grp-initprogs/systemd-user-sessions/systemd-user-sessions.service.in
@@ -0,0 +1,17 @@
+# 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=Permit User Sessions
+Documentation=man:systemd-user-sessions.service(8)
+After=remote-fs.target nss-user-lookup.target network.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-user-sessions start
+ExecStop=@rootlibexecdir@/systemd-user-sessions stop
diff --git a/src/grp-initprogs/systemd-user-sessions/user-sessions.c b/src/grp-initprogs/systemd-user-sessions/user-sessions.c
new file mode 100644
index 0000000000..9b29b5ba1d
--- /dev/null
+++ b/src/grp-initprogs/systemd-user-sessions/user-sessions.c
@@ -0,0 +1,84 @@
+/***
+ 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/>.
+***/
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "fileio.h"
+#include "fileio-label.h"
+#include "log.h"
+#include "selinux-util.h"
+#include "string-util.h"
+#include "util.h"
+
+int main(int argc, char*argv[]) {
+
+ if (argc != 2) {
+ log_error("This program requires one argument.");
+ return EXIT_FAILURE;
+ }
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ mac_selinux_init();
+
+ if (streq(argv[1], "start")) {
+ int r = 0;
+
+ if (unlink("/run/nologin") < 0 && errno != ENOENT)
+ r = log_error_errno(errno,
+ "Failed to remove /run/nologin file: %m");
+
+ if (unlink("/etc/nologin") < 0 && errno != ENOENT) {
+ /* If the file doesn't exist and /etc simply
+ * was read-only (in which case unlink()
+ * returns EROFS even if the file doesn't
+ * exist), don't complain */
+
+ if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) {
+ log_error_errno(errno, "Failed to remove /etc/nologin file: %m");
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (r < 0)
+ return EXIT_FAILURE;
+
+ } else if (streq(argv[1], "stop")) {
+ int r;
+
+ r = write_string_file_atomic_label("/run/nologin", "System is going down.");
+ if (r < 0) {
+ log_error_errno(r, "Failed to create /run/nologin: %m");
+ return EXIT_FAILURE;
+ }
+
+ } else {
+ log_error("Unknown verb %s.", argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ mac_selinux_finish();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/grp-initprogs/systemd-vconsole-setup/.gitignore b/src/grp-initprogs/systemd-vconsole-setup/.gitignore
new file mode 100644
index 0000000000..82741b2fb3
--- /dev/null
+++ b/src/grp-initprogs/systemd-vconsole-setup/.gitignore
@@ -0,0 +1 @@
+/90-vconsole.rules
diff --git a/src/grp-initprogs/systemd-vconsole-setup/90-vconsole.rules.in b/src/grp-initprogs/systemd-vconsole-setup/90-vconsole.rules.in
new file mode 100644
index 0000000000..35b9ad5151
--- /dev/null
+++ b/src/grp-initprogs/systemd-vconsole-setup/90-vconsole.rules.in
@@ -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.
+
+# Each vtcon keeps its own state of fonts.
+#
+ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", RUN+="@rootlibexecdir@/systemd-vconsole-setup"
diff --git a/src/grp-initprogs/systemd-vconsole-setup/systemd-vconsole-setup.service.in b/src/grp-initprogs/systemd-vconsole-setup/systemd-vconsole-setup.service.in
new file mode 100644
index 0000000000..6160361871
--- /dev/null
+++ b/src/grp-initprogs/systemd-vconsole-setup/systemd-vconsole-setup.service.in
@@ -0,0 +1,19 @@
+# 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=Setup Virtual Console
+Documentation=man:systemd-vconsole-setup.service(8) man:vconsole.conf(5)
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=sysinit.target shutdown.target
+ConditionPathExists=/dev/tty0
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootlibexecdir@/systemd-vconsole-setup
diff --git a/src/grp-initprogs/systemd-vconsole-setup/vconsole-setup.c b/src/grp-initprogs/systemd-vconsole-setup/vconsole-setup.c
new file mode 100644
index 0000000000..1118118450
--- /dev/null
+++ b/src/grp-initprogs/systemd-vconsole-setup/vconsole-setup.c
@@ -0,0 +1,332 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010 Kay Sievers
+
+ 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 <limits.h>
+#include <linux/kd.h>
+#include <linux/tiocl.h>
+#include <linux/vt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "io-util.h"
+#include "locale-util.h"
+#include "log.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "terminal-util.h"
+#include "util.h"
+#include "virt.h"
+
+static bool is_vconsole(int fd) {
+ unsigned char data[1];
+
+ data[0] = TIOCL_GETFGCONSOLE;
+ return ioctl(fd, TIOCLINUX, data) >= 0;
+}
+
+static int disable_utf8(int fd) {
+ int r = 0, k;
+
+ if (ioctl(fd, KDSKBMODE, K_XLATE) < 0)
+ r = -errno;
+
+ k = loop_write(fd, "\033%@", 3, false);
+ if (k < 0)
+ r = k;
+
+ k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0);
+ if (k < 0)
+ r = k;
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to disable UTF-8: %m");
+
+ return r;
+}
+
+static int enable_utf8(int fd) {
+ int r = 0, k;
+ long current = 0;
+
+ if (ioctl(fd, KDGKBMODE, &current) < 0 || current == K_XLATE) {
+ /*
+ * Change the current keyboard to unicode, unless it
+ * is currently in raw or off mode anyway. We
+ * shouldn't interfere with X11's processing of the
+ * key events.
+ *
+ * http://lists.freedesktop.org/archives/systemd-devel/2013-February/008573.html
+ *
+ */
+
+ if (ioctl(fd, KDSKBMODE, K_UNICODE) < 0)
+ r = -errno;
+ }
+
+ k = loop_write(fd, "\033%G", 3, false);
+ if (k < 0)
+ r = k;
+
+ k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0);
+ if (k < 0)
+ r = k;
+
+ if (r < 0)
+ log_warning_errno(r, "Failed to enable UTF-8: %m");
+
+ return r;
+}
+
+static int keyboard_load_and_wait(const char *vc, const char *map, const char *map_toggle, bool utf8) {
+ const char *args[8];
+ int i = 0, r;
+ pid_t pid;
+
+ /* An empty map means kernel map */
+ if (isempty(map))
+ return 1;
+
+ args[i++] = KBD_LOADKEYS;
+ args[i++] = "-q";
+ args[i++] = "-C";
+ args[i++] = vc;
+ if (utf8)
+ args[i++] = "-u";
+ args[i++] = map;
+ if (map_toggle)
+ args[i++] = map_toggle;
+ args[i++] = NULL;
+
+ pid = fork();
+ if (pid < 0)
+ return log_error_errno(errno, "Failed to fork: %m");
+ else if (pid == 0) {
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+
+ execv(args[0], (char **) args);
+ _exit(EXIT_FAILURE);
+ }
+
+ r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true);
+ if (r < 0)
+ return r;
+
+ return r == 0;
+}
+
+static int font_load_and_wait(const char *vc, const char *font, const char *map, const char *unimap) {
+ const char *args[9];
+ int i = 0, r;
+ pid_t pid;
+
+ /* An empty font means kernel font */
+ if (isempty(font))
+ return 1;
+
+ args[i++] = KBD_SETFONT;
+ args[i++] = "-C";
+ args[i++] = vc;
+ args[i++] = font;
+ if (map) {
+ args[i++] = "-m";
+ args[i++] = map;
+ }
+ if (unimap) {
+ args[i++] = "-u";
+ args[i++] = unimap;
+ }
+ args[i++] = NULL;
+
+ pid = fork();
+ if (pid < 0)
+ return log_error_errno(errno, "Failed to fork: %m");
+ else if (pid == 0) {
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+
+ execv(args[0], (char **) args);
+ _exit(EXIT_FAILURE);
+ }
+
+ r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true);
+ if (r < 0)
+ return r;
+
+ return r == 0;
+}
+
+/*
+ * A newly allocated VT uses the font from the active VT. Here
+ * we update all possibly already allocated VTs with the configured
+ * font. It also allows to restart systemd-vconsole-setup.service,
+ * to apply a new font to all VTs.
+ */
+static void font_copy_to_all_vcs(int fd) {
+ struct vt_stat vcs = {};
+ unsigned char map8[E_TABSZ];
+ unsigned short map16[E_TABSZ];
+ struct unimapdesc unimapd;
+ _cleanup_free_ struct unipair* unipairs = NULL;
+ int i, r;
+
+ unipairs = new(struct unipair, USHRT_MAX);
+ if (!unipairs) {
+ log_oom();
+ return;
+ }
+
+ /* get active, and 16 bit mask of used VT numbers */
+ r = ioctl(fd, VT_GETSTATE, &vcs);
+ if (r < 0) {
+ log_debug_errno(errno, "VT_GETSTATE failed, ignoring: %m");
+ return;
+ }
+
+ for (i = 1; i <= 15; i++) {
+ char vcname[strlen("/dev/vcs") + DECIMAL_STR_MAX(int)];
+ _cleanup_close_ int vcfd = -1;
+ struct console_font_op cfo = {};
+
+ if (i == vcs.v_active)
+ continue;
+
+ /* skip non-allocated ttys */
+ xsprintf(vcname, "/dev/vcs%i", i);
+ if (access(vcname, F_OK) < 0)
+ continue;
+
+ xsprintf(vcname, "/dev/tty%i", i);
+ vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC);
+ if (vcfd < 0)
+ continue;
+
+ /* copy font from active VT, where the font was uploaded to */
+ cfo.op = KD_FONT_OP_COPY;
+ cfo.height = vcs.v_active-1; /* tty1 == index 0 */
+ (void) ioctl(vcfd, KDFONTOP, &cfo);
+
+ /* copy map of 8bit chars */
+ if (ioctl(fd, GIO_SCRNMAP, map8) >= 0)
+ (void) ioctl(vcfd, PIO_SCRNMAP, map8);
+
+ /* copy map of 8bit chars -> 16bit Unicode values */
+ if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0)
+ (void) ioctl(vcfd, PIO_UNISCRNMAP, map16);
+
+ /* copy unicode translation table */
+ /* unimapd is a ushort count and a pointer to an
+ array of struct unipair { ushort, ushort } */
+ unimapd.entries = unipairs;
+ unimapd.entry_ct = USHRT_MAX;
+ if (ioctl(fd, GIO_UNIMAP, &unimapd) >= 0) {
+ struct unimapinit adv = { 0, 0, 0 };
+
+ (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv);
+ (void) ioctl(vcfd, PIO_UNIMAP, &unimapd);
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ const char *vc;
+ _cleanup_free_ char
+ *vc_keymap = NULL, *vc_keymap_toggle = NULL,
+ *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL;
+ _cleanup_close_ int fd = -1;
+ bool utf8, font_copy = false, font_ok, keyboard_ok;
+ int r = EXIT_FAILURE;
+
+ log_set_target(LOG_TARGET_AUTO);
+ log_parse_environment();
+ log_open();
+
+ umask(0022);
+
+ if (argv[1])
+ vc = argv[1];
+ else {
+ vc = "/dev/tty0";
+ font_copy = true;
+ }
+
+ fd = open_terminal(vc, O_RDWR|O_CLOEXEC);
+ if (fd < 0) {
+ log_error_errno(fd, "Failed to open %s: %m", vc);
+ return EXIT_FAILURE;
+ }
+
+ if (!is_vconsole(fd)) {
+ log_error("Device %s is not a virtual console.", vc);
+ return EXIT_FAILURE;
+ }
+
+ utf8 = is_locale_utf8();
+
+ r = parse_env_file("/etc/vconsole.conf", NEWLINE,
+ "KEYMAP", &vc_keymap,
+ "KEYMAP_TOGGLE", &vc_keymap_toggle,
+ "FONT", &vc_font,
+ "FONT_MAP", &vc_font_map,
+ "FONT_UNIMAP", &vc_font_unimap,
+ NULL);
+
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m");
+
+ /* Let the kernel command line override /etc/vconsole.conf */
+ if (detect_container() <= 0) {
+ r = parse_env_file("/proc/cmdline", WHITESPACE,
+ "vconsole.keymap", &vc_keymap,
+ "vconsole.keymap.toggle", &vc_keymap_toggle,
+ "vconsole.font", &vc_font,
+ "vconsole.font.map", &vc_font_map,
+ "vconsole.font.unimap", &vc_font_unimap,
+ NULL);
+
+ if (r < 0 && r != -ENOENT)
+ log_warning_errno(r, "Failed to read /proc/cmdline: %m");
+ }
+
+ if (utf8)
+ (void) enable_utf8(fd);
+ else
+ (void) disable_utf8(fd);
+
+ font_ok = font_load_and_wait(vc, vc_font, vc_font_map, vc_font_unimap) > 0;
+ keyboard_ok = keyboard_load_and_wait(vc, vc_keymap, vc_keymap_toggle, utf8) > 0;
+
+ /* Only copy the font when we executed setfont successfully */
+ if (font_copy && font_ok)
+ (void) font_copy_to_all_vcs(fd);
+
+ return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE;
+}