summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CODING_STYLE5
-rw-r--r--Makefile.am5
-rw-r--r--NEWS4
-rw-r--r--hwdb/60-keyboard.hwdb20
-rwxr-xr-xhwdb/acpi-update.py4
-rwxr-xr-xhwdb/parse_hwdb.py28
-rw-r--r--man/systemd-nspawn.xml36
-rw-r--r--man/systemd.exec.xml6
-rw-r--r--man/systemd.netdev.xml4
-rw-r--r--man/systemd.nspawn.xml11
-rw-r--r--man/systemd.offline-updates.xml43
-rw-r--r--man/systemd.service.xml13
-rw-r--r--man/systemd.special.xml21
-rw-r--r--po/cs.po16
-rw-r--r--rules/60-persistent-storage.rules2
-rw-r--r--src/activate/activate.c2
-rw-r--r--src/basic/fs-util.c89
-rw-r--r--src/basic/fs-util.h9
-rw-r--r--src/basic/mount-util.c14
-rw-r--r--src/basic/mount-util.h2
-rw-r--r--src/basic/path-util.c45
-rw-r--r--src/basic/path-util.h4
-rw-r--r--src/boot/efi/boot.c2
-rw-r--r--src/core/automount.c2
-rw-r--r--src/core/cgroup.c11
-rw-r--r--src/core/device.c2
-rw-r--r--src/core/ima-setup.c22
-rw-r--r--src/core/machine-id-setup.c2
-rw-r--r--src/core/mount-setup.c2
-rw-r--r--src/core/mount.c2
-rw-r--r--src/core/namespace.c6
-rw-r--r--src/core/service.c48
-rw-r--r--src/core/service.h1
-rw-r--r--src/core/swap.c2
-rw-r--r--src/core/unit.c3
-rw-r--r--src/delta/delta.c15
-rw-r--r--src/gpt-auto-generator/gpt-auto-generator.c2
-rwxr-xr-xsrc/journal-remote/log-generator.py4
-rw-r--r--src/journal/journalctl.c18
-rw-r--r--src/libsystemd/sd-device/sd-device.c2
-rw-r--r--src/libudev/libudev.c2
-rw-r--r--src/login/logind-user.c2
-rw-r--r--src/network/netdev/bridge.c3
-rw-r--r--src/nspawn/nspawn-gperf.gperf2
-rw-r--r--src/nspawn/nspawn-mount.c315
-rw-r--r--src/nspawn/nspawn-mount.h7
-rw-r--r--src/nspawn/nspawn-register.c5
-rw-r--r--src/nspawn/nspawn-settings.c26
-rw-r--r--src/nspawn/nspawn-settings.h1
-rw-r--r--src/nspawn/nspawn.c145
-rw-r--r--src/shared/bus-unit-util.c1
-rw-r--r--src/shared/condition.c2
-rw-r--r--src/shared/machine-pool.c2
-rw-r--r--src/systemctl/systemctl.c4
-rw-r--r--src/test/test-copy.c2
-rw-r--r--src/test/test-fs-util.c103
-rw-r--r--src/test/test-path-util.c60
-rw-r--r--src/udev/udev-rules.c2
-rw-r--r--src/udev/udevadm-monitor.c2
-rwxr-xr-xtest/networkd-test.py41
-rwxr-xr-xtest/rule-syntax-check.py8
-rwxr-xr-xtest/sysv-generator-test.py2
-rw-r--r--units/.gitignore1
-rw-r--r--units/system-update-cleanup.service.in32
-rw-r--r--units/system-update.target5
65 files changed, 896 insertions, 408 deletions
diff --git a/CODING_STYLE b/CODING_STYLE
index e89b3c67e5..ed61ea9d28 100644
--- a/CODING_STYLE
+++ b/CODING_STYLE
@@ -429,3 +429,8 @@
and Linux/GNU-specific APIs, we generally prefer the POSIX APIs. If there
aren't, we are happy to use GNU or Linux APIs, and expect non-GNU
implementations of libc to catch up with glibc.
+
+- Whenever installing a signal handler, make sure to set SA_RESTART for it, so
+ that interrupted system calls are automatically restarted, and we minimize
+ hassles with handling EINTR (in particular as EINTR handling is pretty broken
+ on Linux).
diff --git a/Makefile.am b/Makefile.am
index 5cec19fbb8..124e1867cd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -529,6 +529,7 @@ nodist_systemunit_DATA = \
units/serial-getty@.service \
units/console-getty.service \
units/container-getty@.service \
+ units/system-update-cleanup.service \
units/systemd-initctl.service \
units/systemd-remount-fs.service \
units/systemd-ask-password-wall.service \
@@ -592,6 +593,7 @@ EXTRA_DIST += \
units/console-getty.service.m4.in \
units/container-getty@.service.m4.in \
units/rescue.service.in \
+ units/system-update-cleanup.service.in \
units/systemd-initctl.service.in \
units/systemd-remount-fs.service.in \
units/systemd-update-utmp.service.in \
@@ -5717,6 +5719,9 @@ networkctl_LDADD = \
dist_bashcompletion_data += \
shell-completion/bash/networkctl
+dist_zshcompletion_data += \
+ shell-completion/zsh/_networkctl
+
test_networkd_conf_SOURCES = \
src/network/test-networkd-conf.c
diff --git a/NEWS b/NEWS
index 7ca129db80..17aa5fb025 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,10 @@ CHANGES WITH 233 in spe
The 'n' choice for the confirmation spawn prompt has been removed,
because its meaning was confusing.
+ * Services of Type=notify require a READY=1 notification to be sent
+ during startup. If no such message is sent, the service now fails,
+ even if the main process exited with a successful exit code.
+
CHANGES WITH 232:
* The new RemoveIPC= option can be used to remove IPC objects owned by
diff --git a/hwdb/60-keyboard.hwdb b/hwdb/60-keyboard.hwdb
index c4031ab587..5f81be5c47 100644
--- a/hwdb/60-keyboard.hwdb
+++ b/hwdb/60-keyboard.hwdb
@@ -4,7 +4,7 @@
# scan codes to add to the AT keyboard's 'force-release' list.
#
# The lookup keys are composed in:
-# 60-keyboard.rules
+# 60-evdev.rules
#
# Note: The format of the "evdev:" prefix match key is a
# contract between the rules file and the hardware data, it might
@@ -42,6 +42,13 @@
#
# To debug key presses and access scan code mapping data of
# an input device use the commonly available tool: evtest(1).
+
+# A device with a fixed keyboard layout that must not be changed by
+# the desktop environment may specify that layout as:
+# XKB_FIXED_LAYOUT="us"
+# XKB_FIXED_VARIANT=""
+# Examples of such devices: the Yubikey or other key-code generating
+# devices.
#
# To update this file, create a new file
# /etc/udev/hwdb.d/70-keyboard.hwdb
@@ -1244,3 +1251,14 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnDIXONSP:pnDIXON*:pvr*
KEYBOARD_KEY_a0=! # mute
KEYBOARD_KEY_ae=! # volume down
KEYBOARD_KEY_b0=! # volume up
+
+###########################################################
+# Fixed layout devices
+###########################################################
+
+# Yubico Yubico Yubikey II"
+evdev:input:b0003v1050p0010*
+# Yubico Yubikey NEO OTP+CCID
+evdev:input:b0003v1050p0111*
+ XKB_FIXED_LAYOUT="us"
+ XKB_FIXED_VARIANT=""
diff --git a/hwdb/acpi-update.py b/hwdb/acpi-update.py
index 2dc8c7c064..50da531dc6 100755
--- a/hwdb/acpi-update.py
+++ b/hwdb/acpi-update.py
@@ -31,7 +31,7 @@ class PNPTableParser(HTMLParser):
elif self.state == State.AFTER_PNPID:
self.state = State.DATE
else:
- raise Error("Unexpected field")
+ raise ValueError
self.data = ""
@@ -48,7 +48,7 @@ class PNPTableParser(HTMLParser):
elif self.state == State.DATE:
self.state = State.NOWHERE
else:
- raise Error("Unexpected field")
+ raise ValueError
def handle_data(self, data):
self.data += data
diff --git a/hwdb/parse_hwdb.py b/hwdb/parse_hwdb.py
index 5d4c5ea64d..16f74b0777 100755
--- a/hwdb/parse_hwdb.py
+++ b/hwdb/parse_hwdb.py
@@ -26,7 +26,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-import functools
import glob
import string
import sys
@@ -35,9 +34,9 @@ import os
try:
from pyparsing import (Word, White, Literal, ParserElement, Regex,
LineStart, LineEnd,
- ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group,
+ OneOrMore, Combine, Or, Optional, Suppress, Group,
nums, alphanums, printables,
- stringEnd, pythonStyleComment,
+ stringEnd, pythonStyleComment, QuotedString,
ParseBaseException)
except ImportError:
print('pyparsing is not available')
@@ -56,9 +55,10 @@ except ImportError:
lru_cache = lambda: (lambda f: f)
EOL = LineEnd().suppress()
-EMPTYLINE = LineStart() + LineEnd()
+EMPTYLINE = LineEnd()
COMMENTLINE = pythonStyleComment + EOL
INTEGER = Word(nums)
+STRING = QuotedString('"')
REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
@@ -66,7 +66,7 @@ TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
'evdev': ('name', 'atkbd', 'input'),
'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
'keyboard': ('name', ),
- }
+ }
@lru_cache()
def hwdb_grammar():
@@ -76,13 +76,13 @@ def hwdb_grammar():
for category, conn in TYPES.items())
matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
propertyline = (White(' ', exact=1).suppress() +
- Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) +
+ Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! "') - Optional(pythonStyleComment)) +
EOL)
propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
- (EMPTYLINE ^ stringEnd()).suppress() )
+ (EMPTYLINE ^ stringEnd()).suppress())
commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
@@ -103,17 +103,19 @@ def property_grammar():
('POINTINGSTICK_SENSITIVITY', INTEGER),
('POINTINGSTICK_CONST_ACCEL', REAL),
('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
- )
+ ('XKB_FIXED_LAYOUT', STRING),
+ ('XKB_FIXED_VARIANT', STRING),
+ )
fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
for name, val in props]
kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
- Suppress('=') -
('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
- ]
+ ]
abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
- Suppress('=') -
Word(nums + ':')('VALUE')
- ]
+ ]
grammar = Or(fixed_props + kbd_props + abs_props)
@@ -133,7 +135,8 @@ def convert_properties(group):
def parse(fname):
grammar = hwdb_grammar()
try:
- parsed = grammar.parseFile(fname)
+ with open(fname, 'r', encoding='UTF-8') as f:
+ parsed = grammar.parseFile(f)
except ParseBaseException as e:
error('Cannot parse {}: {}', fname, e)
return []
@@ -185,8 +188,7 @@ def print_summary(fname, groups):
.format(fname,
len(groups),
sum(len(matches) for matches, props in groups),
- sum(len(props) for matches, props in groups),
- ))
+ sum(len(props) for matches, props in groups)))
if __name__ == '__main__':
args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index dbbf9890c8..cd0a90d82f 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -740,21 +740,19 @@
<term><option>--bind=</option></term>
<term><option>--bind-ro=</option></term>
- <listitem><para>Bind mount a file or directory from the host
- into the container. Takes one of: a path argument — in which
- case the specified path will be mounted from the host to the
- same path in the container —, or a colon-separated pair of
- paths — in which case the first specified path is the source
- in the host, and the second path is the destination in the
- container —, or a colon-separated triple of source path,
- destination path and mount options. Mount options are
- comma-separated and currently, only "rbind" and "norbind"
- are allowed. Defaults to "rbind". Backslash escapes are interpreted, so
- <literal>\:</literal> may be used to embed colons in either path.
- This option may be specified multiple times for
- creating multiple independent bind mount points. The
- <option>--bind-ro=</option> option creates read-only bind
- mounts.</para></listitem>
+ <listitem><para>Bind mount a file or directory from the host into the container. Takes one of: a path
+ argument — in which case the specified path will be mounted from the host to the same path in the container, or
+ a colon-separated pair of paths — in which case the first specified path is the source in the host, and the
+ second path is the destination in the container, or a colon-separated triple of source path, destination path
+ and mount options. The source path may optionally be prefixed with a <literal>+</literal> character. If so, the
+ source path is taken relative to the image's root directory. This permits setting up bind mounts within the
+ container image. The source path may be specified as empty string, in which case a temporary directory below
+ the host's <filename>/var/tmp</filename> directory is used. It is automatically removed when the container is
+ shut down. Mount options are comma-separated and currently, only <option>rbind</option> and
+ <option>norbind</option> are allowed, controlling whether to create a recursive or a regular bind
+ mount. Defaults to "rbind". Backslash escapes are interpreted, so <literal>\:</literal> may be used to embed
+ colons in either path. This option may be specified multiple times for creating multiple independent bind
+ mount points. The <option>--bind-ro=</option> option creates read-only bind mounts.</para></listitem>
</varlistentry>
<varlistentry>
@@ -808,6 +806,14 @@
point for the overlay file system in the container. At least
two paths have to be specified.</para>
+ <para>The source paths may optionally be prefixed with <literal>+</literal> character. If so they are taken
+ relative to the image's root directory. The uppermost source path may also be specified as empty string, in
+ which case a temporary directory below the host's <filename>/var/tmp</filename> is used. The directory is
+ removed automatically when the container is shut down. This behaviour is useful in order to make read-only
+ container directories writable while the container is running. For example, use the
+ <literal>--overlay=+/var::/var</literal> option in order to automatically overlay a writable temporary
+ directory on a read-only <filename>/var</filename> directory.</para>
+
<para>For details about overlay file systems, see <ulink
url="https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt">overlayfs.txt</ulink>. Note
that the semantics of overlay file systems are substantially
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index abc275aad0..ab83876eba 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -1772,9 +1772,9 @@
<listitem><para>Only defined for the service unit type, this environment variable is passed to all
<varname>ExecStop=</varname> and <varname>ExecStopPost=</varname> processes, and encodes the service
"result". Currently, the following values are defined: <literal>protocol</literal> (in case of a protocol
- violation; if a service did not take the steps required by its configuration), <literal>timeout</literal> (in
- case of an operation timeout), <literal>exit-code</literal> (if a service process exited with a non-zero exit
- code; see <varname>$EXIT_CODE</varname> below for the actual exit code returned), <literal>signal</literal>
+ violation; if a service did not take the steps required by its unit configuration), <literal>timeout</literal>
+ (in case of an operation timeout), <literal>exit-code</literal> (if a service process exited with a non-zero
+ exit code; see <varname>$EXIT_CODE</varname> below for the actual exit code returned), <literal>signal</literal>
(if a service process was terminated abnormally by a signal; see <varname>$EXIT_CODE</varname> below for the
actual signal used for the termination), <literal>core-dump</literal> (if a service process terminated
abnormally and dumped core), <literal>watchdog</literal> (if the watchdog keep-alive ping was enabled for the
diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index ffb66e735b..a549ec83bd 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -512,7 +512,9 @@
<varlistentry>
<term><varname>ARPProxy=</varname></term>
<listitem>
- <para>A boolean. When true, enables ARP proxying.</para>
+ <para>A boolean. When true bridge-connected VXLAN tunnel endpoint
+ answers ARP requests from the local bridge on behalf of
+ remote Distributed Overlay Virtual Ethernet (DOVE) clients.</para>
</listitem>
</varlistentry>
<varlistentry>
diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml
index b1344d6c10..7143188356 100644
--- a/man/systemd.nspawn.xml
+++ b/man/systemd.nspawn.xml
@@ -335,6 +335,17 @@
</varlistentry>
<varlistentry>
+ <term><varname>Overlay=</varname></term>
+ <term><varname>OverlayReadOnly=</varname></term>
+
+ <listitem><para>Adds an overlay mount point. Takes a colon-separated list of paths. This option may be used
+ multiple times to configure multiple overlay mounts. This option is equivalent to the command line switches
+ <option>--overlay=</option> and <option>--overlay-ro=</option>, see
+ <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> for details
+ about the specific options supported. This setting is privileged (see above).</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>PrivateUsersChown=</varname></term>
<listitem><para>Configures whether the ownership of the files and directories in the container tree shall be
diff --git a/man/systemd.offline-updates.xml b/man/systemd.offline-updates.xml
index 07a5225512..d673cf5db8 100644
--- a/man/systemd.offline-updates.xml
+++ b/man/systemd.offline-updates.xml
@@ -86,34 +86,44 @@
</listitem>
<listitem>
- <para>The system now continues to boot into <filename>default.target</filename>, and thus
- into <filename>system-update.target</filename>. This target pulls in the system update unit,
- which starts the system update script after all file systems have been mounted.</para>
+ <para>The system now continues to boot into <filename>default.target</filename>, and
+ thus into <filename>system-update.target</filename>. This target pulls in all system
+ update units. Only one service should perform an update (see the next point), and all
+ the other ones should exit cleanly with a "success" return code and without doing
+ anything. Update services should be ordered after <filename>sysinit.target</filename>
+ so that the update starts after after all file systems have been mounted.</para>
</listitem>
<listitem>
- <para>As the first step, the update script should check if the
+ <para>As the first step, an update service should check if the
<filename>/system-update</filename> symlink points to the location used by that update
- script. In case it does not exists or points to a different location, the script must exit
+ service. In case it does not exist or points to a different location, the service must exit
without error. It is possible for multiple update services to be installed, and for multiple
- update scripts to be launched in parallel, and only the one that corresponds to the tool
+ update services to be launched in parallel, and only the one that corresponds to the tool
that <emphasis>created</emphasis> the symlink before reboot should perform any actions. It
is unsafe to run multiple updates in parallel.</para>
</listitem>
<listitem>
- <para>The update script should now do its job. If applicable and possible, it should
- create a file system snapshot, then install all packages.
- After completion (regardless whether the update succeeded or failed) the machine
- must be rebooted, for example by calling <command>systemctl reboot</command>.
- In addition, on failure the script should revert to the old file system snapshot
- (without the symlink).</para>
+ <para>The update service should now do its job. If applicable and possible, it should
+ create a file system snapshot, then install all packages. After completion (regardless
+ whether the update succeeded or failed) the machine must be rebooted, for example by
+ calling <command>systemctl reboot</command>. In addition, on failure the script should
+ revert to the old file system snapshot (without the symlink).</para>
</listitem>
<listitem>
- <para>The system is rebooted. Since the <filename>/system-update</filename> symlink is gone,
- the generator won't redirect <filename>default.target</filename> after reboot and the
- system now boots into the default target again.</para>
+ <para>The upgrade scripts should exit only after the update is finished. It is expected
+ that the service which performs the upgrade will cause the machine to reboot after it
+ is done. If the <filename>system-update.target</filename> is successfully reached, i.e.
+ all update services have run, and the <filename>/system-update</filename> symlink still
+ exists, it will be removed and the machine rebooted as a safety measure.</para>
+ </listitem>
+
+ <listitem>
+ <para>After a reboot, now that the <filename>/system-update</filename> symlink is gone,
+ the generator won't redirect <filename>default.target</filename> anymore and the system
+ now boots into the default target again.</para>
</listitem>
</orderedlist>
</refsect1>
@@ -150,7 +160,8 @@
<listitem>
<para>The update service should declare <varname>DefaultDependencies=false</varname>,
- and pull in any services it requires explicitly.</para>
+ <varname>Requires=sysinit.target</varname>, <varname>After=sysinit.target</varname>,
+ and explicitly pull in any other services it requires.</para>
</listitem>
</orderedlist>
</refsect1>
diff --git a/man/systemd.service.xml b/man/systemd.service.xml
index 3ba6ab34db..67c68d2f8b 100644
--- a/man/systemd.service.xml
+++ b/man/systemd.service.xml
@@ -798,11 +798,14 @@
notification socket, as accessible via the
<citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>
call. Takes one of <option>none</option> (the default),
- <option>main</option> or <option>all</option>. If
- <option>none</option>, no daemon status updates are accepted
- from the service processes, all status update messages are
- ignored. If <option>main</option>, only service updates sent
- from the main process of the service are accepted. If
+ <option>main</option>, <option>exec</option> or
+ <option>all</option>. If <option>none</option>, no daemon status
+ updates are accepted from the service processes, all status
+ update messages are ignored. If <option>main</option>, only
+ service updates sent from the main process of the service are
+ accepted. If <option>exec</option>, only service updates sent
+ from any of the control processes originating from one of the
+ <varname>Exec*=</varname> commands are accepted. If
<option>all</option>, all services updates from all members of
the service's control group are accepted. This option should
be set to open access to the notification socket when using
diff --git a/man/systemd.special.xml b/man/systemd.special.xml
index d977298cd8..b513a13b5a 100644
--- a/man/systemd.special.xml
+++ b/man/systemd.special.xml
@@ -102,6 +102,7 @@
<filename>sysinit.target</filename>,
<filename>syslog.socket</filename>,
<filename>system-update.target</filename>,
+ <filename>system-update-cleanup.service</filename>,
<filename>time-sync.target</filename>,
<filename>timers.target</filename>,
<filename>umount.target</filename>,
@@ -608,15 +609,21 @@
</varlistentry>
<varlistentry>
<term><filename>system-update.target</filename></term>
+ <term><filename>system-update-cleanup.service</filename></term>
<listitem>
- <para>A special target unit that is used for off-line system
- updates.
+ <para>A special target unit that is used for offline system updates.
<citerefentry><refentrytitle>systemd-system-update-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- will redirect the boot process to this target if
- <filename>/system-update</filename> exists. For more
- information see the <ulink
- url="http://freedesktop.org/wiki/Software/systemd/SystemUpdates">System
- Updates Specification</ulink>.</para>
+ will redirect the boot process to this target if <filename>/system-update</filename>
+ exists. For more information see
+ <citerefentry><refentrytitle>systemd.offline-updates</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+ </para>
+
+ <para>Updates should happen before the <filename>system-update.target</filename> is
+ reached, and the services which implement them should cause the machine to reboot. As
+ a safety measure, if this does not happen, and <filename>/system-update</filename>
+ still exists after <filename>system-update.target</filename> is reached,
+ <filename>system-update-cleanup.service</filename> will remove this symlink and
+ reboot the machine.</para>
</listitem>
</varlistentry>
<varlistentry>
diff --git a/po/cs.po b/po/cs.po
index d5f1dcafeb..b42083beec 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: systemd master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-04-23 14:24+0200\n"
-"PO-Revision-Date: 2016-09-22 16:00+0200\n"
+"PO-Revision-Date: 2016-11-30 16:00+0100\n"
"Last-Translator: Daniel Rusek <mail@asciiwolf.com>\n"
"Language: cs\n"
"MIME-Version: 1.0\n"
@@ -551,32 +551,32 @@ msgid ""
"shall be enabled."
msgstr "Autentizace je vyžadována pro kontrolu synchronizace času ze sítě."
-#: ../src/core/dbus-unit.c:450
+#: ../src/core/dbus-unit.c:459
msgid "Authentication is required to start '$(unit)'."
msgstr "Autentizace je vyžadována pro spuštění „$(unit)”."
-#: ../src/core/dbus-unit.c:451
+#: ../src/core/dbus-unit.c:460
msgid "Authentication is required to stop '$(unit)'."
msgstr "Autentizace je vyžadována pro vypnutí „$(unit)”."
-#: ../src/core/dbus-unit.c:452
+#: ../src/core/dbus-unit.c:461
msgid "Authentication is required to reload '$(unit)'."
msgstr "Autentizace je vyžadována pro znovu načtení „$(unit)”."
-#: ../src/core/dbus-unit.c:453 ../src/core/dbus-unit.c:454
+#: ../src/core/dbus-unit.c:462 ../src/core/dbus-unit.c:463
msgid "Authentication is required to restart '$(unit)'."
msgstr "Autentizace je vyžadována pro restart „$(unit)”."
-#: ../src/core/dbus-unit.c:560
+#: ../src/core/dbus-unit.c:570
msgid "Authentication is required to kill '$(unit)'."
msgstr "Autentizace je vyžadována pro ukončení „$(unit)”."
-#: ../src/core/dbus-unit.c:590
+#: ../src/core/dbus-unit.c:601
msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
msgstr ""
"Autentizace je vyžadována pro resetování chybného stavu "
"„$(unit)”."
-#: ../src/core/dbus-unit.c:622
+#: ../src/core/dbus-unit.c:634
msgid "Authentication is required to set properties on '$(unit)'."
msgstr "Autentizace je vyžadována pro nastavení vlastností na „$(unit)”."
diff --git a/rules/60-persistent-storage.rules b/rules/60-persistent-storage.rules
index c13d05cdb1..f7543b7fa8 100644
--- a/rules/60-persistent-storage.rules
+++ b/rules/60-persistent-storage.rules
@@ -7,7 +7,7 @@ ACTION=="remove", GOTO="persistent_storage_end"
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_end"
SUBSYSTEM!="block", GOTO="persistent_storage_end"
-KERNEL!="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|sr*|vd*|xvd*|bcache*|cciss*|dasd*|ubd*|scm*|pmem*", GOTO="persistent_storage_end"
+KERNEL!="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|sr*|vd*|xvd*|bcache*|cciss*|dasd*|ubd*|scm*|pmem*|nbd*", GOTO="persistent_storage_end"
# ignore partitions that span the entire disk
TEST=="whole_disk", GOTO="persistent_storage_end"
diff --git a/src/activate/activate.c b/src/activate/activate.c
index a0cfc22000..6ebd820410 100644
--- a/src/activate/activate.c
+++ b/src/activate/activate.c
@@ -339,7 +339,7 @@ static void sigchld_hdl(int sig) {
static int install_chld_handler(void) {
static const struct sigaction act = {
- .sa_flags = SA_NOCLDSTOP,
+ .sa_flags = SA_NOCLDSTOP|SA_RESTART,
.sa_handler = sigchld_hdl,
};
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index d2c322a0de..b30cec4f92 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -224,25 +224,25 @@ int readlink_and_make_absolute(const char *p, char **r) {
return 0;
}
-int readlink_and_canonicalize(const char *p, char **r) {
+int readlink_and_canonicalize(const char *p, const char *root, char **ret) {
char *t, *s;
- int j;
+ int r;
assert(p);
- assert(r);
+ assert(ret);
- j = readlink_and_make_absolute(p, &t);
- if (j < 0)
- return j;
+ r = readlink_and_make_absolute(p, &t);
+ if (r < 0)
+ return r;
- s = canonicalize_file_name(t);
- if (s) {
+ r = chase_symlinks(t, root, 0, &s);
+ if (r < 0)
+ /* If we can't follow up, then let's return the original string, slightly cleaned up. */
+ *ret = path_kill_slashes(t);
+ else {
+ *ret = s;
free(t);
- *r = s;
- } else
- *r = t;
-
- path_kill_slashes(*r);
+ }
return 0;
}
@@ -598,10 +598,11 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
return r;
}
-int chase_symlinks(const char *path, const char *_root, char **ret) {
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
_cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
_cleanup_close_ int fd = -1;
unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
+ bool exists = true;
char *todo;
int r;
@@ -610,26 +611,39 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
/* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
* symlinks relative to a root directory, instead of the root of the host.
*
- * Note that "root" matters only if we encounter an absolute symlink, it's unused otherwise. Most importantly
- * this means the path parameter passed in is not prefixed by it.
+ * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
+ * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
+ * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
+ * prefixed accordingly.
*
* Algorithmically this operates on two path buffers: "done" are the components of the path we already
* processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
* process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
* each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
* slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
- * at a minimum. */
-
- r = path_make_absolute_cwd(path, &buffer);
- if (r < 0)
- return r;
+ * at a minimum.
+ *
+ * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
+ * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
+ * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
+ * specified path.
+ *
+ * Note: there's also chase_symlinks_prefix() (see below), which as first step prefixes the passed path by the
+ * passed root. */
- if (_root) {
- r = path_make_absolute_cwd(_root, &root);
+ if (original_root) {
+ r = path_make_absolute_cwd(original_root, &root);
if (r < 0)
return r;
+
+ if (flags & CHASE_PREFIX_ROOT)
+ path = prefix_roota(root, path);
}
+ r = path_make_absolute_cwd(path, &buffer);
+ if (r < 0)
+ return r;
+
fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
if (fd < 0)
return -errno;
@@ -665,18 +679,20 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
_cleanup_free_ char *parent = NULL;
int fd_parent = -1;
+ /* If we already are at the top, then going up will not change anything. This is in-line with
+ * how the kernel handles this. */
if (isempty(done) || path_equal(done, "/"))
- return -EINVAL;
+ continue;
parent = dirname_malloc(done);
if (!parent)
return -ENOMEM;
- /* Don't allow this to leave the root dir */
+ /* Don't allow this to leave the root dir. */
if (root &&
path_startswith(done, root) &&
!path_startswith(parent, root))
- return -EINVAL;
+ continue;
free_and_replace(done, parent);
@@ -692,8 +708,25 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
/* Otherwise let's see what this is. */
child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
- if (child < 0)
+ if (child < 0) {
+
+ if (errno == ENOENT &&
+ (flags & CHASE_NONEXISTENT) &&
+ (isempty(todo) || path_is_safe(todo))) {
+
+ /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
+ * what we got so far. But don't allow this if the remaining path contains "../ or "./"
+ * or something else weird. */
+
+ if (!strextend(&done, first, todo, NULL))
+ return -ENOMEM;
+
+ exists = false;
+ break;
+ }
+
return -errno;
+ }
if (fstat(child, &st) < 0)
return -errno;
@@ -778,5 +811,5 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
*ret = done;
done = NULL;
- return 0;
+ return exists;
}
diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h
index 31df47cf1e..0d925c6b84 100644
--- a/src/basic/fs-util.h
+++ b/src/basic/fs-util.h
@@ -39,7 +39,7 @@ int readlinkat_malloc(int fd, const char *p, char **ret);
int readlink_malloc(const char *p, char **r);
int readlink_value(const char *p, char **ret);
int readlink_and_make_absolute(const char *p, char **r);
-int readlink_and_canonicalize(const char *p, char **r);
+int readlink_and_canonicalize(const char *p, const char *root, char **r);
int readlink_and_make_absolute_root(const char *root, const char *path, char **ret);
int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
@@ -78,4 +78,9 @@ union inotify_event_buffer {
int inotify_add_watch_fd(int fd, int what, uint32_t mask);
-int chase_symlinks(const char *path, const char *_root, char **ret);
+enum {
+ CHASE_PREFIX_ROOT = 1, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
+ CHASE_NONEXISTENT = 2, /* If set, it's OK if the path doesn't actually exist. */
+};
+
+int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c
index 5d37fb48be..352c3505fb 100644
--- a/src/basic/mount-util.c
+++ b/src/basic/mount-util.c
@@ -29,6 +29,7 @@
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
+#include "fs-util.h"
#include "hashmap.h"
#include "mount-util.h"
#include "parse-util.h"
@@ -205,9 +206,10 @@ fallback_fstat:
}
/* flags can be AT_SYMLINK_FOLLOW or 0 */
-int path_is_mount_point(const char *t, int flags) {
- _cleanup_close_ int fd = -1;
+int path_is_mount_point(const char *t, const char *root, int flags) {
_cleanup_free_ char *canonical = NULL, *parent = NULL;
+ _cleanup_close_ int fd = -1;
+ int r;
assert(t);
@@ -219,9 +221,9 @@ int path_is_mount_point(const char *t, int flags) {
* /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
* look at needs to be /usr, not /. */
if (flags & AT_SYMLINK_FOLLOW) {
- canonical = canonicalize_file_name(t);
- if (!canonical)
- return -errno;
+ r = chase_symlinks(t, root, 0, &canonical);
+ if (r < 0)
+ return r;
t = canonical;
}
@@ -473,7 +475,7 @@ int bind_remount_recursive(const char *prefix, bool ro, char **blacklist) {
return r;
/* Deal with mount points that are obstructed by a later mount */
- r = path_is_mount_point(x, 0);
+ r = path_is_mount_point(x, NULL, 0);
if (r == -ENOENT || r == 0)
continue;
if (r < 0)
diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h
index 4f305df19f..b840956d63 100644
--- a/src/basic/mount-util.h
+++ b/src/basic/mount-util.h
@@ -30,7 +30,7 @@
#include "missing.h"
int fd_is_mount_point(int fd, const char *filename, int flags);
-int path_is_mount_point(const char *path, int flags);
+int path_is_mount_point(const char *path, const char *root, int flags);
int repeat_unmount(const char *path, int flags);
diff --git a/src/basic/path-util.c b/src/basic/path-util.c
index 5cdac50c68..9a51e0d8bc 100644
--- a/src/basic/path-util.c
+++ b/src/basic/path-util.c
@@ -220,10 +220,11 @@ int path_strv_make_absolute_cwd(char **l) {
return 0;
}
-char **path_strv_resolve(char **l, const char *prefix) {
+char **path_strv_resolve(char **l, const char *root) {
char **s;
unsigned k = 0;
bool enomem = false;
+ int r;
if (strv_isempty(l))
return l;
@@ -233,17 +234,17 @@ char **path_strv_resolve(char **l, const char *prefix) {
* changes on failure. */
STRV_FOREACH(s, l) {
- char *t, *u;
_cleanup_free_ char *orig = NULL;
+ char *t, *u;
if (!path_is_absolute(*s)) {
free(*s);
continue;
}
- if (prefix) {
+ if (root) {
orig = *s;
- t = strappend(prefix, orig);
+ t = prefix_root(root, orig);
if (!t) {
enomem = true;
continue;
@@ -251,28 +252,26 @@ char **path_strv_resolve(char **l, const char *prefix) {
} else
t = *s;
- errno = 0;
- u = canonicalize_file_name(t);
- if (!u) {
- if (errno == ENOENT) {
- if (prefix) {
- u = orig;
- orig = NULL;
- free(t);
- } else
- u = t;
- } else {
+ r = chase_symlinks(t, root, 0, &u);
+ if (r == -ENOENT) {
+ if (root) {
+ u = orig;
+ orig = NULL;
free(t);
- if (errno == ENOMEM || errno == 0)
- enomem = true;
+ } else
+ u = t;
+ } else if (r < 0) {
+ free(t);
- continue;
- }
- } else if (prefix) {
+ if (r == -ENOMEM)
+ enomem = true;
+
+ continue;
+ } else if (root) {
char *x;
free(t);
- x = path_startswith(u, prefix);
+ x = path_startswith(u, root);
if (x) {
/* restore the slash if it was lost */
if (!startswith(x, "/"))
@@ -304,12 +303,12 @@ char **path_strv_resolve(char **l, const char *prefix) {
return l;
}
-char **path_strv_resolve_uniq(char **l, const char *prefix) {
+char **path_strv_resolve_uniq(char **l, const char *root) {
if (strv_isempty(l))
return l;
- if (!path_strv_resolve(l, prefix))
+ if (!path_strv_resolve(l, root))
return NULL;
return strv_uniq(l);
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index 66545f52d9..d2bc0d3b8e 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -66,8 +66,8 @@ static inline bool path_equal_ptr(const char *a, const char *b) {
})
int path_strv_make_absolute_cwd(char **l);
-char** path_strv_resolve(char **l, const char *prefix);
-char** path_strv_resolve_uniq(char **l, const char *prefix);
+char** path_strv_resolve(char **l, const char *root);
+char** path_strv_resolve_uniq(char **l, const char *root);
int find_binary(const char *name, char **filename);
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
index 30c1ead1aa..44ea6215dc 100644
--- a/src/boot/efi/boot.c
+++ b/src/boot/efi/boot.c
@@ -1482,7 +1482,7 @@ static VOID config_entry_add_osx(Config *config) {
root = LibOpenRoot(handles[i]);
if (!root)
continue;
- found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X",
+ found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"macOS",
L"\\System\\Library\\CoreServices\\boot.efi");
uefi_call_wrapper(root->Close, 1, root);
if (found)
diff --git a/src/core/automount.c b/src/core/automount.c
index 5fa6eb7b18..8ff1ca90f7 100644
--- a/src/core/automount.c
+++ b/src/core/automount.c
@@ -783,7 +783,7 @@ static int automount_start(Unit *u) {
assert(a);
assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED);
- if (path_is_mount_point(a->where, 0) > 0) {
+ if (path_is_mount_point(a->where, NULL, 0) > 0) {
log_unit_error(u, "Path %s is already a mount point, refusing start.", a->where);
return -EEXIST;
}
diff --git a/src/core/cgroup.c b/src/core/cgroup.c
index bd6248406f..6dab6e9043 100644
--- a/src/core/cgroup.c
+++ b/src/core/cgroup.c
@@ -293,8 +293,11 @@ static int whitelist_device(const char *path, const char *node, const char *acc)
assert(acc);
if (stat(node, &st) < 0) {
- log_warning("Couldn't stat device %s", node);
- return -errno;
+ /* path starting with "-" must be silently ignored */
+ if (errno == ENOENT && startswith(node, "-"))
+ return 0;
+
+ return log_warning_errno(errno, "Couldn't stat device %s: %m", node);
}
if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) {
@@ -914,8 +917,8 @@ static void cgroup_context_apply(Unit *u, CGroupMask mask, ManagerState state) {
"/dev/tty\0" "rwm\0"
"/dev/pts/ptmx\0" "rw\0" /* /dev/pts/ptmx may not be duplicated, but accessed */
/* Allow /run/systemd/inaccessible/{chr,blk} devices for mapping InaccessiblePaths */
- "/run/systemd/inaccessible/chr\0" "rwm\0"
- "/run/systemd/inaccessible/blk\0" "rwm\0";
+ "-/run/systemd/inaccessible/chr\0" "rwm\0"
+ "-/run/systemd/inaccessible/blk\0" "rwm\0";
const char *x, *y;
diff --git a/src/core/device.c b/src/core/device.c
index 8e2e3c7bed..074e93ffe2 100644
--- a/src/core/device.c
+++ b/src/core/device.c
@@ -359,7 +359,7 @@ static int device_setup_unit(Manager *m, struct udev_device *dev, const char *pa
fail:
log_unit_warning_errno(u, r, "Failed to set up device unit: %m");
- if (delete && u)
+ if (delete)
unit_free(u);
return r;
diff --git a/src/core/ima-setup.c b/src/core/ima-setup.c
index d1b0ce76ef..94ae429f46 100644
--- a/src/core/ima-setup.c
+++ b/src/core/ima-setup.c
@@ -44,6 +44,22 @@ int ima_setup(void) {
return 0;
}
+ if (access(IMA_SECFS_POLICY, W_OK) < 0) {
+ log_warning("Another IMA custom policy has already been loaded, ignoring.");
+ return 0;
+ }
+
+ imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC);
+ if (imafd < 0) {
+ log_error_errno(errno, "Failed to open the IMA kernel interface "IMA_SECFS_POLICY", ignoring: %m");
+ return 0;
+ }
+
+ /* attempt to write the name of the policy file into sysfs file */
+ if (write(imafd, IMA_POLICY_PATH, strlen(IMA_POLICY_PATH)) > 0)
+ goto done;
+
+ /* fall back to copying the policy line-by-line */
input = fopen(IMA_POLICY_PATH, "re");
if (!input) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
@@ -51,10 +67,7 @@ int ima_setup(void) {
return 0;
}
- if (access(IMA_SECFS_POLICY, F_OK) < 0) {
- log_warning("Another IMA custom policy has already been loaded, ignoring.");
- return 0;
- }
+ close(imafd);
imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC);
if (imafd < 0) {
@@ -74,6 +87,7 @@ int ima_setup(void) {
lineno);
}
+done:
log_info("Successfully loaded the IMA custom policy "IMA_POLICY_PATH".");
#endif /* HAVE_IMA */
return 0;
diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c
index 76dfcfa6d7..c83bb561c7 100644
--- a/src/core/machine-id-setup.c
+++ b/src/core/machine-id-setup.c
@@ -199,7 +199,7 @@ int machine_id_commit(const char *root) {
etc_machine_id = prefix_roota(root, "/etc/machine-id");
- r = path_is_mount_point(etc_machine_id, 0);
+ r = path_is_mount_point(etc_machine_id, NULL, 0);
if (r < 0)
return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id);
if (r == 0) {
diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
index ca63a93e8b..6338067d7e 100644
--- a/src/core/mount-setup.c
+++ b/src/core/mount-setup.c
@@ -159,7 +159,7 @@ static int mount_one(const MountPoint *p, bool relabel) {
if (relabel)
(void) label_fix(p->where, true, true);
- r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(p->where, NULL, AT_SYMLINK_FOLLOW);
if (r < 0 && r != -ENOENT) {
log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, r, "Failed to determine whether %s is a mount point: %m", p->where);
return (p->mode & MNT_FATAL) ? r : 0;
diff --git a/src/core/mount.c b/src/core/mount.c
index 1c2be28d55..0c4d061c27 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -1509,7 +1509,7 @@ static int mount_setup_unit(
fail:
log_warning_errno(r, "Failed to set up mount unit: %m");
- if (delete && u)
+ if (delete)
unit_free(u);
return r;
diff --git a/src/core/namespace.c b/src/core/namespace.c
index e9ad26bfc3..aca47a4d2f 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -596,7 +596,7 @@ static int apply_mount(
case READONLY:
case READWRITE:
- r = path_is_mount_point(bind_mount_path(m), 0);
+ r = path_is_mount_point(bind_mount_path(m), NULL, 0);
if (r < 0)
return log_debug_errno(r, "Failed to determine whether %s is already a mount point: %m", bind_mount_path(m));
if (r > 0) /* Nothing to do here, it is already a mount. We just later toggle the MS_RDONLY bit for the mount point if needed. */
@@ -665,7 +665,7 @@ static int chase_all_symlinks(const char *root_directory, BindMount *m, unsigned
_cleanup_free_ char *chased = NULL;
int k;
- k = chase_symlinks(bind_mount_path(f), root_directory, &chased);
+ k = chase_symlinks(bind_mount_path(f), root_directory, 0, &chased);
if (k < 0) {
/* Get only real errors */
if (r >= 0 && (k != -ENOENT || !f->ignore))
@@ -860,7 +860,7 @@ int setup_namespace(
if (root_directory) {
/* Turn directory into bind mount, if it isn't one yet */
- r = path_is_mount_point(root_directory, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(root_directory, NULL, AT_SYMLINK_FOLLOW);
if (r < 0)
goto finish;
if (r == 0) {
diff --git a/src/core/service.c b/src/core/service.c
index 180854b57c..c68a7122b6 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1179,6 +1179,25 @@ static int service_collect_fds(Service *s, int **fds, char ***fd_names) {
return rn_fds;
}
+static bool service_exec_needs_notify_socket(Service *s, ExecFlags flags) {
+ assert(s);
+
+ /* Notifications are accepted depending on the process and
+ * the access setting of the service:
+ * process: \ access: NONE MAIN EXEC ALL
+ * main no yes yes yes
+ * control no no yes yes
+ * other (forked) no no no yes */
+
+ if (flags & EXEC_IS_CONTROL)
+ /* A control process */
+ return IN_SET(s->notify_access, NOTIFY_EXEC, NOTIFY_ALL);
+
+ /* We only spawn main processes and control processes, so any
+ * process that is not a control process is a main process */
+ return s->notify_access != NOTIFY_NONE;
+}
+
static int service_spawn(
Service *s,
ExecCommand *c,
@@ -1252,7 +1271,7 @@ static int service_spawn(
if (!our_env)
return -ENOMEM;
- if ((flags & EXEC_IS_CONTROL) ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE)
+ if (service_exec_needs_notify_socket(s, flags))
if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0)
return -ENOMEM;
@@ -2579,11 +2598,16 @@ static void service_notify_cgroup_empty_event(Unit *u) {
* SIGCHLD for. */
case SERVICE_START:
- case SERVICE_START_POST:
- if (s->type == SERVICE_NOTIFY)
+ if (s->type == SERVICE_NOTIFY) {
/* No chance of getting a ready notification anymore */
service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL);
- else if (s->pid_file_pathspec) {
+ break;
+ }
+
+ /* Fall through */
+
+ case SERVICE_START_POST:
+ if (s->pid_file_pathspec) {
/* Give up hoping for the daemon to write its PID file */
log_unit_warning(u, "Daemon never wrote its PID file. Failing.");
@@ -3056,7 +3080,18 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
if (s->main_pid != 0)
log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
else
- log_unit_debug(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid);
+ log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid);
+ return;
+ } else if (s->notify_access == NOTIFY_EXEC && pid != s->main_pid && pid != s->control_pid) {
+ if (s->main_pid != 0 && s->control_pid != 0)
+ log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT" and control PID "PID_FMT,
+ pid, s->main_pid, s->control_pid);
+ else if (s->main_pid != 0)
+ log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid);
+ else if (s->control_pid != 0)
+ log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid);
+ else
+ log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid);
return;
} else
log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc);
@@ -3066,6 +3101,8 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds)
if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) {
if (parse_pid(e, &pid) < 0)
log_unit_warning(u, "Failed to parse MAINPID= field in notification message: %s", e);
+ else if (pid == s->control_pid)
+ log_unit_warning(u, "A control process cannot also be the main process");
else {
service_set_main_pid(s, pid);
unit_watch_pid(UNIT(s), pid);
@@ -3381,6 +3418,7 @@ DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand);
static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = {
[NOTIFY_NONE] = "none",
[NOTIFY_MAIN] = "main",
+ [NOTIFY_EXEC] = "exec",
[NOTIFY_ALL] = "all"
};
diff --git a/src/core/service.h b/src/core/service.h
index 278cc1ceb8..e09722a952 100644
--- a/src/core/service.h
+++ b/src/core/service.h
@@ -65,6 +65,7 @@ typedef enum NotifyAccess {
NOTIFY_NONE,
NOTIFY_ALL,
NOTIFY_MAIN,
+ NOTIFY_EXEC,
_NOTIFY_ACCESS_MAX,
_NOTIFY_ACCESS_INVALID = -1
} NotifyAccess;
diff --git a/src/core/swap.c b/src/core/swap.c
index bf404db8c3..e9468e105c 100644
--- a/src/core/swap.c
+++ b/src/core/swap.c
@@ -420,7 +420,7 @@ static int swap_setup_unit(
fail:
log_unit_warning_errno(u, r, "Failed to load swap unit: %m");
- if (delete && u)
+ if (delete)
unit_free(u);
return r;
diff --git a/src/core/unit.c b/src/core/unit.c
index cba6342eca..e485c01fc1 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -516,7 +516,8 @@ void unit_free(Unit *u) {
Iterator i;
char *t;
- assert(u);
+ if (!u)
+ return;
if (u->transient_file)
fclose(u->transient_file);
diff --git a/src/delta/delta.c b/src/delta/delta.c
index 04de75475d..107b105fde 100644
--- a/src/delta/delta.c
+++ b/src/delta/delta.c
@@ -87,14 +87,15 @@ static enum {
static int equivalent(const char *a, const char *b) {
_cleanup_free_ char *x = NULL, *y = NULL;
+ int r;
- x = canonicalize_file_name(a);
- if (!x)
- return -errno;
+ r = chase_symlinks(a, NULL, 0, &x);
+ if (r < 0)
+ return r;
- y = canonicalize_file_name(b);
- if (!y)
- return -errno;
+ r = chase_symlinks(b, NULL, 0, &y);
+ if (r < 0)
+ return r;
return path_equal(x, y);
}
@@ -360,7 +361,7 @@ static int should_skip_prefix(const char* p) {
int r;
_cleanup_free_ char *target = NULL;
- r = chase_symlinks(p, NULL, &target);
+ r = chase_symlinks(p, NULL, 0, &target);
if (r < 0)
return r;
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index 52cde493e5..0f95f0d813 100644
--- a/src/gpt-auto-generator/gpt-auto-generator.c
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
@@ -252,7 +252,7 @@ static bool path_is_busy(const char *where) {
int r;
/* already a mountpoint; generators run during reload */
- r = path_is_mount_point(where, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(where, NULL, AT_SYMLINK_FOLLOW);
if (r > 0)
return false;
diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py
index 24874e960c..7b434b334e 100755
--- a/src/journal-remote/log-generator.py
+++ b/src/journal-remote/log-generator.py
@@ -29,7 +29,7 @@ _SOURCE_REALTIME_TIMESTAMP={source_realtime_ts}
DATA={data}
"""
-m = 0x198603b12d7
+m = 0x198603b12d7
realtime_ts = 1404101101501873
monotonic_ts = 1753961140951
source_realtime_ts = 1404101101483516
@@ -71,5 +71,5 @@ for i in range(OPTIONS.n):
print('.', file=sys.stderr, end='', flush=True)
if OPTIONS.dots:
- print(file=sys.stderr)
+ print(file=sys.stderr)
print('Wrote {} bytes'.format(bytes), file=sys.stderr)
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
index 22cab67824..10d3ff3b45 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -938,21 +938,21 @@ static int add_matches(sd_journal *j, char **args) {
have_term = false;
} else if (path_is_absolute(*i)) {
- _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL;
- const char *path;
+ _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL;
struct stat st;
- p = canonicalize_file_name(*i);
- path = p ?: *i;
+ r = chase_symlinks(*i, NULL, 0, &p);
+ if (r < 0)
+ return log_error_errno(r, "Couldn't canonicalize path: %m");
- if (lstat(path, &st) < 0)
+ if (lstat(p, &st) < 0)
return log_error_errno(errno, "Couldn't stat file: %m");
if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
- if (executable_is_script(path, &interpreter) > 0) {
+ if (executable_is_script(p, &interpreter) > 0) {
_cleanup_free_ char *comm;
- comm = strndup(basename(path), 15);
+ comm = strndup(basename(p), 15);
if (!comm)
return log_oom();
@@ -968,7 +968,7 @@ static int add_matches(sd_journal *j, char **args) {
return log_oom();
}
} else {
- t = strappend("_EXE=", path);
+ t = strappend("_EXE=", p);
if (!t)
return log_oom();
}
@@ -979,7 +979,7 @@ static int add_matches(sd_journal *j, char **args) {
r = sd_journal_add_match(j, t2, 0);
} else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
- r = add_matches_for_device(j, path);
+ r = add_matches_for_device(j, p);
if (r < 0)
return r;
} else {
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
index 411453e08d..1081979bf9 100644
--- a/src/libsystemd/sd-device/sd-device.c
+++ b/src/libsystemd/sd-device/sd-device.c
@@ -164,7 +164,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) {
}
if (verify) {
- r = readlink_and_canonicalize(_syspath, &syspath);
+ r = readlink_and_canonicalize(_syspath, NULL, &syspath);
if (r == -ENOENT)
/* the device does not exist (any more?) */
return -ENODEV;
diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c
index 63fb05547d..57ce749e07 100644
--- a/src/libudev/libudev.c
+++ b/src/libudev/libudev.c
@@ -156,7 +156,7 @@ _public_ struct udev *udev_new(void) {
/* unquote */
if (val[0] == '"' || val[0] == '\'') {
- if (val[len-1] != val[0]) {
+ if (len == 1 || val[len-1] != val[0]) {
log_debug("/etc/udev/udev.conf:%u: inconsistent quoting, skipping line.", line_nr);
continue;
}
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index 0d1417ea16..888a97c2fc 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -338,7 +338,7 @@ static int user_mkdir_runtime_path(User *u) {
if (r < 0)
return log_error_errno(r, "Failed to create /run/user: %m");
- if (path_is_mount_point(u->runtime_path, 0) <= 0) {
+ if (path_is_mount_point(u->runtime_path, NULL, 0) <= 0) {
_cleanup_free_ char *t = NULL;
(void) mkdir_label(u->runtime_path, 0700);
diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c
index 08e31b974f..9fdcb55376 100644
--- a/src/network/netdev/bridge.c
+++ b/src/network/netdev/bridge.c
@@ -72,7 +72,7 @@ static int netdev_bridge_post_create(NetDev *netdev, Link *link, sd_netlink_mess
return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
/* convert to jiffes */
- if (b->forward_delay > 0) {
+ if (b->forward_delay != USEC_INFINITY) {
r = sd_netlink_message_append_u32(req, IFLA_BR_FORWARD_DELAY, usec_to_jiffies(b->forward_delay));
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_FORWARD_DELAY attribute: %m");
@@ -160,6 +160,7 @@ static void bridge_init(NetDev *n) {
b->mcast_snooping = -1;
b->vlan_filtering = -1;
b->stp = -1;
+ b->forward_delay = USEC_INFINITY;
}
const NetDevVTable bridge_vtable = {
diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf
index 3231a48d5a..c0fa4bfa1f 100644
--- a/src/nspawn/nspawn-gperf.gperf
+++ b/src/nspawn/nspawn-gperf.gperf
@@ -33,6 +33,8 @@ Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings,
Files.Bind, config_parse_bind, 0, 0
Files.BindReadOnly, config_parse_bind, 1, 0
Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0
+Files.Overlay, config_parse_overlay, 0, 0
+Files.OverlayReadOnly, config_parse_overlay, 1, 0
Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown)
Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network)
Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces)
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c
index 91cb0861d3..c9d5ac419e 100644
--- a/src/nspawn/nspawn-mount.c
+++ b/src/nspawn/nspawn-mount.c
@@ -47,7 +47,7 @@ CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) {
assert(t >= 0);
assert(t < _CUSTOM_MOUNT_TYPE_MAX);
- c = realloc(*l, (*n + 1) * sizeof(CustomMount));
+ c = realloc_multiply(*l, (*n + 1), sizeof(CustomMount));
if (!c)
return NULL;
@@ -75,13 +75,18 @@ void custom_mount_free_all(CustomMount *l, unsigned n) {
free(m->work_dir);
}
+ if (m->rm_rf_tmpdir) {
+ (void) rm_rf(m->rm_rf_tmpdir, REMOVE_ROOT|REMOVE_PHYSICAL);
+ free(m->rm_rf_tmpdir);
+ }
+
strv_free(m->lower);
}
free(l);
}
-int custom_mount_compare(const void *a, const void *b) {
+static int custom_mount_compare(const void *a, const void *b) {
const CustomMount *x = a, *y = b;
int r;
@@ -97,6 +102,109 @@ int custom_mount_compare(const void *a, const void *b) {
return 0;
}
+static bool source_path_is_valid(const char *p) {
+ assert(p);
+
+ if (*p == '+')
+ p++;
+
+ return path_is_absolute(p);
+}
+
+static char *resolve_source_path(const char *dest, const char *source) {
+
+ if (!source)
+ return NULL;
+
+ if (source[0] == '+')
+ return prefix_root(dest, source + 1);
+
+ return strdup(source);
+}
+
+int custom_mount_prepare_all(const char *dest, CustomMount *l, unsigned n) {
+ unsigned i;
+ int r;
+
+ /* Prepare all custom mounts. This will make source we know all temporary directories. This is called in the
+ * parent process, so that we know the temporary directories to remove on exit before we fork off the
+ * children. */
+
+ assert(l || n == 0);
+
+ /* Order the custom mounts, and make sure we have a working directory */
+ qsort_safe(l, n, sizeof(CustomMount), custom_mount_compare);
+
+ for (i = 0; i < n; i++) {
+ CustomMount *m = l + i;
+
+ if (m->source) {
+ char *s;
+
+ s = resolve_source_path(dest, m->source);
+ if (!s)
+ return log_oom();
+
+ free(m->source);
+ m->source = s;
+ } else {
+ /* No source specified? In that case, use a throw-away temporary directory in /var/tmp */
+
+ m->rm_rf_tmpdir = strdup("/var/tmp/nspawn-temp-XXXXXX");
+ if (!m->rm_rf_tmpdir)
+ return log_oom();
+
+ if (!mkdtemp(m->rm_rf_tmpdir)) {
+ m->rm_rf_tmpdir = mfree(m->rm_rf_tmpdir);
+ return log_error_errno(errno, "Failed to acquire temporary directory: %m");
+ }
+
+ m->source = strjoin(m->rm_rf_tmpdir, "/src");
+ if (!m->source)
+ return log_oom();
+
+ if (mkdir(m->source, 0755) < 0)
+ return log_error_errno(errno, "Failed to create %s: %m", m->source);
+ }
+
+ if (m->type == CUSTOM_MOUNT_OVERLAY) {
+ char **j;
+
+ STRV_FOREACH(j, m->lower) {
+ char *s;
+
+ s = resolve_source_path(dest, *j);
+ if (!s)
+ return log_oom();
+
+ free(*j);
+ *j = s;
+ }
+
+ if (m->work_dir) {
+ char *s;
+
+ s = resolve_source_path(dest, m->work_dir);
+ if (!s)
+ return log_oom();
+
+ free(m->work_dir);
+ m->work_dir = s;
+ } else {
+ assert(m->source);
+
+ r = tempfn_random(m->source, NULL, &m->work_dir);
+ if (r < 0)
+ return log_error_errno(r, "Failed to acquire working directory: %m");
+ }
+
+ (void) mkdir_label(m->work_dir, 0700);
+ }
+ }
+
+ return 0;
+}
+
int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) {
_cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL;
const char *p = s;
@@ -111,20 +219,20 @@ int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only
return r;
if (r == 0)
return -EINVAL;
-
if (r == 1) {
- destination = strdup(source);
+ destination = strdup(source[0] == '+' ? source+1 : source);
if (!destination)
return -ENOMEM;
}
-
if (r == 2 && !isempty(p)) {
opts = strdup(p);
if (!opts)
return -ENOMEM;
}
- if (!path_is_absolute(source))
+ if (isempty(source))
+ source = NULL;
+ else if (!source_path_is_valid(source))
return -EINVAL;
if (!path_is_absolute(destination))
@@ -132,7 +240,7 @@ int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only
m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND);
if (!m)
- return log_oom();
+ return -ENOMEM;
m->source = source;
m->destination = destination;
@@ -180,6 +288,71 @@ int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) {
return 0;
}
+int overlay_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) {
+ _cleanup_free_ char *upper = NULL, *destination = NULL;
+ _cleanup_strv_free_ char **lower = NULL;
+ CustomMount *m;
+ int k;
+
+ k = strv_split_extract(&lower, s, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+ if (k < 0)
+ return k;
+ if (k < 2)
+ return -EADDRNOTAVAIL;
+ if (k == 2) {
+ /* If two parameters are specified, the first one is the lower, the second one the upper directory. And
+ * we'll also define the destination mount point the same as the upper. */
+
+ if (!source_path_is_valid(lower[0]) ||
+ !source_path_is_valid(lower[1]))
+ return -EINVAL;
+
+ upper = lower[1];
+ lower[1] = NULL;
+
+ destination = strdup(upper[0] == '+' ? upper+1 : upper); /* take the destination without "+" prefix */
+ if (!destination)
+ return -ENOMEM;
+ } else {
+ char **i;
+
+ /* If more than two parameters are specified, the last one is the destination, the second to last one
+ * the "upper", and all before that the "lower" directories. */
+
+ destination = lower[k - 1];
+ upper = lower[k - 2];
+ lower[k - 2] = NULL;
+
+ STRV_FOREACH(i, lower)
+ if (!source_path_is_valid(*i))
+ return -EINVAL;
+
+ /* If the upper directory is unspecified, then let's create it automatically as a throw-away directory
+ * in /var/tmp */
+ if (isempty(upper))
+ upper = NULL;
+ else if (!source_path_is_valid(upper))
+ return -EINVAL;
+
+ if (!path_is_absolute(destination))
+ return -EINVAL;
+ }
+
+ m = custom_mount_add(l, n, CUSTOM_MOUNT_OVERLAY);
+ if (!m)
+ return -ENOMEM;
+
+ m->destination = destination;
+ m->source = upper;
+ m->lower = lower;
+ m->read_only = read_only;
+
+ upper = destination = NULL;
+ lower = NULL;
+
+ return 0;
+}
+
static int tmpfs_patch_options(
const char *options,
bool userns,
@@ -414,11 +587,11 @@ int mount_all(const char *dest,
if (!ro && (bool)(mount_table[k].mount_settings & MOUNT_APPLY_APIVFS_RO))
continue;
- where = prefix_root(dest, mount_table[k].where);
- if (!where)
- return log_oom();
+ r = chase_symlinks(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].where);
- r = path_is_mount_point(where, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(where, NULL, 0);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where);
@@ -464,12 +637,14 @@ static int parse_mount_bind_options(const char *options, unsigned long *mount_fl
const char *p = options;
unsigned long flags = *mount_flags;
char *opts = NULL;
+ int r;
assert(options);
for (;;) {
_cleanup_free_ char *word = NULL;
- int r = extract_first_word(&p, &word, ",", 0);
+
+ r = extract_first_word(&p, &word, ",", 0);
if (r < 0)
return log_error_errno(r, "Failed to extract mount option: %m");
if (r == 0)
@@ -493,12 +668,13 @@ static int parse_mount_bind_options(const char *options, unsigned long *mount_fl
}
static int mount_bind(const char *dest, CustomMount *m) {
- struct stat source_st, dest_st;
- const char *where;
+
+ _cleanup_free_ char *mount_opts = NULL, *where = NULL;
unsigned long mount_flags = MS_BIND | MS_REC;
- _cleanup_free_ char *mount_opts = NULL;
+ struct stat source_st, dest_st;
int r;
+ assert(dest);
assert(m);
if (m->options) {
@@ -510,9 +686,14 @@ static int mount_bind(const char *dest, CustomMount *m) {
if (stat(m->source, &source_st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", m->source);
- where = prefix_roota(dest, m->destination);
+ r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
+ if (r > 0) { /* Path exists already? */
+
+ if (stat(where, &dest_st) < 0)
+ return log_error_errno(errno, "Failed to stat %s: %m", where);
- if (stat(where, &dest_st) >= 0) {
if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) {
log_error("Cannot bind mount directory %s on file %s.", m->source, where);
return -EINVAL;
@@ -523,7 +704,7 @@ static int mount_bind(const char *dest, CustomMount *m) {
return -EINVAL;
}
- } else if (errno == ENOENT) {
+ } else { /* Path doesn't exist yet? */
r = mkdir_parents_label(where, 0755);
if (r < 0)
return log_error_errno(r, "Failed to make parents of %s: %m", where);
@@ -539,8 +720,7 @@ static int mount_bind(const char *dest, CustomMount *m) {
if (r < 0)
return log_error_errno(r, "Failed to create mount point %s: %m", where);
- } else
- return log_error_errno(errno, "Failed to stat %s: %m", where);
+ }
r = mount_verbose(LOG_ERR, m->source, where, NULL, mount_flags, mount_opts);
if (r < 0)
@@ -561,18 +741,21 @@ static int mount_tmpfs(
bool userns, uid_t uid_shift, uid_t uid_range,
const char *selinux_apifs_context) {
- const char *where, *options;
- _cleanup_free_ char *buf = NULL;
+ const char *options;
+ _cleanup_free_ char *buf = NULL, *where = NULL;
int r;
assert(dest);
assert(m);
- where = prefix_roota(dest, m->destination);
-
- r = mkdir_p_label(where, 0755);
- if (r < 0 && r != -EEXIST)
- return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where);
+ r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
+ if (r == 0) { /* Doesn't exist yet? */
+ r = mkdir_p_label(where, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where);
+ }
r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf);
if (r < 0)
@@ -582,7 +765,7 @@ static int mount_tmpfs(
return mount_verbose(LOG_ERR, "tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options);
}
-static char *joined_and_escaped_lower_dirs(char * const *lower) {
+static char *joined_and_escaped_lower_dirs(char **lower) {
_cleanup_strv_free_ char **sv = NULL;
sv = strv_copy(lower);
@@ -598,18 +781,22 @@ static char *joined_and_escaped_lower_dirs(char * const *lower) {
}
static int mount_overlay(const char *dest, CustomMount *m) {
- _cleanup_free_ char *lower = NULL;
- const char *where, *options;
+
+ _cleanup_free_ char *lower = NULL, *where = NULL, *escaped_source = NULL;
+ const char *options;
int r;
assert(dest);
assert(m);
- where = prefix_roota(dest, m->destination);
-
- r = mkdir_label(where, 0755);
- if (r < 0 && r != -EEXIST)
- return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where);
+ r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination);
+ if (r == 0) { /* Doesn't exist yet? */
+ r = mkdir_label(where, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where);
+ }
(void) mkdir_p_label(m->source, 0755);
@@ -617,23 +804,15 @@ static int mount_overlay(const char *dest, CustomMount *m) {
if (!lower)
return log_oom();
- if (m->read_only) {
- _cleanup_free_ char *escaped_source = NULL;
-
- escaped_source = shell_escape(m->source, ",:");
- if (!escaped_source)
- return log_oom();
+ escaped_source = shell_escape(m->source, ",:");
+ if (!escaped_source)
+ return log_oom();
+ if (m->read_only)
options = strjoina("lowerdir=", escaped_source, ":", lower);
- } else {
- _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL;
+ else {
+ _cleanup_free_ char *escaped_work_dir = NULL;
- assert(m->work_dir);
- (void) mkdir_label(m->work_dir, 0700);
-
- escaped_source = shell_escape(m->source, ",:");
- if (!escaped_source)
- return log_oom();
escaped_work_dir = shell_escape(m->work_dir, ",:");
if (!escaped_work_dir)
return log_oom();
@@ -726,14 +905,19 @@ static int get_controllers(Set *subsystems) {
return 0;
}
-static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy,
- CGroupUnified unified_requested, bool read_only) {
+static int mount_legacy_cgroup_hierarchy(
+ const char *dest,
+ const char *controller,
+ const char *hierarchy,
+ CGroupUnified unified_requested,
+ bool read_only) {
+
const char *to, *fstype, *opts;
int r;
to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy);
- r = path_is_mount_point(to, 0);
+ r = path_is_mount_point(to, dest, 0);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to);
if (r > 0)
@@ -773,8 +957,13 @@ static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controlle
/* Mount a legacy cgroup hierarchy when cgroup namespaces are supported. */
static int mount_legacy_cgns_supported(
- CGroupUnified unified_requested, bool userns, uid_t uid_shift,
- uid_t uid_range, const char *selinux_apifs_context) {
+ const char *dest,
+ CGroupUnified unified_requested,
+ bool userns,
+ uid_t uid_shift,
+ uid_t uid_range,
+ const char *selinux_apifs_context) {
+
_cleanup_set_free_free_ Set *controllers = NULL;
const char *cgroup_root = "/sys/fs/cgroup", *c;
int r;
@@ -782,7 +971,7 @@ static int mount_legacy_cgns_supported(
(void) mkdir_p(cgroup_root, 0755);
/* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */
- r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW);
if (r < 0)
return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
if (r == 0) {
@@ -871,8 +1060,12 @@ skip_controllers:
/* Mount legacy cgroup hierarchy when cgroup namespaces are unsupported. */
static int mount_legacy_cgns_unsupported(
const char *dest,
- CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range,
+ CGroupUnified unified_requested,
+ bool userns,
+ uid_t uid_shift,
+ uid_t uid_range,
const char *selinux_apifs_context) {
+
_cleanup_set_free_free_ Set *controllers = NULL;
const char *cgroup_root;
int r;
@@ -882,7 +1075,7 @@ static int mount_legacy_cgns_unsupported(
(void) mkdir_p(cgroup_root, 0755);
/* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */
- r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW);
if (r < 0)
return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
if (r == 0) {
@@ -975,7 +1168,7 @@ static int mount_unified_cgroups(const char *dest) {
(void) mkdir_p(p, 0755);
- r = path_is_mount_point(p, AT_SYMLINK_FOLLOW);
+ r = path_is_mount_point(p, dest, AT_SYMLINK_FOLLOW);
if (r < 0)
return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p);
if (r > 0) {
@@ -995,14 +1188,16 @@ static int mount_unified_cgroups(const char *dest) {
int mount_cgroups(
const char *dest,
CGroupUnified unified_requested,
- bool userns, uid_t uid_shift, uid_t uid_range,
+ bool userns,
+ uid_t uid_shift,
+ uid_t uid_range,
const char *selinux_apifs_context,
bool use_cgns) {
if (unified_requested >= CGROUP_UNIFIED_ALL)
return mount_unified_cgroups(dest);
else if (use_cgns)
- return mount_legacy_cgns_supported(unified_requested, userns, uid_shift, uid_range, selinux_apifs_context);
+ return mount_legacy_cgns_supported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context);
return mount_legacy_cgns_unsupported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context);
}
diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h
index 74aee7ee7f..467082a737 100644
--- a/src/nspawn/nspawn-mount.h
+++ b/src/nspawn/nspawn-mount.h
@@ -56,15 +56,16 @@ typedef struct CustomMount {
char *options;
char *work_dir;
char **lower;
+ char *rm_rf_tmpdir;
} CustomMount;
CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t);
-
void custom_mount_free_all(CustomMount *l, unsigned n);
+int custom_mount_prepare_all(const char *dest, CustomMount *l, unsigned n);
+
int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only);
int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s);
-
-int custom_mount_compare(const void *a, const void *b);
+int overlay_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only);
int mount_all(const char *dest, MountSettingsMask mount_settings, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context);
int mount_sysfs(const char *dest, MountSettingsMask mount_settings);
diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c
index 06c56d9ec8..e3ab39faea 100644
--- a/src/nspawn/nspawn-register.c
+++ b/src/nspawn/nspawn-register.c
@@ -135,6 +135,11 @@ int register_machine(
continue;
r = is_device_node(cm->source);
+ if (r == -ENOENT) {
+ /* The bind source might only appear as the image is put together, hence don't complain */
+ log_debug_errno(r, "Bind mount source %s not found, ignoring: %m", cm->source);
+ continue;
+ }
if (r < 0)
return log_error_errno(r, "Failed to stat %s: %m", cm->source);
diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c
index 09c8f070ba..22b74d88e4 100644
--- a/src/nspawn/nspawn-settings.c
+++ b/src/nspawn/nspawn-settings.c
@@ -293,6 +293,32 @@ int config_parse_tmpfs(
return 0;
}
+int config_parse_overlay(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Settings *settings = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = overlay_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype);
+ if (r < 0)
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid overlay file system specification %s, ignoring: %m", rvalue);
+
+ return 0;
+}
+
int config_parse_veth_extra(
const char *unit,
const char *filename,
diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h
index 231e6d7266..4bd0c642df 100644
--- a/src/nspawn/nspawn-settings.h
+++ b/src/nspawn/nspawn-settings.h
@@ -111,6 +111,7 @@ int config_parse_expose_port(const char *unit, const char *filename, unsigned li
int config_parse_volatile_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_bind(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_tmpfs(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_overlay(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_veth_extra(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_network_zone(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_boot(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 2770770cd0..ddd6a64ec6 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -280,14 +280,9 @@ static void help(void) {
, program_invocation_short_name);
}
-static int custom_mounts_prepare(void) {
+static int custom_mount_check_all(void) {
unsigned i;
- int r;
-
- /* Ensure the mounts are applied prefix first. */
- qsort_safe(arg_custom_mounts, arg_n_custom_mounts, sizeof(CustomMount), custom_mount_compare);
- /* Allocate working directories for the overlay file systems that need it */
for (i = 0; i < arg_n_custom_mounts; i++) {
CustomMount *m = &arg_custom_mounts[i];
@@ -301,19 +296,6 @@ static int custom_mounts_prepare(void) {
return -EINVAL;
}
}
-
- if (m->type != CUSTOM_MOUNT_OVERLAY)
- continue;
-
- if (m->work_dir)
- continue;
-
- if (m->read_only)
- continue;
-
- r = tempfn_random(m->source, NULL, &m->work_dir);
- if (r < 0)
- return log_error_errno(r, "Failed to generate work directory from %s: %m", m->source);
}
return 0;
@@ -789,69 +771,15 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_OVERLAY:
- case ARG_OVERLAY_RO: {
- _cleanup_free_ char *upper = NULL, *destination = NULL;
- _cleanup_strv_free_ char **lower = NULL;
- CustomMount *m;
- unsigned n = 0;
- char **i;
-
- r = strv_split_extract(&lower, optarg, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
- if (r == -ENOMEM)
- return log_oom();
- else if (r < 0) {
- log_error("Invalid overlay specification: %s", optarg);
- return r;
- }
-
- STRV_FOREACH(i, lower) {
- if (!path_is_absolute(*i)) {
- log_error("Overlay path %s is not absolute.", *i);
- return -EINVAL;
- }
-
- n++;
- }
-
- if (n < 2) {
- log_error("--overlay= needs at least two colon-separated directories specified.");
- return -EINVAL;
- }
-
- if (n == 2) {
- /* If two parameters are specified,
- * the first one is the lower, the
- * second one the upper directory. And
- * we'll also define the destination
- * mount point the same as the upper. */
- upper = lower[1];
- lower[1] = NULL;
-
- destination = strdup(upper);
- if (!destination)
- return log_oom();
-
- } else {
- upper = lower[n - 2];
- destination = lower[n - 1];
- lower[n - 2] = NULL;
- }
-
- m = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_OVERLAY);
- if (!m)
- return log_oom();
-
- m->destination = destination;
- m->source = upper;
- m->lower = lower;
- m->read_only = c == ARG_OVERLAY_RO;
-
- upper = destination = NULL;
- lower = NULL;
+ case ARG_OVERLAY_RO:
+ r = overlay_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_OVERLAY_RO);
+ if (r == -EADDRNOTAVAIL)
+ return log_error_errno(r, "--overlay(-ro)= needs at least two colon-separated directories specified.");
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --overlay(-ro)= argument %s: %m", optarg);
arg_settings_mask |= SETTING_CUSTOM_MOUNTS;
break;
- }
case 'E': {
char **n;
@@ -1133,6 +1061,16 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
+ if (arg_ephemeral && arg_template && !arg_directory) {
+ /* User asked for ephemeral execution but specified --template= instead of --directory=. Semantically
+ * such an invocation makes some sense, see https://github.com/systemd/systemd/issues/3667. Let's
+ * accept this here, and silently make "--ephemeral --template=" equivalent to "--ephemeral
+ * --directory=". */
+
+ arg_directory = arg_template;
+ arg_template = NULL;
+ }
+
if (arg_template && !(arg_directory || arg_machine)) {
log_error("--template= needs --directory= or --machine=.");
return -EINVAL;
@@ -1191,6 +1129,10 @@ static int parse_argv(int argc, char *argv[]) {
else
arg_use_cgns = r;
+ r = custom_mount_check_all();
+ if (r < 0)
+ return r;
+
return 1;
}
@@ -1666,7 +1608,7 @@ static int setup_journal(const char *directory) {
p = strjoina("/var/log/journal/", id);
q = prefix_roota(directory, p);
- if (path_is_mount_point(p, 0) > 0) {
+ if (path_is_mount_point(p, NULL, 0) > 0) {
if (try)
return 0;
@@ -1674,7 +1616,7 @@ static int setup_journal(const char *directory) {
return -EEXIST;
}
- if (path_is_mount_point(q, 0) > 0) {
+ if (path_is_mount_point(q, NULL, 0) > 0) {
if (try)
return 0;
@@ -2656,6 +2598,25 @@ static int determine_names(void) {
return 0;
}
+static int chase_symlinks_and_update(char **p, unsigned flags) {
+ char *chased;
+ int r;
+
+ assert(p);
+
+ if (!*p)
+ return 0;
+
+ r = chase_symlinks(*p, NULL, flags, &chased);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve path %s: %m", *p);
+
+ free(*p);
+ *p = chased;
+
+ return 0;
+}
+
static int determine_uid_shift(const char *directory) {
int r;
@@ -3657,7 +3618,7 @@ static int run(int master,
static const struct sigaction sa = {
.sa_handler = nop_signal_handler,
- .sa_flags = SA_NOCLDSTOP,
+ .sa_flags = SA_NOCLDSTOP|SA_RESTART,
};
_cleanup_release_lock_file_ LockFile uid_shift_lock = LOCK_FILE_INIT;
@@ -4126,13 +4087,17 @@ int main(int argc, char *argv[]) {
if (arg_ephemeral) {
_cleanup_free_ char *np = NULL;
+ r = chase_symlinks_and_update(&arg_directory, 0);
+ if (r < 0)
+ goto finish;
+
/* If the specified path is a mount point we
* generate the new snapshot immediately
* inside it under a random name. However if
* the specified is not a mount point we
* create the new snapshot in the parent
* directory, just next to it. */
- r = path_is_mount_point(arg_directory, 0);
+ r = path_is_mount_point(arg_directory, NULL, 0);
if (r < 0) {
log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory);
goto finish;
@@ -4170,6 +4135,10 @@ int main(int argc, char *argv[]) {
remove_directory = true;
} else {
+ r = chase_symlinks_and_update(&arg_directory, arg_template ? CHASE_NONEXISTENT : 0);
+ if (r < 0)
+ goto finish;
+
r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
if (r == -EBUSY) {
log_error_errno(r, "Directory tree %s is currently busy.", arg_directory);
@@ -4181,6 +4150,10 @@ int main(int argc, char *argv[]) {
}
if (arg_template) {
+ r = chase_symlinks_and_update(&arg_template, 0);
+ if (r < 0)
+ goto finish;
+
r = btrfs_subvol_snapshot(arg_template, arg_directory,
(arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) |
BTRFS_SNAPSHOT_FALLBACK_COPY |
@@ -4222,6 +4195,10 @@ int main(int argc, char *argv[]) {
assert(arg_image);
assert(!arg_template);
+ r = chase_symlinks_and_update(&arg_image, 0);
+ if (r < 0)
+ goto finish;
+
if (arg_ephemeral) {
_cleanup_free_ char *np = NULL;
@@ -4293,7 +4270,7 @@ int main(int argc, char *argv[]) {
remove_image = false;
}
- r = custom_mounts_prepare();
+ r = custom_mount_prepare_all(arg_directory, arg_custom_mounts, arg_n_custom_mounts);
if (r < 0)
goto finish;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 388b391342..3114275c85 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -764,6 +764,7 @@ static const struct {
const char *result, *explanation;
} explanations [] = {
{ "resources", "of unavailable resources or another system error" },
+ { "protocol", "the service did not take the steps required by its unit configuration" },
{ "timeout", "a timeout was exceeded" },
{ "exit-code", "the control process exited with error code" },
{ "signal", "a fatal signal was delivered to the control process" },
diff --git a/src/shared/condition.c b/src/shared/condition.c
index 525e65aedf..0b77d2c22d 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -399,7 +399,7 @@ static int condition_test_path_is_mount_point(Condition *c) {
assert(c->parameter);
assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
- return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0;
+ return path_is_mount_point(c->parameter, NULL, AT_SYMLINK_FOLLOW) > 0;
}
static int condition_test_path_is_read_write(Condition *c) {
diff --git a/src/shared/machine-pool.c b/src/shared/machine-pool.c
index 23890c63a0..c581bdeb79 100644
--- a/src/shared/machine-pool.c
+++ b/src/shared/machine-pool.c
@@ -225,7 +225,7 @@ int setup_machine_directory(uint64_t size, sd_bus_error *error) {
return 1;
}
- if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) {
+ if (path_is_mount_point("/var/lib/machines", NULL, AT_SYMLINK_FOLLOW) > 0) {
log_debug("/var/lib/machines is already a mount point, not creating loopback file for it.");
return 0;
}
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index ed1c7178b5..67516cf5fb 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -2487,7 +2487,7 @@ static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **un
if (!path)
return log_oom();
- r = chase_symlinks(path, arg_root, &lpath);
+ r = chase_symlinks(path, arg_root, 0, &lpath);
if (r == -ENOENT)
continue;
if (r == -ENOMEM)
@@ -6438,7 +6438,7 @@ static int unit_is_enabled(int argc, char *argv[], void *userdata) {
r = unit_file_get_state(arg_scope, arg_root, *name, &state);
if (r < 0)
- return log_error_errno(state, "Failed to get unit file state for %s: %m", *name);
+ return log_error_errno(r, "Failed to get unit file state for %s: %m", *name);
if (IN_SET(state,
UNIT_FILE_ENABLED,
diff --git a/src/test/test-copy.c b/src/test/test-copy.c
index 91d2a0bcd4..e65516f080 100644
--- a/src/test/test-copy.c
+++ b/src/test/test-copy.c
@@ -144,7 +144,7 @@ static void test_copy_tree(void) {
assert_se((f = strjoin(original_dir, *p)));
assert_se((l = strjoin(copy_dir, *link)));
- assert_se(readlink_and_canonicalize(l, &target) == 0);
+ assert_se(readlink_and_canonicalize(l, NULL, &target) == 0);
assert_se(path_equal(f, target));
}
diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c
index 53a3cdc663..b502cd0ad1 100644
--- a/src/test/test-fs-util.c
+++ b/src/test/test-fs-util.c
@@ -60,66 +60,131 @@ static void test_chase_symlinks(void) {
p = strjoina(temp, "/start");
assert_se(symlink("top/dot/dotdota", p) >= 0);
- r = chase_symlinks(p, NULL, &result);
- assert_se(r >= 0);
+ /* Paths that use symlinks underneath the "root" */
+
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, "/usr"));
result = mfree(result);
- r = chase_symlinks(p, temp, &result);
+ r = chase_symlinks(p, temp, 0, &result);
assert_se(r == -ENOENT);
q = strjoina(temp, "/usr");
+
+ r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, q));
+
assert_se(mkdir(q, 0700) >= 0);
- r = chase_symlinks(p, temp, &result);
- assert_se(r >= 0);
+ r = chase_symlinks(p, temp, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, q));
p = strjoina(temp, "/slash");
assert_se(symlink("/", p) >= 0);
result = mfree(result);
- r = chase_symlinks(p, NULL, &result);
- assert_se(r >= 0);
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, "/"));
result = mfree(result);
- r = chase_symlinks(p, temp, &result);
- assert_se(r >= 0);
+ r = chase_symlinks(p, temp, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, temp));
+ /* Paths that would "escape" outside of the "root" */
+
+ p = strjoina(temp, "/6dots");
+ assert_se(symlink("../../..", p) >= 0);
+
+ result = mfree(result);
+ r = chase_symlinks(p, temp, 0, &result);
+ assert_se(r > 0 && path_equal(result, temp));
+
+ p = strjoina(temp, "/6dotsusr");
+ assert_se(symlink("../../../usr", p) >= 0);
+
+ result = mfree(result);
+ r = chase_symlinks(p, temp, 0, &result);
+ assert_se(r > 0 && path_equal(result, q));
+
+ p = strjoina(temp, "/top/8dotsusr");
+ assert_se(symlink("../../../../usr", p) >= 0);
+
+ result = mfree(result);
+ r = chase_symlinks(p, temp, 0, &result);
+ assert_se(r > 0 && path_equal(result, q));
+
+ /* Paths that contain repeated slashes */
+
p = strjoina(temp, "/slashslash");
assert_se(symlink("///usr///", p) >= 0);
result = mfree(result);
- r = chase_symlinks(p, NULL, &result);
- assert_se(r >= 0);
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, "/usr"));
result = mfree(result);
- r = chase_symlinks(p, temp, &result);
- assert_se(r >= 0);
+ r = chase_symlinks(p, temp, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, q));
+ /* Paths using . */
+
result = mfree(result);
- r = chase_symlinks("/etc/./.././", NULL, &result);
- assert_se(r >= 0);
+ r = chase_symlinks("/etc/./.././", NULL, 0, &result);
+ assert_se(r > 0);
assert_se(path_equal(result, "/"));
result = mfree(result);
- r = chase_symlinks("/etc/./.././", "/etc", &result);
- assert_se(r == -EINVAL);
+ r = chase_symlinks("/etc/./.././", "/etc", 0, &result);
+ assert_se(r > 0 && path_equal(result, "/etc"));
result = mfree(result);
- r = chase_symlinks("/etc/machine-id/foo", NULL, &result);
+ r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result);
assert_se(r == -ENOTDIR);
+ /* Path that loops back to self */
+
result = mfree(result);
p = strjoina(temp, "/recursive-symlink");
assert_se(symlink("recursive-symlink", p) >= 0);
- r = chase_symlinks(p, NULL, &result);
+ r = chase_symlinks(p, NULL, 0, &result);
assert_se(r == -ELOOP);
+ /* Path which doesn't exist */
+
+ p = strjoina(temp, "/idontexist");
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r == -ENOENT);
+
+ r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ p = strjoina(temp, "/idontexist/meneither");
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r == -ENOENT);
+
+ r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ /* Path which doesn't exist, but contains weird stuff */
+
+ p = strjoina(temp, "/idontexist/..");
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r == -ENOENT);
+
+ r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
+ assert_se(r == -ENOENT);
+
assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}
diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c
index a6a09a0031..22df20a1eb 100644
--- a/src/test/test-path-util.c
+++ b/src/test/test-path-util.c
@@ -337,17 +337,17 @@ static void test_path_is_mount_point(void) {
_cleanup_free_ char *dir1 = NULL, *dir1file = NULL, *dirlink1 = NULL, *dirlink1file = NULL;
_cleanup_free_ char *dir2 = NULL, *dir2file = NULL;
- assert_se(path_is_mount_point("/", AT_SYMLINK_FOLLOW) > 0);
- assert_se(path_is_mount_point("/", 0) > 0);
+ assert_se(path_is_mount_point("/", NULL, AT_SYMLINK_FOLLOW) > 0);
+ assert_se(path_is_mount_point("/", NULL, 0) > 0);
- assert_se(path_is_mount_point("/proc", AT_SYMLINK_FOLLOW) > 0);
- assert_se(path_is_mount_point("/proc", 0) > 0);
+ assert_se(path_is_mount_point("/proc", NULL, AT_SYMLINK_FOLLOW) > 0);
+ assert_se(path_is_mount_point("/proc", NULL, 0) > 0);
- assert_se(path_is_mount_point("/proc/1", AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point("/proc/1", 0) == 0);
+ assert_se(path_is_mount_point("/proc/1", NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point("/proc/1", NULL, 0) == 0);
- assert_se(path_is_mount_point("/sys", AT_SYMLINK_FOLLOW) > 0);
- assert_se(path_is_mount_point("/sys", 0) > 0);
+ assert_se(path_is_mount_point("/sys", NULL, AT_SYMLINK_FOLLOW) > 0);
+ assert_se(path_is_mount_point("/sys", NULL, 0) > 0);
/* we'll create a hierarchy of different kinds of dir/file/link
* layouts:
@@ -381,10 +381,10 @@ static void test_path_is_mount_point(void) {
assert_se(link1);
assert_se(symlink("file2", link2) == 0);
- assert_se(path_is_mount_point(file1, AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point(file1, 0) == 0);
- assert_se(path_is_mount_point(link1, AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point(link1, 0) == 0);
+ assert_se(path_is_mount_point(file1, NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point(file1, NULL, 0) == 0);
+ assert_se(path_is_mount_point(link1, NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point(link1, NULL, 0) == 0);
/* directory mountpoints */
dir1 = path_join(NULL, tmp_dir, "dir1");
@@ -400,10 +400,10 @@ static void test_path_is_mount_point(void) {
assert_se(dir2);
assert_se(mkdir(dir2, 0755) == 0);
- assert_se(path_is_mount_point(dir1, AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point(dir1, 0) == 0);
- assert_se(path_is_mount_point(dirlink1, AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point(dirlink1, 0) == 0);
+ assert_se(path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point(dir1, NULL, 0) == 0);
+ assert_se(path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point(dirlink1, NULL, 0) == 0);
/* file in subdirectory mountpoints */
dir1file = path_join(NULL, dir1, "file");
@@ -412,10 +412,10 @@ static void test_path_is_mount_point(void) {
assert_se(fd > 0);
close(fd);
- assert_se(path_is_mount_point(dir1file, AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point(dir1file, 0) == 0);
- assert_se(path_is_mount_point(dirlink1file, AT_SYMLINK_FOLLOW) == 0);
- assert_se(path_is_mount_point(dirlink1file, 0) == 0);
+ assert_se(path_is_mount_point(dir1file, NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point(dir1file, NULL, 0) == 0);
+ assert_se(path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW) == 0);
+ assert_se(path_is_mount_point(dirlink1file, NULL, 0) == 0);
/* these tests will only work as root */
if (mount(file1, file2, NULL, MS_BIND, NULL) >= 0) {
@@ -423,10 +423,10 @@ static void test_path_is_mount_point(void) {
/* files */
/* capture results in vars, to avoid dangling mounts on failure */
- rf = path_is_mount_point(file2, 0);
- rt = path_is_mount_point(file2, AT_SYMLINK_FOLLOW);
- rlf = path_is_mount_point(link2, 0);
- rlt = path_is_mount_point(link2, AT_SYMLINK_FOLLOW);
+ rf = path_is_mount_point(file2, NULL, 0);
+ rt = path_is_mount_point(file2, NULL, AT_SYMLINK_FOLLOW);
+ rlf = path_is_mount_point(link2, NULL, 0);
+ rlt = path_is_mount_point(link2, NULL, AT_SYMLINK_FOLLOW);
assert_se(umount(file2) == 0);
@@ -444,13 +444,13 @@ static void test_path_is_mount_point(void) {
assert_se(mount(dir2, dir1, NULL, MS_BIND, NULL) >= 0);
- rf = path_is_mount_point(dir1, 0);
- rt = path_is_mount_point(dir1, AT_SYMLINK_FOLLOW);
- rlf = path_is_mount_point(dirlink1, 0);
- rlt = path_is_mount_point(dirlink1, AT_SYMLINK_FOLLOW);
+ rf = path_is_mount_point(dir1, NULL, 0);
+ rt = path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW);
+ rlf = path_is_mount_point(dirlink1, NULL, 0);
+ rlt = path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW);
/* its parent is a mount point, but not /file itself */
- rl1f = path_is_mount_point(dirlink1file, 0);
- rl1t = path_is_mount_point(dirlink1file, AT_SYMLINK_FOLLOW);
+ rl1f = path_is_mount_point(dirlink1file, NULL, 0);
+ rl1t = path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW);
assert_se(umount(dir1) == 0);
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index f6c416bf70..d88687e9c2 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -614,7 +614,7 @@ static int import_property_from_string(struct udev_device *dev, char *line) {
/* unquote */
if (val[0] == '"' || val[0] == '\'') {
- if (val[len-1] != val[0]) {
+ if (len == 1 || val[len-1] != val[0]) {
log_debug("inconsistent quoting: '%s', skip", line);
return -1;
}
diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c
index f631834341..11abebb351 100644
--- a/src/udev/udevadm-monitor.c
+++ b/src/udev/udevadm-monitor.c
@@ -143,7 +143,7 @@ static int adm_monitor(struct udev *udev, int argc, char *argv[]) {
/* set signal handlers */
act.sa_handler = sig_handler;
- act.sa_flags = SA_RESTART;
+ act.sa_flags = SA_RESTART|SA_RESTART;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
sigemptyset(&mask);
diff --git a/test/networkd-test.py b/test/networkd-test.py
index aed5139275..a932d32b92 100755
--- a/test/networkd-test.py
+++ b/test/networkd-test.py
@@ -221,7 +221,7 @@ DHCP=%s
# check iface state and IP 6 address; FIXME: we need to wait a bit
# longer, as the iface is "configured" already with IPv4 *or*
# IPv6, but we want to wait for both
- for timeout in range(10):
+ for _ in range(10):
out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface])
if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out:
break
@@ -234,30 +234,30 @@ DHCP=%s
else:
# should have link-local address on IPv6 only
out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface])
- self.assertRegex(out, b'inet6 fe80::.* scope link')
+ self.assertRegex(out, br'inet6 fe80::.* scope link')
self.assertNotIn(b'scope global', out)
# should have IPv4 address
out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
self.assertIn(b'state UP', out)
- self.assertRegex(out, b'inet 192.168.5.\d+/.* scope global dynamic')
+ self.assertRegex(out, br'inet 192.168.5.\d+/.* scope global dynamic')
# check networkctl state
out = subprocess.check_output(['networkctl'])
- self.assertRegex(out, ('%s\s+ether\s+routable\s+unmanaged' % self.if_router).encode())
- self.assertRegex(out, ('%s\s+ether\s+routable\s+configured' % self.iface).encode())
+ self.assertRegex(out, (r'%s\s+ether\s+routable\s+unmanaged' % self.if_router).encode())
+ self.assertRegex(out, (r'%s\s+ether\s+routable\s+configured' % self.iface).encode())
out = subprocess.check_output(['networkctl', 'status', self.iface])
- self.assertRegex(out, b'Type:\s+ether')
- self.assertRegex(out, b'State:\s+routable.*configured')
- self.assertRegex(out, b'Address:\s+192.168.5.\d+')
+ self.assertRegex(out, br'Type:\s+ether')
+ self.assertRegex(out, br'State:\s+routable.*configured')
+ self.assertRegex(out, br'Address:\s+192.168.5.\d+')
if ipv6:
- self.assertRegex(out, b'2600::')
+ self.assertRegex(out, br'2600::')
else:
- self.assertNotIn(b'2600::', out)
- self.assertRegex(out, b'fe80::')
- self.assertRegex(out, b'Gateway:\s+192.168.5.1')
- self.assertRegex(out, b'DNS:\s+192.168.5.1')
+ self.assertNotIn(br'2600::', out)
+ self.assertRegex(out, br'fe80::')
+ self.assertRegex(out, br'Gateway:\s+192.168.5.1')
+ self.assertRegex(out, br'DNS:\s+192.168.5.1')
except (AssertionError, subprocess.CalledProcessError):
# show networkd status, journal, and DHCP server log on failure
with open(os.path.join(NETWORK_UNITDIR, self.config)) as f:
@@ -516,8 +516,17 @@ Domains= ~company ~lab''')
# should have received the fixed IP above
out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
- # should have set transient hostname in hostnamed
- self.assertIn(b'testgreen', subprocess.check_output(['hostnamectl']))
+ # should have set transient hostname in hostnamed; this is
+ # sometimes a bit lagging (issue #4753), so retry a few times
+ for retry in range(1, 6):
+ out = subprocess.check_output(['hostnamectl'])
+ if b'testgreen' in out:
+ break
+ time.sleep(5)
+ sys.stdout.write('[retry %i] ' % retry)
+ sys.stdout.flush()
+ else:
+ self.fail('Transient hostname not found in hostnamectl:\n%s' % out.decode())
# and also applied to the system
self.assertEqual(socket.gethostname(), 'testgreen')
except AssertionError:
@@ -613,7 +622,7 @@ exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=//
'--service-type=notify', script])
# wait until devices got created
- for timeout in range(50):
+ for _ in range(50):
out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.if_router])
if b'state UP' in out and b'scope global' in out:
break
diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py
index e4185cb0fa..dab01f1d8a 100755
--- a/test/rule-syntax-check.py
+++ b/test/rule-syntax-check.py
@@ -34,10 +34,10 @@ else:
sys.exit(2)
rules_files = glob(os.path.join(rules_dir, '*.rules'))
-no_args_tests = re.compile('(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|RESULT|TEST)\s*(?:=|!)=\s*"([^"]*)"$')
-args_tests = re.compile('(ATTRS?|ENV|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*"([^"]*)"$')
-no_args_assign = re.compile('(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|PROGRAM|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*"([^"]*)"$')
-args_assign = re.compile('(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*"([^"]*)"$')
+no_args_tests = re.compile(r'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|RESULT|TEST)\s*(?:=|!)=\s*"([^"]*)"$')
+args_tests = re.compile(r'(ATTRS?|ENV|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*"([^"]*)"$')
+no_args_assign = re.compile(r'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|PROGRAM|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*"([^"]*)"$')
+args_assign = re.compile(r'(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*"([^"]*)"$')
result = 0
buffer = ''
diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py
index 50175485f7..16ea65690a 100755
--- a/test/sysv-generator-test.py
+++ b/test/sysv-generator-test.py
@@ -308,7 +308,7 @@ class SysvGeneratorTest(unittest.TestCase):
err, results = self.run_generator()
self.assertEqual(list(results), ['foo.service'])
self.assertEqual(os.readlink(os.path.join(self.out_dir, 'foo\\x2b.service')),
- 'foo.service')
+ 'foo.service')
self.assertNotIn('Overwriting', err)
def test_same_provides_in_multiple_scripts(self):
diff --git a/units/.gitignore b/units/.gitignore
index 8f4949258e..8fdb6e9ab5 100644
--- a/units/.gitignore
+++ b/units/.gitignore
@@ -16,6 +16,7 @@
/rc-local.service
/rescue.service
/serial-getty@.service
+/system-update-cleanup.service
/systemd-ask-password-console.service
/systemd-ask-password-wall.service
/systemd-backlight@.service
diff --git a/units/system-update-cleanup.service.in b/units/system-update-cleanup.service.in
new file mode 100644
index 0000000000..116be8bc2d
--- /dev/null
+++ b/units/system-update-cleanup.service.in
@@ -0,0 +1,32 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Remove the Offline System Updates symlink
+Documentation=man:systemd.special(5) man:systemd.offline-updates(7)
+After=system-update.target
+DefaultDependencies=no
+Conflicts=shutdown.target
+
+# system-update-generator uses laccess("/system-update"), while a plain
+# ConditionPathExists=/system-update uses access("/system-update"), so
+# we need an alternate condition to cover the case of a dangling symlink.
+#
+# This service is only invoked if /system-update exists, i.e. if the
+# condition tested by system-update-generator remains true and the system
+# would be diverted into system-update.target again after reboot. This way
+# we guard against being diverted into system-update.target again, which
+# works as a safety measure, but we will not step on the toes of the
+# update script if it successfully removed the symlink and scheduled a
+# reboot or some other action on its own.
+ConditionPathExists=|/system-update
+ConditionPathIsSymbolicLink=|/system-update
+
+[Service]
+Type=oneshot
+ExecStart=/bin/rm -fv /system-update
+ExecStart=@SYSTEMCTL@ reboot
diff --git a/units/system-update.target b/units/system-update.target
index 48d46fcbda..3542879706 100644
--- a/units/system-update.target
+++ b/units/system-update.target
@@ -6,11 +6,12 @@
# (at your option) any later version.
[Unit]
-Description=System Update
-Documentation=http://freedesktop.org/wiki/Software/systemd/SystemUpdates
+Description=Offline System Update
+Documentation=man:systemd.offline-updates(7)
Documentation=man:systemd.special(7) man:systemd-system-update-generator(8)
Requires=sysinit.target
Conflicts=shutdown.target
After=sysinit.target
Before=shutdown.target
AllowIsolate=yes
+Wants=system-update-cleanup.service