From 7c79caf61273479c93f269f471186965623fb590 Mon Sep 17 00:00:00 2001
From: Frederic Crozat <fcrozat@suse.com>
Date: Tue, 11 Aug 2015 12:56:01 +0200
Subject: Backport firstboot helper and ConditionFirstBoot support from v223

FATE#318894
---
 Makefile-man.am                       |   12 +
 Makefile.am                           |   24 +
 configure.ac                          |    9 +
 man/systemd-firstboot.1               |  222 +++++
 man/systemd-firstboot.service.1       |    1 +
 man/systemd-firstboot.xml             |  259 ++++++
 man/systemd.unit.xml                  |   13 +
 src/core/condition.c                  |   17 +
 src/core/load-fragment-gperf.gperf.m4 |    1 +
 src/core/main.c                       |   16 +-
 src/core/manager.c                    |   17 +
 src/core/manager.h                    |    3 +
 src/firstboot/Makefile                |   28 +
 src/firstboot/firstboot.c             | 1532 +++++++++++++++++++++++++++++++++
 src/shared/condition-util.c           |    1 +
 src/shared/condition-util.h           |    1 +
 units/systemd-firstboot.service.in    |   24 +
 17 files changed, 2179 insertions(+), 1 deletion(-)
 create mode 100644 man/systemd-firstboot.1
 create mode 100644 man/systemd-firstboot.service.1
 create mode 100644 man/systemd-firstboot.xml
 create mode 100644 src/firstboot/Makefile
 create mode 100644 src/firstboot/firstboot.c
 create mode 100644 units/systemd-firstboot.service.in

diff --git a/Makefile-man.am b/Makefile-man.am
index aad3f44..0e570c5 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -667,6 +667,17 @@ MANPAGES_ALIAS += \
 
 endif
 
+if ENABLE_FIRSTBOOT
+MANPAGES += \
+	man/systemd-firstboot.1
+MANPAGES_ALIAS += \
+	man/systemd-firstboot.service.1
+man/systemd-firstboot.service.1: man/systemd-firstboot.1
+man/systemd-firstboot.service.html: man/systemd-firstboot.html
+	$(html-alias)
+
+endif
+
 if ENABLE_HOSTNAMED
 MANPAGES += \
 	man/hostnamectl.1 \
@@ -1407,6 +1418,7 @@ EXTRA_DIST += \
 	man/systemd-delta.xml \
 	man/systemd-detect-virt.xml \
 	man/systemd-efi-boot-generator.xml \
+	man/systemd-firstboot.xml \
 	man/systemd-fsck@.service.xml \
 	man/systemd-fstab-generator.xml \
 	man/systemd-getty-generator.xml \
diff --git a/Makefile.am b/Makefile.am
index 2aaaf22..8d47d2d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1679,6 +1679,30 @@ EXTRA_DIST += \
 	units/systemd-tmpfiles-clean.service.in
 
 # ------------------------------------------------------------------------------
+if ENABLE_FIRSTBOOT
+systemd_firstboot_SOURCES = \
+	src/firstboot/firstboot.c
+
+systemd_firstboot_LDADD = \
+	libsystemd-label.la \
+	libsystemd-internal.la \
+	libsystemd-shared.la \
+	-lcrypt
+
+rootbin_PROGRAMS += \
+	systemd-firstboot
+
+nodist_systemunit_DATA += \
+	units/systemd-firstboot.service
+
+SYSINIT_TARGET_WANTS += \
+	systemd-firstboot.service
+endif
+
+EXTRA_DIST += \
+	units/systemd-firstboot.service.in
+
+# ------------------------------------------------------------------------------
 systemd_machine_id_setup_SOURCES = \
 	src/machine-id-setup/machine-id-setup-main.c \
 	src/core/machine-id-setup.c \
diff --git a/configure.ac b/configure.ac
index c123a5d..b887f4e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -704,6 +704,14 @@ fi
 AM_CONDITIONAL(ENABLE_TMPFILES, [test "$have_tmpfiles" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_firstboot=no
+AC_ARG_ENABLE(firstboot, AS_HELP_STRING([--disable-firstboot], [disable firstboot support]))
+if test "x$enable_firstboot" != "xno"; then
+        have_firstboot=yes
+fi
+AM_CONDITIONAL(ENABLE_FIRSTBOOT, [test "$have_firstboot" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_randomseed=no
 AC_ARG_ENABLE(randomseed, AS_HELP_STRING([--disable-randomseed], [disable randomseed tools]))
 if test "x$enable_randomseed" != "xno"; then
@@ -1076,6 +1084,7 @@ AC_MSG_RESULT([
         bootchart:               ${have_bootchart}
         quotacheck:              ${have_quotacheck}
         tmpfiles:                ${have_tmpfiles}
+        firstboot:               ${have_firstboot}
         randomseed:              ${have_randomseed}
         backlight:               ${have_backlight}
         rfkill:                  ${have_rfkill}
diff --git a/man/systemd-firstboot.1 b/man/systemd-firstboot.1
new file mode 100644
index 0000000..f424d94
--- /dev/null
+++ b/man/systemd-firstboot.1
@@ -0,0 +1,222 @@
+'\" t
+.TH "SYSTEMD\-FIRSTBOOT" "1" "" "systemd 210" "systemd-firstboot"
+.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el       .ds Aq '
+.\" -----------------------------------------------------------------
+.\" * set default formatting
+.\" -----------------------------------------------------------------
+.\" disable hyphenation
+.nh
+.\" disable justification (adjust text to left margin only)
+.ad l
+.\" -----------------------------------------------------------------
+.\" * MAIN CONTENT STARTS HERE *
+.\" -----------------------------------------------------------------
+.SH "NAME"
+systemd-firstboot, systemd-firstboot.service \- Initialize basic system settings on or before the first boot\-up of a system
+.SH "SYNOPSIS"
+.HP \w'\fBsystemd\-firstboot\fR\ 'u
+\fBsystemd\-firstboot\fR [OPTIONS...]
+.PP
+systemd\-firstboot\&.service
+.SH "DESCRIPTION"
+.PP
+\fBsystemd\-firstboot\fR
+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:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+The system locale, more specifically the two locale variables
+\fILANG=\fR
+and
+\fILC_MESSAGES\fR
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+The system time zone
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+The system host name
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+The machine ID of the system
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+The root user\*(Aqs password
+.RE
+.PP
+Each of the fields may either be queried interactively from the users, set non\-interactively on the tool\*(Aqs command line, or be copied from a host system that is used to set up the system image\&.
+.PP
+If a setting is already initialized it will not be overwritten and the user will not be prompted for the setting\&.
+.PP
+Note that this tool operates directly on the file system and does not involve any running system services, unlike
+\fBlocalectl\fR(1),
+\fBtimedatectl\fR(1)
+or
+\fBhostnamectl\fR(1)\&. This allows
+\fBsystemd\-firstboot\fR
+to operate on mounted but not booted disk images and in early boot\&. It is not recommended to use
+\fBsystemd\-firstboot\fR
+on the running system while it is up\&.
+.SH "OPTIONS"
+.PP
+The following options are understood:
+.PP
+\fB\-\-root=\fR\fB\fIroot\fR\fR
+.RS 4
+Takes a directory path as an argument\&. All paths will be prefixed with the given alternate
+\fIroot\fR
+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\&.
+.RE
+.PP
+\fB\-\-locale=\fR\fB\fILOCALE\fR\fR, \fB\-\-locale\-messages=\fR\fB\fILOCALE\fR\fR
+.RS 4
+Sets the system locale, more specifically the
+\fILANG=\fR
+and
+\fILC_MESSAGES\fR
+settings\&. The argument should be a valid locale identifier, such as
+"de_DE\&.UTF\-8"\&. This controls the
+\fBlocale.conf\fR(5)
+configuration file\&.
+.RE
+.PP
+\fB\-\-timezone=\fR\fB\fITIMEZONE\fR\fR
+.RS 4
+Sets the system time zone\&. The argument should be a valid time zone identifier, such as
+"Europe/Berlin"\&. This controls the
+\fBlocaltime\fR(5)
+symlink\&.
+.RE
+.PP
+\fB\-\-hostname=\fR\fB\fIHOSTNAME\fR\fR
+.RS 4
+Sets the system hostname\&. The argument should be a host name, compatible with DNS\&. This controls the
+\fBhostname\fR(5)
+configuration file\&.
+.RE
+.PP
+\fB\-\-machine\-id=\fR\fB\fIID\fR\fR
+.RS 4
+Sets the system\*(Aqs machine ID\&. This controls the
+\fBmachine-id\fR(5)
+file\&.
+.RE
+.PP
+\fB\-\-root\-password=\fR\fB\fIPASSWORD\fR\fR, \fB\-\-root\-password\-file=\fR\fB\fIPATH\fR\fR
+.RS 4
+Sets the password of the system\*(Aqs root user\&. This creates a
+\fBshadow\fR(5)
+file\&. This setting exists in two forms:
+\fB\-\-root\-password=\fR
+accepts the password to set directly on the command line,
+\fB\-\-root\-password\-file=\fR
+reads it from a file\&. Note that it is not recommended specifying passwords on the command line as other users might be able to see them simply by invoking
+\fBps\fR(1)\&.
+.RE
+.PP
+\fB\-\-prompt\-locale\fR, \fB\-\-prompt\-timezone\fR, \fB\-\-prompt\-hostname\fR, \fB\-\-prompt\-root\-password\fR
+.RS 4
+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\&.
+.RE
+.PP
+\fB\-\-prompt\fR
+.RS 4
+Query the user for locale, timezone, hostname and root password\&. This is equivalent to specifying
+\fB\-\-prompt\-locale\fR,
+\fB\-\-prompt\-timezone\fR,
+\fB\-\-prompt\-hostname\fR,
+\fB\-\-prompt\-root\-password\fR
+in combination\&.
+.RE
+.PP
+\fB\-\-copy\-locale\fR, \fB\-\-copy\-timezone\fR, \fB\-\-copy\-root\-password\fR
+.RS 4
+Copy a specific basic setting from the host\&. This only works in combination with
+\fB\-\-root=\fR
+(see above)\&.
+.RE
+.PP
+\fB\-\-copy\fR
+.RS 4
+Copy locale, time zone and root password from the host\&. This is equivalent to specifying
+\fB\-\-copy\-locale\fR,
+\fB\-\-copy\-timezone\fR,
+\fB\-\-copy\-root\-password\fR
+in combination\&.
+.RE
+.PP
+\fB\-\-setup\-machine\-id\fR
+.RS 4
+Initialize the system\*(Aqs machine ID to a random ID\&. This only works in combination with
+\fB\-\-root=\fR\&.
+.RE
+.PP
+\fB\-h\fR, \fB\-\-help\fR
+.RS 4
+Print a short help text and exit\&.
+.RE
+.PP
+\fB\-\-version\fR
+.RS 4
+Print a short version string and exit\&.
+.RE
+.SH "EXIT STATUS"
+.PP
+On success, 0 is returned, a non\-zero failure code otherwise\&.
+.SH "SEE ALSO"
+.PP
+\fBsystemd\fR(1),
+\fBlocale.conf\fR(5),
+\fBlocaltime\fR(5),
+\fBhostname\fR(5),
+\fBmachine-id\fR(5),
+\fBshadow\fR(5),
+\fBsystemd-machine-id-setup\fR(1),
+\fBlocalectl\fR(1),
+\fBtimedatectl\fR(1),
+\fBhostnamectl\fR(1)
diff --git a/man/systemd-firstboot.service.1 b/man/systemd-firstboot.service.1
new file mode 100644
index 0000000..0899234
--- /dev/null
+++ b/man/systemd-firstboot.service.1
@@ -0,0 +1 @@
+.so man1/systemd-firstboot.1
diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml
new file mode 100644
index 0000000..67289da
--- /dev/null
+++ b/man/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 from
+    the 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,
+        <option>--root-password-file=</option> reads it from a file.
+        Note that it is not recommended specifying 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/man/systemd.unit.xml b/man/systemd.unit.xml
index 85bcf29..64791f2 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -887,6 +887,7 @@
                                 <term><varname>ConditionSecurity=</varname></term>
                                 <term><varname>ConditionCapability=</varname></term>
                                 <term><varname>ConditionACPower=</varname></term>
+                                <term><varname>ConditionFirstBoot=</varname></term>
                                 <term><varname>ConditionPathExists=</varname></term>
                                 <term><varname>ConditionPathExistsGlob=</varname></term>
                                 <term><varname>ConditionPathIsDirectory=</varname></term>
@@ -1056,6 +1057,18 @@
                                 all AC connectors are disconnected
                                 from a power source.</para>
 
+                                <para><varname>ConditionFirstBoot=</varname>
+                                takes a boolean argument. This
+                                condition may be used to
+                                conditionalize units on whether the
+                                system is booting up with an
+                                unpopulated <filename>/etc</filename>
+                                directory. This may be used to
+                                populate <filename>/etc</filename> on
+                                the first boot after factory reset, or
+                                when a new system instances boots up
+                                for the first time.</para>
+
                                 <para>With
                                 <varname>ConditionPathExists=</varname>
                                 a file existence condition is
diff --git a/src/core/condition.c b/src/core/condition.c
index 1448fa1..f816fef 100644
--- a/src/core/condition.c
+++ b/src/core/condition.c
@@ -91,6 +91,20 @@ static bool condition_test_capability(Condition *c) {
         return !!(capabilities & (1ULL << value)) == !c->negate;
 }
 
+static bool condition_test_first_boot(Condition *c) {
+        int r;
+
+        assert(c);
+        assert(c->parameter);
+        assert(c->type == CONDITION_FIRST_BOOT);
+
+        r = parse_boolean(c->parameter);
+        if (r < 0)
+                return c->negate;
+
+        return ((access("/run/systemd/first-boot", F_OK) >= 0) == !!r) == !c->negate;
+}
+
 static bool condition_test(Condition *c) {
         assert(c);
 
@@ -170,6 +184,9 @@ static bool condition_test(Condition *c) {
         case CONDITION_ARCHITECTURE:
                 return condition_test_architecture(c);
 
+        case CONDITION_FIRST_BOOT:
+                return condition_test_first_boot(c);
+
         case CONDITION_NULL:
                 return !c->negate;
 
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 2eb24c4..6e8eac5 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -155,6 +155,7 @@ Unit.ConditionPathIsReadWrite,   config_parse_unit_condition_path,   CONDITION_P
 Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path,   CONDITION_DIRECTORY_NOT_EMPTY, 0
 Unit.ConditionFileNotEmpty,      config_parse_unit_condition_path,   CONDITION_FILE_NOT_EMPTY,      0
 Unit.ConditionFileIsExecutable,  config_parse_unit_condition_path,   CONDITION_FILE_IS_EXECUTABLE,  0
+Unit.ConditionFirstBoot,         config_parse_unit_condition_path,   CONDITION_FIRST_BOOT,          0
 Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, 0
 Unit.ConditionArchitecture,      config_parse_unit_condition_string, CONDITION_ARCHITECTURE,        0
 Unit.ConditionVirtualization,    config_parse_unit_condition_string, CONDITION_VIRTUALIZATION,      0
diff --git a/src/core/main.c b/src/core/main.c
index 15bef1f..27d2d58 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -1291,6 +1291,7 @@ int main(int argc, char *argv[]) {
         bool loaded_policy = false;
         bool arm_reboot_watchdog = false;
         bool queue_default_job = false;
+        bool empty_etc = false;
         char *switch_root_dir = NULL, *switch_root_init = NULL;
         static struct rlimit saved_rlimit_nofile = { 0, 0 };
 
@@ -1575,9 +1576,21 @@ int main(int argc, char *argv[]) {
                 if (in_initrd())
                         log_info("Running in initial RAM disk.");
 
+                /* Let's check whether /etc is already populated. We
+                 * don't actually really check for that, but use
+                 * /etc/machine-id as flag file. This allows container
+                 * managers and installers to provision a couple of
+                 * files already. If the container manager wants to
+                 * provision the machine ID itself it should pass
+                 * $container_uuid to PID 1. */
+
+                empty_etc = access("/etc/machine-id", F_OK) < 0;
+                if (empty_etc)
+                        log_info("Running with unpopulated /etc.");
+
         } else {
                 _cleanup_free_ char *t;
-		    
+
                 t = uid_to_name(getuid());
                 log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")",
                           arg_action == ACTION_TEST ? " test" : "", getuid(), t);
@@ -1655,6 +1668,7 @@ int main(int argc, char *argv[]) {
 
         manager_set_defaults(m);
         manager_set_show_status(m, arg_show_status);
+        manager_set_first_boot(m, empty_etc);
 
         /* Remember whether we should queue the default job */
         queue_default_job = !arg_serialization || arg_switched_root;
diff --git a/src/core/manager.c b/src/core/manager.c
index e08b3a5..e284bd8 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -2751,6 +2751,9 @@ void manager_check_finished(Manager *m) {
         /* Turn off confirm spawn now */
         m->confirm_spawn = false;
 
+        /* This is no longer the first boot */
+        manager_set_first_boot(m, false);
+
         /* No need to update ask password status when we're going non-interactive */
         manager_close_ask_password(m);
 
@@ -3086,6 +3089,20 @@ static bool manager_get_show_status(Manager *m) {
         return plymouth_running();
 }
 
+void manager_set_first_boot(Manager *m, bool b) {
+        assert(m);
+
+        if (m->running_as != SYSTEMD_SYSTEM)
+                return;
+
+        m->first_boot = b;
+
+        if (m->first_boot)
+                touch("/run/systemd/first-boot");
+        else
+                unlink("/run/systemd/first-boot");
+}
+
 void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...) {
         va_list ap;
 
diff --git a/src/core/manager.h b/src/core/manager.h
index a77f201..9005b7d 100644
--- a/src/core/manager.h
+++ b/src/core/manager.h
@@ -216,6 +216,7 @@ struct Manager {
         bool dispatching_dbus_queue:1;
 
         bool taint_usr:1;
+        bool first_boot:1;
 
         bool test_run:1;
 
@@ -323,6 +324,8 @@ void manager_undo_generators(Manager *m);
 void manager_recheck_journal(Manager *m);
 
 void manager_set_show_status(Manager *m, ShowStatus mode);
+void manager_set_first_boot(Manager *m, bool b);
+
 void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...) _printf_(4,5);
 void manager_flip_auto_status(Manager *m, bool enable);
 
diff --git a/src/firstboot/Makefile b/src/firstboot/Makefile
new file mode 100644
index 0000000..9d07505
--- /dev/null
+++ b/src/firstboot/Makefile
@@ -0,0 +1,28 @@
+#  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/>.
+
+# This file is a dirty trick to simplify compilation from within
+# emacs. This file is not intended to be distributed. So, don't touch
+# it, even better ignore it!
+
+all:
+	$(MAKE) -C ..
+
+clean:
+	$(MAKE) -C .. clean
+
+.PHONY: all clean
diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c
new file mode 100644
index 0000000..d145a8c
--- /dev/null
+++ b/src/firstboot/firstboot.c
@@ -0,0 +1,1532 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  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 <unistd.h>
+#include <getopt.h>
+#include <shadow.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "fileio.h"
+#include "util.h"
+#include "build.h"
+#include "mkdir.h"
+#include "log.h"
+#include "time-util.h"
+#include "path-util.h"
+#include "ask-password-api.h"
+#include "strv.h"
+
+#include "set.h"
+#include <sys/mman.h>
+
+#define USEC_INFINITY ((usec_t) -1)
+
+#define strjoina(a, ...)                                                \
+        ({                                                              \
+                const char *_appendees_[] = { a, __VA_ARGS__ };         \
+                char *_d_, *_p_;                                        \
+                int _len_ = 0;                                          \
+                unsigned _i_;                                           \
+                for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
+                        _len_ += strlen(_appendees_[_i_]);              \
+                _p_ = _d_ = alloca(_len_ + 1);                          \
+                for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \
+                        _p_ = stpcpy(_p_, _appendees_[_i_]);            \
+                *_p_ = 0;                                               \
+                _d_;                                                    \
+        })
+
+
+#define prefix_roota(root, path)                                        \
+        ({                                                              \
+                const char* _path = (path), *_root = (root), *_ret;     \
+                char *_p, *_n;                                          \
+                size_t _l;                                              \
+                while (_path[0] == '/' && _path[1] == '/')              \
+                        _path ++;                                       \
+                if (isempty(_root) || path_equal(_root, "/"))           \
+                        _ret = _path;                                   \
+                else {                                                  \
+                        _l = strlen(_root) + 1 + strlen(_path) + 1;     \
+                        _n = alloca(_l);                                \
+                        _p = stpcpy(_n, _root);                         \
+                        while (_p > _n && _p[-1] == '/')                \
+                                _p--;                                   \
+                        if (_path[0] != '/')                            \
+                                *(_p++) = '/';                          \
+                        strcpy(_p, _path);                              \
+                        _ret = _n;                                      \
+                }                                                       \
+                _ret;                                                   \
+        })
+static int ask_string(char **ret, const char *text, ...) {
+        assert(ret);
+        assert(text);
+
+        for (;;) {
+                char line[LINE_MAX];
+                va_list ap;
+
+                if (on_tty())
+                        fputs(ANSI_HIGHLIGHT_ON, stdout);
+
+                va_start(ap, text);
+                vprintf(text, ap);
+                va_end(ap);
+
+                if (on_tty())
+                        fputs(ANSI_HIGHLIGHT_OFF, stdout);
+
+                fflush(stdout);
+
+                errno = 0;
+                if (!fgets(line, sizeof(line), stdin))
+                        return errno ? -errno : -EIO;
+
+                if (!endswith(line, "\n"))
+                        putchar('\n');
+                else {
+                        char *s;
+
+                        if (isempty(line))
+                                continue;
+
+                        truncate_nl(line);
+                        s = strdup(line);
+                        if (!s)
+                                return -ENOMEM;
+
+                        *ret = s;
+                        return 0;
+                }
+        }
+}
+
+static inline void *mfree(void *memory) {
+        free(memory);
+        return NULL;
+}
+static int free_and_strdup(char **p, const char *s) {
+        char *t;
+
+        assert(p);
+
+        /* Replaces a string pointer with an strdup()ed new string,
+         * possibly freeing the old one. */
+
+        if (streq_ptr(*p, s))
+                return 0;
+
+        if (s) {
+                t = strdup(s);
+                if (!t)
+                        return -ENOMEM;
+        } else
+                t = NULL;
+
+        free(*p);
+        *p = t;
+
+        return 1;
+}
+
+static int get_timezones(char ***ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_strv_free_ char **zones = NULL;
+        size_t n_zones = 0, n_allocated = 0;
+
+        assert(ret);
+
+        zones = strv_new("UTC", NULL);
+        if (!zones)
+                return -ENOMEM;
+
+        n_allocated = 2;
+        n_zones = 1;
+
+        f = fopen("/usr/share/zoneinfo/zone.tab", "re");
+        if (f) {
+                char l[LINE_MAX];
+
+                FOREACH_LINE(l, f, return -errno) {
+                        char *p, *w;
+                        size_t k;
+
+                        p = strstrip(l);
+
+                        if (isempty(p) || *p == '#')
+                                continue;
+
+                        /* Skip over country code */
+                        p += strcspn(p, WHITESPACE);
+                        p += strspn(p, WHITESPACE);
+
+                        /* Skip over coordinates */
+                        p += strcspn(p, WHITESPACE);
+                        p += strspn(p, WHITESPACE);
+
+                        /* Found timezone name */
+                        k = strcspn(p, WHITESPACE);
+                        if (k <= 0)
+                                continue;
+
+                        w = strndup(p, k);
+                        if (!w)
+                                return -ENOMEM;
+
+                        if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) {
+                                free(w);
+                                return -ENOMEM;
+                        }
+
+                        zones[n_zones++] = w;
+                        zones[n_zones] = NULL;
+                }
+
+                strv_sort(zones);
+
+        } else if (errno != ENOENT)
+                return -errno;
+
+        *ret = zones;
+        zones = NULL;
+
+        return 0;
+}
+
+static bool timezone_is_valid(const char *name) {
+        bool slash = false;
+        const char *p, *t;
+        struct stat st;
+
+        if (!name || *name == 0 || *name == '/')
+                return false;
+
+        for (p = name; *p; p++) {
+                if (!(*p >= '0' && *p <= '9') &&
+                    !(*p >= 'a' && *p <= 'z') &&
+                    !(*p >= 'A' && *p <= 'Z') &&
+                    !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
+                        return false;
+
+                if (*p == '/') {
+
+                        if (slash)
+                                return false;
+
+                        slash = true;
+                } else
+                        slash = false;
+        }
+
+        if (slash)
+                return false;
+
+        t = strjoina("/usr/share/zoneinfo/", name);
+        if (stat(t, &st) < 0)
+                return false;
+
+        if (!S_ISREG(st.st_mode))
+                return false;
+
+        return true;
+}
+
+static bool hostname_valid_char2(char c) {
+        return
+                (c >= 'a' && c <= 'z') ||
+                (c >= 'A' && c <= 'Z') ||
+                (c >= '0' && c <= '9') ||
+                c == '-' ||
+                c == '_' ||
+                c == '.';
+}
+
+
+static bool hostname_is_valid2(const char *s, bool relax) {
+        const char *p;
+        bool dot;
+        unsigned dots = 0;
+
+        if (isempty(s))
+                return false;
+
+        /* Doesn't accept empty hostnames, hostnames with
+         * leading dots, and hostnames with multiple dots in a
+         * sequence. Also ensures that the length stays below
+         * HOST_NAME_MAX. */
+
+        for (p = s, dot = true; *p; p++) {
+                if (*p == '.') {
+                        if (dot)
+                                return false;
+
+                        dot = true;
+                        dots ++;
+                } else {
+                        if (!hostname_valid_char2(*p))
+                                return false;
+
+                        dot = false;
+                }
+        }
+
+        if (dot && (dots < 2 || !relax))
+                return false;
+
+        if (p-s > HOST_NAME_MAX)
+                return false;
+
+        return true;
+}
+
+
+static bool filename_is_valid(const char *p) {
+
+        if (isempty(p))
+                return false;
+
+        if (strchr(p, '/'))
+                return false;
+
+        if (streq(p, "."))
+                return false;
+
+        if (streq(p, ".."))
+                return false;
+
+        if (strlen(p) > FILENAME_MAX)
+                return false;
+
+        return true;
+}
+
+static int fflush_and_check(FILE *f) {
+        assert(f);
+
+        errno = 0;
+        fflush(f);
+
+        if (ferror(f))
+                return errno ? -errno : -EIO;
+
+        return 0;
+}
+
+static int safe_close(int fd) {
+
+        /*
+         * Like close_nointr() but cannot fail. Guarantees errno is
+         * unchanged. Is a NOP with negative fds passed, and returns
+         * -1, so that it can be used in this syntax:
+         *
+         * fd = safe_close(fd);
+         */
+
+        if (fd >= 0) {
+                close_nointr_nofail (fd);
+        }
+
+        return -1;
+}
+
+static int take_password_lock(const char *root) {
+
+        struct flock flock = {
+                .l_type = F_WRLCK,
+                .l_whence = SEEK_SET,
+                .l_start = 0,
+                .l_len = 0,
+        };
+
+        const char *path;
+        int fd, r;
+
+        /* This is roughly the same as lckpwdf(), but not as awful. We
+         * don't want to use alarm() and signals, hence we implement
+         * our own trivial version of this.
+         *
+         * Note that shadow-utils also takes per-database locks in
+         * addition to lckpwdf(). However, we don't given that they
+         * are redundant as they they invoke lckpwdf() first and keep
+         * it during everything they do. The per-database locks are
+         * awfully racy, and thus we just won't do them. */
+
+        if (root)
+                path = strjoina(root, "/etc/.pwd.lock");
+        else
+                path = "/etc/.pwd.lock";
+
+        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+        if (fd < 0)
+                return -errno;
+
+        r = fcntl(fd, F_SETLKW, &flock);
+        if (r < 0) {
+                safe_close(fd);
+                return -errno;
+        }
+
+        return fd;
+}
+
+
+typedef enum LocaleVariable {
+        /* We don't list LC_ALL here on purpose. People should be
+         * using LANG instead. */
+
+        VARIABLE_LANG,
+        VARIABLE_LANGUAGE,
+        VARIABLE_LC_CTYPE,
+        VARIABLE_LC_NUMERIC,
+        VARIABLE_LC_TIME,
+        VARIABLE_LC_COLLATE,
+        VARIABLE_LC_MONETARY,
+        VARIABLE_LC_MESSAGES,
+        VARIABLE_LC_PAPER,
+        VARIABLE_LC_NAME,
+        VARIABLE_LC_ADDRESS,
+        VARIABLE_LC_TELEPHONE,
+        VARIABLE_LC_MEASUREMENT,
+        VARIABLE_LC_IDENTIFICATION,
+        _VARIABLE_LC_MAX,
+        _VARIABLE_LC_INVALID = -1
+} LocaleVariable;
+
+#include "set.h"
+#include "utf8.h"
+
+static int add_locales_from_archive(Set *locales) {
+        /* Stolen from glibc... */
+
+        struct locarhead {
+                uint32_t magic;
+                /* Serial number.  */
+                uint32_t serial;
+                /* Name hash table.  */
+                uint32_t namehash_offset;
+                uint32_t namehash_used;
+                uint32_t namehash_size;
+                /* String table.  */
+                uint32_t string_offset;
+                uint32_t string_used;
+                uint32_t string_size;
+                /* Table with locale records.  */
+                uint32_t locrectab_offset;
+                uint32_t locrectab_used;
+                uint32_t locrectab_size;
+                /* MD5 sum hash table.  */
+                uint32_t sumhash_offset;
+                uint32_t sumhash_used;
+                uint32_t sumhash_size;
+        };
+
+        struct namehashent {
+                /* Hash value of the name.  */
+                uint32_t hashval;
+                /* Offset of the name in the string table.  */
+                uint32_t name_offset;
+                /* Offset of the locale record.  */
+                uint32_t locrec_offset;
+        };
+
+        const struct locarhead *h;
+        const struct namehashent *e;
+        const void *p = MAP_FAILED;
+        _cleanup_close_ int fd = -1;
+        size_t sz = 0;
+        struct stat st;
+        unsigned i;
+        int r;
+
+        fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
+        if (fd < 0)
+                return errno == ENOENT ? 0 : -errno;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (!S_ISREG(st.st_mode))
+                return -EBADMSG;
+
+        if (st.st_size < (off_t) sizeof(struct locarhead))
+                return -EBADMSG;
+
+        p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+        if (p == MAP_FAILED)
+                return -errno;
+
+        h = (const struct locarhead *) p;
+        if (h->magic != 0xde020109 ||
+            h->namehash_offset + h->namehash_size > st.st_size ||
+            h->string_offset + h->string_size > st.st_size ||
+            h->locrectab_offset + h->locrectab_size > st.st_size ||
+            h->sumhash_offset + h->sumhash_size > st.st_size) {
+                r = -EBADMSG;
+                goto finish;
+        }
+
+        e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
+        for (i = 0; i < h->namehash_size; i++) {
+                char *z;
+
+                if (e[i].locrec_offset == 0)
+                        continue;
+
+                if (!utf8_is_valid((char*) p + e[i].name_offset))
+                        continue;
+
+                z = strdup((char*) p + e[i].name_offset);
+                if (!z) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                r = set_consume(locales, z);
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = 0;
+
+ finish:
+        if (p != MAP_FAILED)
+                munmap((void*) p, sz);
+
+        return r;
+}
+
+static int add_locales_from_libdir (Set *locales) {
+        _cleanup_closedir_ DIR *dir = NULL;
+        struct dirent *entry;
+        int r;
+
+        dir = opendir("/usr/lib/locale");
+        if (!dir)
+                return errno == ENOENT ? 0 : -errno;
+
+        FOREACH_DIRENT(entry, dir, return -errno) {
+                char *z;
+
+                if (entry->d_type != DT_DIR)
+                        continue;
+
+                z = strdup(entry->d_name);
+                if (!z)
+                        return -ENOMEM;
+
+                r = set_consume(locales, z);
+                if (r < 0 && r != -EEXIST)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int get_locales(char ***ret) {
+        _cleanup_set_free_ Set *locales = NULL;
+        _cleanup_strv_free_ char **l = NULL;
+        int r;
+
+        locales = set_new(string_hash_func, string_compare_func);
+        if (!locales)
+                return -ENOMEM;
+
+        r = add_locales_from_archive(locales);
+        if (r < 0 && r != -ENOENT)
+                return r;
+
+        r = add_locales_from_libdir(locales);
+        if (r < 0)
+                return r;
+
+        l = set_get_strv(locales);
+        if (!l)
+                return -ENOMEM;
+
+        strv_sort(l);
+
+        *ret = l;
+        l = NULL;
+
+        return 0;
+}
+
+static bool locale_is_valid(const char *name) {
+
+        if (isempty(name))
+                return false;
+
+        if (strlen(name) >= 128)
+                return false;
+
+        if (!utf8_is_valid(name))
+                return false;
+
+        if (!filename_is_valid(name))
+                return false;
+
+        if (!string_is_safe(name))
+                return false;
+
+        return true;
+}
+
+static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
+        [VARIABLE_LANG] = "LANG",
+        [VARIABLE_LANGUAGE] = "LANGUAGE",
+        [VARIABLE_LC_CTYPE] = "LC_CTYPE",
+        [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
+        [VARIABLE_LC_TIME] = "LC_TIME",
+        [VARIABLE_LC_COLLATE] = "LC_COLLATE",
+        [VARIABLE_LC_MONETARY] = "LC_MONETARY",
+        [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
+        [VARIABLE_LC_PAPER] = "LC_PAPER",
+        [VARIABLE_LC_NAME] = "LC_NAME",
+        [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
+        [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
+        [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
+        [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
+};
+
+const char* locale_variable_to_string(LocaleVariable i) _const_;
+LocaleVariable locale_variable_from_string(const char *s) _pure_;
+
+DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);
+
+
+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 void clear_string(char *x) {
+
+        if (!x)
+                return;
+
+        /* A delicious drop of snake-oil! */
+        memset(x, 'x', strlen(x));
+}
+
+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("Failed to read os-release file: %s",strerror(-r));
+
+        printf("\nWelcome to your new installation of %s!\nPlease configure a few basic system settings:\n\n",
+               isempty(pretty_name) ? "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): ", draw_special_char(DRAW_TRIANGULAR_BULLET), text);
+                if (r < 0) {
+                        log_error("Failed to query user: %s",strerror(-r));
+                        return r;
+                }
+
+                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) {
+            log_error("Cannot query locales list: %s",strerror(-r));
+            return r;
+        }
+
+        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 (faccessat(AT_FDCWD, etc_localeconf, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        if (arg_copy_locale && arg_root) {
+
+                mkdir_parents(etc_localeconf, 0755);
+                r = copy_file("/etc/locale.conf", etc_localeconf, 0);
+                if (r != -ENOENT) {
+                        if (r < 0)
+                        {
+                            log_error("Failed to copy %s: %s", etc_localeconf,strerror(-r));
+                            return r;
+                        }
+
+                        log_info("%s copied.", etc_localeconf);
+                        return 0;
+                }
+                r = chmod ("/etc/locale.conf", S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
+                if (r < 0)
+                {
+                    log_error("Failed to chmod 0644 %s: %s", etc_localeconf,strerror(-r));
+                    return r;
+                }
+        }
+
+        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)
+        {
+            log_error("Failed to write %s: %s", etc_localeconf,strerror(-r));
+            return r;
+        }
+
+        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)
+        {
+            log_error("Cannot query timezone list: %s",strerror(-r));
+            return r;
+        }
+
+        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 (faccessat(AT_FDCWD, etc_localtime, F_OK, AT_SYMLINK_NOFOLLOW) >= 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)
+                        {
+                            log_error("Failed to read host timezone: %s",strerror(-r));
+                            return r;
+                        }
+
+                        mkdir_parents(etc_localtime, 0755);
+                        if (symlink(p, etc_localtime) < 0)
+                        {
+                            r = -errno;
+                            log_error("Failed to create %s symlink: %s", etc_localtime,strerror(-r));
+                            return r;
+                        }
+
+                        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)
+        {
+            r = -errno;
+            log_error("Failed to create %s symlink: %s", etc_localtime,strerror(-r));
+            return r;
+        }
+
+        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): ", draw_special_char(DRAW_TRIANGULAR_BULLET));
+                if (r < 0)
+                {
+                    log_error("Failed to query hostname: %s",strerror(-r));
+                    return r;
+                }
+
+                if (isempty(h)) {
+                        log_warning("No hostname entered, skipping.");
+                        break;
+                }
+
+                if (!hostname_is_valid2(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,false);
+                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 (faccessat(AT_FDCWD, etc_hostname, F_OK, AT_SYMLINK_NOFOLLOW) >= 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);
+        if (r < 0)
+        {
+            log_error("Failed to write %s: %s", etc_hostname,strerror(-r));
+            return r;
+        }
+
+        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 (faccessat(AT_FDCWD, etc_machine_id, F_OK, AT_SYMLINK_NOFOLLOW) >= 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));
+        if (r < 0)
+        {
+            log_error("Failed to write machine id: %s",strerror(-r));
+            return r;
+        }
+
+        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 (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        print_welcome();
+        putchar('\n');
+
+        msg1 = strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter a new root password (empty to skip): ");
+        msg2 = strjoina(draw_special_char(DRAW_TRIANGULAR_BULLET), " Please enter new root password again: ");
+
+        for (;;) {
+                _cleanup_free_ char *a = NULL, *b = NULL;
+
+                r = ask_password_tty(msg1, 0, NULL, &a);
+                if (r < 0)
+                {
+                    log_error("Failed to query root password: %s",strerror(-r));
+                    return r;
+                }
+
+                if (isempty(a)) {
+                        log_warning("No password entered, skipping.");
+                        break;
+                }
+
+                r = ask_password_tty(msg2, 0, NULL, &b);
+                if (r < 0) {
+                        log_error("Failed to query root password: %s",strerror(-r));
+                        clear_string(a);
+                        return r;
+                }
+
+                if (!streq(a, b)) {
+                        log_error("Entered passwords did not match, please try again.");
+                        clear_string(a);
+                        clear_string(b);
+                        continue;
+                }
+
+                clear_string(b);
+                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 ? -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 (faccessat(AT_FDCWD, etc_shadow, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)
+                return 0;
+
+        mkdir_parents(etc_shadow, 0755);
+
+        lock = take_password_lock(arg_root);
+        if (lock < 0)
+                return lock;
+
+        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;
+
+                                r = -errno;
+                                log_error("Failed to find shadow entry for root: %s",strerror(-r));
+                                return r;
+                        }
+
+                        r = write_root_shadow(etc_shadow, p);
+                        if (r < 0)
+                        {
+                            log_error("Failed to write %s: %s", etc_shadow,strerror(-r));
+                            return r;
+                        }
+
+                        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)
+        {
+            log_error("Failed to get salt: %s",strerror(-r));
+            return r;
+        }
+
+        /* 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;
+
+                r = -errno;
+                log_error("Failed to encrypt password: %s",strerror(-r));
+                return r;
+        }
+
+        item.sp_lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
+
+        r = write_root_shadow(etc_shadow, &item);
+        if (r < 0)
+        {
+            log_error("Failed to write %s: %s", etc_shadow,strerror(-r));
+            return r;
+        }
+
+        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:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+
+                case ARG_ROOT:
+                        free(arg_root);
+                        arg_root = path_make_absolute_cwd(optarg);
+                        if (!arg_root)
+                                return log_oom();
+
+                        path_kill_slashes(arg_root);
+
+                        if (path_equal(arg_root, "/"))
+                                arg_root = mfree(arg_root);
+
+                        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)
+                        {
+                            log_error("Failed to read %s: %s", optarg,strerror(-r));
+                            return r;
+                        }
+
+                        break;
+
+                case ARG_HOSTNAME:
+                        if (!hostname_is_valid2(optarg, true)) {
+                                log_error("Host name %s is not valid.", optarg);
+                                return -EINVAL;
+                        }
+
+                        hostname_cleanup(optarg,false);
+                        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)
+                        {
+                            log_error("Failed to generate randomized machine ID: %s",strerror(-r));
+                            return r;
+                        }
+
+                        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);
+        clear_string(arg_root_password);
+        free(arg_root_password);
+
+        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/shared/condition-util.c b/src/shared/condition-util.c
index 9961cb4..5e4afa4 100644
--- a/src/shared/condition-util.c
+++ b/src/shared/condition-util.c
@@ -260,6 +260,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_HOST] = "ConditionHost",
         [CONDITION_AC_POWER] = "ConditionACPower",
         [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
+        [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
         [CONDITION_NULL] = "ConditionNull"
 };
 
diff --git a/src/shared/condition-util.h b/src/shared/condition-util.h
index 63d945e..12c04ec 100644
--- a/src/shared/condition-util.h
+++ b/src/shared/condition-util.h
@@ -44,6 +44,7 @@ typedef enum ConditionType {
         CONDITION_HOST,
         CONDITION_AC_POWER,
         CONDITION_ARCHITECTURE,
+        CONDITION_FIRST_BOOT,
         CONDITION_NULL,
         _CONDITION_TYPE_MAX,
         _CONDITION_TYPE_INVALID = -1
diff --git a/units/systemd-firstboot.service.in b/units/systemd-firstboot.service.in
new file mode 100644
index 0000000..405c6f3
--- /dev/null
+++ b/units/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
-- 
2.1.4

