File firstboot.patch of Package systemd.1059
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