diff options
84 files changed, 1220 insertions, 500 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 6ea367b9a7..99adfe4480 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5731,6 +5731,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 @@ -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 @@ -32,8 +32,6 @@ Features: * replace all canonicalize_file_name() invocations by chase_symlinks(), in particulr those where a rootdir is relevant. -* add parse_ip_port() or so, that is like safe_atou16() but checks for != 0 - * drop nss-myhostname in favour of nss-resolve? * drop internal dlopen() based nss-dns fallback in nss-resolve, and rely on the diff --git a/hwdb/60-keyboard.hwdb b/hwdb/60-keyboard.hwdb index c4031ab587..fff3b9a6ea 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,18 @@ 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* +# Yubico Yubikey NEO OTP+U2F+CCID +evdev:input:b0003v1050p0116* +# OKE Electron Company USB barcode reader +evdev:input:b0003v05FEp1010* + 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/sd_event_add_defer.xml b/man/sd_event_add_defer.xml index d9ebd3b179..ab28b330fe 100644 --- a/man/sd_event_add_defer.xml +++ b/man/sd_event_add_defer.xml @@ -153,7 +153,7 @@ <refsect1> <title>Return Value</title> - <para>On success, this functions return 0 or a positive + <para>On success, these functions return 0 or a positive integer. On failure, they return a negative errno-style error code.</para> </refsect1> diff --git a/man/sd_event_source_set_prepare.xml b/man/sd_event_source_set_prepare.xml index 24861d01d9..ee61d23983 100644 --- a/man/sd_event_source_set_prepare.xml +++ b/man/sd_event_source_set_prepare.xml @@ -76,10 +76,11 @@ specified as <parameter>callback</parameter> will be invoked immediately before the event loop goes to sleep to wait for incoming events. It is invoked with the user data pointer passed - when the event source was created. The callback function may be - used to reconfigure the precise events to wait for. If the - <parameter>callback</parameter> parameter is passed as NULL the - callback function is reset. </para> + when the event source was created. The event source will be disabled + if the callback function returns a negative error code. The callback + function may be used to reconfigure the precise events to wait for. + If the <parameter>callback</parameter> parameter is passed as NULL + the callback function is reset. </para> <para>Event source objects have no preparation callback associated when they are first created with calls such as 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..f27e4a5c04 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 @@ -1806,23 +1806,32 @@ <title>Summary of possible service result variable values</title> <tgroup cols='3'> <colspec colname='result' /> - <colspec colname='status' /> <colspec colname='code' /> + <colspec colname='status' /> <thead> <row> <entry><varname>$SERVICE_RESULT</varname></entry> - <entry><varname>$EXIT_STATUS</varname></entry> <entry><varname>$EXIT_CODE</varname></entry> + <entry><varname>$EXIT_STATUS</varname></entry> </row> </thead> <tbody> <row> + <entry morerows="1" valign="top"><literal>protocol</literal></entry> + <entry valign="top">not set</entry> + <entry>not set</entry> + </row> + <row> + <entry><literal>exited</literal></entry> + <entry><literal>0</literal></entry> + </row> + + <row> <entry morerows="1" valign="top"><literal>timeout</literal></entry> <entry valign="top"><literal>killed</literal></entry> <entry><literal>TERM</literal>, <literal>KILL</literal></entry> </row> - <row> <entry valign="top"><literal>exited</literal></entry> <entry><literal>0</literal>, <literal>1</literal>, <literal>2</literal>, <literal 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.network.xml b/man/systemd.network.xml index 99283813fd..0fa68b7623 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -123,7 +123,10 @@ <listitem> <para>A whitespace-separated list of shell-style globs matching the persistent path, as exposed by the udev - property <literal>ID_PATH</literal>.</para> + property <literal>ID_PATH</literal>. If the list is + prefixed with a "!", the test is inverted; i.e. it is + true when <literal>ID_PATH</literal> does not match any + item in the list.</para> </listitem> </varlistentry> <varlistentry> @@ -134,7 +137,8 @@ exposed by the udev property <literal>DRIVER</literal> of its parent device, or if that is not set the driver as exposed by <literal>ethtool -i</literal> of the - device itself.</para> + device itself. If the list is prefixed with a "!", the + test is inverted.</para> </listitem> </varlistentry> <varlistentry> @@ -142,7 +146,8 @@ <listitem> <para>A whitespace-separated list of shell-style globs matching the device type, as exposed by the udev property - <literal>DEVTYPE</literal>.</para> + <literal>DEVTYPE</literal>. If the list is prefixed with + a "!", the test is inverted.</para> </listitem> </varlistentry> <varlistentry> @@ -150,7 +155,8 @@ <listitem> <para>A whitespace-separated list of shell-style globs matching the device name, as exposed by the udev property - <literal>INTERFACE</literal>.</para> + <literal>INTERFACE</literal>. If the list is prefixed + with a "!", the test is inverted.</para> </listitem> </varlistentry> <varlistentry> @@ -232,6 +238,18 @@ the network otherwise.</para> </listitem> </varlistentry> + <varlistentry> + <term><varname>Unmanaged=</varname></term> + <listitem> + <para>A boolean. When <literal>yes</literal>, no attempts are + made to bring up or configure matching links, equivalent to + when there are no matching network files. Defaults to + <literal>no</literal>.</para> + <para>This is useful for preventing later matching network + files from interfering with certain interfaces that are fully + controlled by other applications.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> 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.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 @@ -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..6f60ae9024 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" @@ -54,7 +54,7 @@ KERNEL=="sd*[0-9]", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$att # MMC KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{name}=="?*", ATTRS{serial}=="?*", \ ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}" -KERNEL=="mmcblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n" +KERNEL=="mmcblk[0-9]p[0-9]*", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n" # Memstick KERNEL=="msblk[0-9]|mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}=="?*", \ 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/calendarspec.c b/src/basic/calendarspec.c index 8b57de4744..514587d237 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -752,12 +752,8 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) { goto fail; /* Already at the end? Then it's hours and minutes, and seconds are 0 */ - if (*t == 0) { - if (m != NULL) - goto null_second; - - goto finish; - } + if (*t == 0) + goto null_second; if (*t != ':') { r = -EINVAL; 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/parse-util.c b/src/basic/parse-util.c index c98815b9bc..6e58ced6f5 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -574,3 +574,19 @@ int parse_nice(const char *p, int *ret) { *ret = n; return 0; } + +int parse_ip_port(const char *s, uint16_t *ret) { + uint16_t l; + int r; + + r = safe_atou16(s, &l); + if (r < 0) + return r; + + if (l == 0) + return -EINVAL; + + *ret = (uint16_t) l; + + return 0; +} diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 461e1cd4d8..4d132f0de5 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -110,3 +110,5 @@ int parse_percent_unbounded(const char *p); int parse_percent(const char *p); int parse_nice(const char *p, int *ret); + +int parse_ip_port(const char *s, uint16_t *ret); 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/basic/time-util.c b/src/basic/time-util.c index cbdfd55ada..7a5b29d77e 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -883,6 +883,7 @@ static char* extract_multiplier(char *p, usec_t *multiplier) { { "y", USEC_PER_YEAR }, { "usec", 1ULL }, { "us", 1ULL }, + { "µs", 1ULL }, }; unsigned i; @@ -1016,6 +1017,7 @@ int parse_nsec(const char *t, nsec_t *nsec) { { "y", NSEC_PER_YEAR }, { "usec", NSEC_PER_USEC }, { "us", NSEC_PER_USEC }, + { "µs", NSEC_PER_USEC }, { "nsec", 1ULL }, { "ns", 1ULL }, { "", 1ULL }, /* default is nsec */ 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..5789e2aa82 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -287,14 +287,24 @@ static int lookup_block_device(const char *p, dev_t *dev) { static int whitelist_device(const char *path, const char *node, const char *acc) { char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4]; struct stat st; + bool ignore_notfound; int r; assert(path); assert(acc); + if (node[0] == '-') { + /* Non-existent paths starting with "-" must be silently ignored */ + node++; + ignore_notfound = true; + } else + ignore_notfound = false; + if (stat(node, &st) < 0) { - log_warning("Couldn't stat device %s", node); - return -errno; + if (errno == ENOENT && ignore_notfound) + 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 +924,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..e345552f24 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; @@ -418,7 +418,7 @@ static int device_process_new(Manager *m, struct udev_device *dev) { * aliases */ alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"); for (;;) { - _cleanup_free_ char *word = NULL, *k = NULL; + _cleanup_free_ char *word = NULL; r = extract_first_word(&alias, &word, NULL, EXTRACT_QUOTES); if (r == 0) 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..61246d831d 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; @@ -1695,7 +1714,7 @@ static void service_enter_running(Service *s, ServiceResult f) { } } else if (f != SERVICE_SUCCESS) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); else if (s->remain_after_exit) service_set_state(s, SERVICE_EXITED); else @@ -1832,7 +1851,7 @@ static void service_enter_start(Service *s) { fail: log_unit_warning_errno(UNIT(s), r, "Failed to run 'start' task: %m"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); } static void service_enter_start_pre(Service *s) { @@ -1978,9 +1997,7 @@ static void service_run_next_control(Service *s) { fail: log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m"); - if (s->state == SERVICE_START_PRE) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - else if (s->state == SERVICE_STOP) + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_STOP)) service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); else if (s->state == SERVICE_STOP_POST) service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); @@ -2579,17 +2596,22 @@ 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) { + service_enter_stop_post(s, SERVICE_FAILURE_PROTOCOL); + 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."); service_unwatch_pid_file(s); if (s->state == SERVICE_START) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL); + service_enter_stop_post(s, SERVICE_FAILURE_PROTOCOL); else service_enter_stop(s, SERVICE_FAILURE_PROTOCOL); } @@ -2723,17 +2745,17 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (f == SERVICE_SUCCESS) service_enter_start_post(s); else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); break; } else if (s->type == SERVICE_NOTIFY) { /* Only enter running through a notification, so that the * SERVICE_START state signifies that no ready notification * has been received */ if (f != SERVICE_SUCCESS) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); else if (!s->remain_after_exit) /* The service has never been active */ - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL); break; } @@ -2813,7 +2835,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (f == SERVICE_SUCCESS) service_enter_start(s); else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); break; case SERVICE_START: @@ -2822,7 +2844,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { break; if (f != SERVICE_SUCCESS) { - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); break; } @@ -2839,7 +2861,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (!has_start_post && r < 0) { r = service_demand_pid_file(s); if (r < 0 || !cgroup_good(s)) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_PROTOCOL); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_PROTOCOL); break; } } else @@ -2935,7 +2957,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us case SERVICE_START_PRE: case SERVICE_START: log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); break; case SERVICE_START_POST: @@ -3056,7 +3078,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 +3099,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 +3416,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..ff9cfaeb88 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; @@ -78,6 +79,8 @@ typedef enum NotifyState { _NOTIFY_STATE_INVALID = -1 } NotifyState; +/* The values of this enum are referenced in man/systemd.exec.xml and src/shared/bus-unit-util.c. + * Update those sources for each change to this enum. */ typedef enum ServiceResult { SERVICE_SUCCESS, SERVICE_FAILURE_RESOURCES, /* a bit of a misnomer, just our catch-all error for errnos we didn't expect */ 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/journal/journald-server.c b/src/journal/journald-server.c index 28487a9451..5c6941ebd6 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -144,7 +144,7 @@ static int cache_space_refresh(Server *s, JournalStorage *storage) { ts = now(CLOCK_MONOTONIC); - if (space->timestamp + RECHECK_SPACE_USEC > ts) + if (space->timestamp != 0 && space->timestamp + RECHECK_SPACE_USEC > ts) return 0; r = determine_path_usage(s, storage->path, &vfs_used, &vfs_avail); diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index 5aa8aca426..3fdf02da3e 100644 --- a/src/libsystemd-network/dhcp-internal.h +++ b/src/libsystemd-network/dhcp-internal.h @@ -30,11 +30,11 @@ #include "dhcp-protocol.h" #include "socket-util.h" -int dhcp_network_bind_raw_socket(int index, union sockaddr_union *link, +int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid, const uint8_t *mac_addr, size_t mac_addr_len, uint16_t arp_type, uint16_t port); -int dhcp_network_bind_udp_socket(be32_t address, uint16_t port); +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port); int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len); int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c index 3c85bb0b54..65405dcce0 100644 --- a/src/libsystemd-network/dhcp-network.c +++ b/src/libsystemd-network/dhcp-network.c @@ -19,6 +19,7 @@ #include <errno.h> #include <net/ethernet.h> +#include <net/if.h> #include <net/if_arp.h> #include <stdio.h> #include <string.h> @@ -156,13 +157,14 @@ int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, bcast_addr, ð_mac, arp_type, dhcp_hlen, port); } -int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) { +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) { union sockaddr_union src = { .in.sin_family = AF_INET, .in.sin_port = htobe16(port), .in.sin_addr.s_addr = address, }; _cleanup_close_ int s = -1; + char ifname[IF_NAMESIZE] = ""; int r, on = 1, tos = IPTOS_CLASS_CS6; s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); @@ -177,6 +179,15 @@ int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) { if (r < 0) return -errno; + if (ifindex > 0) { + if (if_indextoname(ifindex, ifname) == 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); + if (r < 0) + return -errno; + } + if (address == INADDR_ANY) { r = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); if (r < 0) @@ -185,6 +196,7 @@ int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) { r = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); if (r < 0) return -errno; + } else { r = setsockopt(s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on)); if (r < 0) diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index 9d78b953fc..092a1eabb0 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -86,6 +86,28 @@ int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result return 0; } +static bool net_condition_test_strv(char * const *raw_patterns, + const char *string) { + if (strv_isempty(raw_patterns)) + return true; + + /* If the patterns begin with "!", edit it out and negate the test. */ + if (raw_patterns[0][0] == '!') { + char **patterns; + unsigned i, length; + + length = strv_length(raw_patterns) + 1; /* Include the NULL. */ + patterns = newa(char*, length); + patterns[0] = raw_patterns[0] + 1; /* Skip the "!". */ + for (i = 1; i < length; i++) + patterns[i] = raw_patterns[i]; + + return !string || !strv_fnmatch(patterns, string, 0); + } + + return string && strv_fnmatch(raw_patterns, string, 0); +} + bool net_match_config(const struct ether_addr *match_mac, char * const *match_paths, char * const *match_drivers, @@ -117,20 +139,16 @@ bool net_match_config(const struct ether_addr *match_mac, if (match_mac && (!dev_mac || memcmp(match_mac, dev_mac, ETH_ALEN))) return false; - if (!strv_isempty(match_paths) && - (!dev_path || !strv_fnmatch(match_paths, dev_path, 0))) + if (!net_condition_test_strv(match_paths, dev_path)) return false; - if (!strv_isempty(match_drivers) && - (!dev_driver || !strv_fnmatch(match_drivers, dev_driver, 0))) + if (!net_condition_test_strv(match_drivers, dev_driver)) return false; - if (!strv_isempty(match_types) && - (!dev_type || !strv_fnmatch_or_empty(match_types, dev_type, 0))) + if (!net_condition_test_strv(match_types, dev_type)) return false; - if (!strv_isempty(match_names) && - (!dev_name || !strv_fnmatch_or_empty(match_names, dev_name, 0))) + if (!net_condition_test_strv(match_names, dev_name)) return false; return true; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 1423264806..b4bf75a3dc 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -1546,7 +1546,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i goto error; } - r = dhcp_network_bind_udp_socket(client->lease->address, client->port); + r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port); if (r < 0) { log_dhcp_client(client, "could not bind UDP socket"); goto error; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index f16314a37f..0e57ab6b69 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -1022,7 +1022,7 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { } server->fd_raw = r; - r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER); + r = dhcp_network_bind_udp_socket(server->ifindex, INADDR_ANY, DHCP_PORT_SERVER); if (r < 0) { sd_dhcp_server_stop(server); return r; diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index c10ca74b86..f5f1284e6d 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -203,7 +203,7 @@ int dhcp_network_bind_raw_socket( return test_fd[0]; } -int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) { +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port) { int fd; fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0); 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/network/netdev/vxlan.c b/src/network/netdev/vxlan.c index 10c892b044..231f5cb442 100644 --- a/src/network/netdev/vxlan.c +++ b/src/network/netdev/vxlan.c @@ -252,8 +252,8 @@ int config_parse_destination_port(const char *unit, assert(rvalue); assert(data); - r = safe_atou16(rvalue, &port); - if (r < 0 || port <= 0) { + r = parse_ip_port(rvalue, &port); + if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VXLAN destination port '%s'.", rvalue); return 0; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index cb7df633b7..dd0e33a1ce 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -686,18 +686,18 @@ static Address* link_find_dhcp_server_address(Link *link) { return NULL; } -static int link_enter_configured(Link *link) { +static void link_enter_configured(Link *link) { assert(link); assert(link->network); - assert(link->state == LINK_STATE_SETTING_ROUTES); + + if (link->state != LINK_STATE_SETTING_ROUTES) + return; log_link_info(link, "Configured"); link_set_state(link, LINK_STATE_CONFIGURED); link_dirty(link); - - return 0; } void link_check_ready(Link *link) { @@ -2523,6 +2523,9 @@ static int link_initialized_and_synced(sd_netlink *rtnl, sd_netlink_message *m, if (r == -ENOENT) { link_enter_unmanaged(link); return 1; + } else if (r == 0 && network->unmanaged) { + link_enter_unmanaged(link); + return 0; } else if (r < 0) return r; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 463f4595c1..c9b9044a14 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -29,6 +29,7 @@ Match.Architecture, config_parse_net_condition, Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) Link.ARP, config_parse_tristate, 0, offsetof(Network, arp) +Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_netdev, 0, offsetof(Network, bridge) Network.Bond, config_parse_netdev, 0, offsetof(Network, bond) @@ -100,7 +101,7 @@ DHCP.RouteMetric, config_parse_unsigned, DHCP.RouteTable, config_parse_dhcp_route_table, 0, offsetof(Network, dhcp_route_table) DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCP.IAID, config_parse_iaid, 0, offsetof(Network, iaid) -DHCP.ListenPort, config_parse_uint32, 0, offsetof(Network, dhcp_client_port) +DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) IPv6AcceptRA.RouteTable, config_parse_dhcp_route_table, 0, offsetof(Network, ipv6_accept_ra_route_table) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 4dbc19fc3b..d13e306add 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -114,7 +114,7 @@ struct Network { char *dhcp_hostname; unsigned dhcp_route_metric; uint32_t dhcp_route_table; - uint32_t dhcp_client_port; + uint16_t dhcp_client_port; bool dhcp_send_hostname; bool dhcp_broadcast; bool dhcp_critical; @@ -176,6 +176,7 @@ struct Network { struct ether_addr *mac; size_t mtu; int arp; + bool unmanaged; uint32_t iaid; DUID duid; diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c index 86124b8779..bcaf0aaeaa 100644 --- a/src/nspawn/nspawn-expose-ports.c +++ b/src/nspawn/nspawn-expose-ports.c @@ -58,17 +58,17 @@ int expose_port_parse(ExposePort **l, const char *s) { memcpy(v, e, split - e); v[split - e] = 0; - r = safe_atou16(v, &host_port); - if (r < 0 || host_port <= 0) + r = parse_ip_port(v, &host_port); + if (r < 0) return -EINVAL; - r = safe_atou16(split + 1, &container_port); + r = parse_ip_port(split + 1, &container_port); } else { - r = safe_atou16(e, &container_port); + r = parse_ip_port(e, &container_port); host_port = container_port; } - if (r < 0 || container_port <= 0) + if (r < 0) return -EINVAL; LIST_FOREACH(ports, p, *l) 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..aaa64a7ba8 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, @@ -377,9 +550,9 @@ int mount_all(const char *dest, { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */ { "/proc/sysrq-trigger", "/proc/sysrq-trigger", NULL, NULL, MS_BIND, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* Bind mount first ...*/ { NULL, "/proc/sysrq-trigger", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, MOUNT_IN_USERNS|MOUNT_APPLY_APIVFS_RO }, /* ... then, make it r/o */ - { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, MOUNT_FATAL|MOUNT_IN_USERNS }, /* outer child mounts */ + { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, MOUNT_FATAL }, { "tmpfs", "/sys", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS }, { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO }, /* skipped if above was mounted */ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL }, /* skipped if above was mounted */ @@ -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..cb09392729 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; } @@ -1357,6 +1299,8 @@ static int setup_resolv_conf(const char *dest) { * advantage that the container will be able to follow the host's DNS server configuration changes * transparently. */ + (void) touch(where); + r = mount_verbose(LOG_WARNING, "/usr/lib/systemd/resolv.conf", where, NULL, MS_BIND, NULL); if (r >= 0) return mount_verbose(LOG_ERR, NULL, where, NULL, @@ -1666,7 +1610,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 +1618,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 +2600,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 +3620,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 +4089,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 +4137,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 +4152,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 +4197,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 +4272,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/resolve/resolve-tool.c b/src/resolve/resolve-tool.c index 9d4d04220c..07d9582ccb 100644 --- a/src/resolve/resolve-tool.c +++ b/src/resolve/resolve-tool.c @@ -881,8 +881,8 @@ static int resolve_tlsa(sd_bus *bus, const char *address) { port = strrchr(address, ':'); if (port) { - r = safe_atou16(port + 1, &port_num); - if (r < 0 || port_num == 0) + r = parse_ip_port(port + 1, &port_num); + if (r < 0) return log_error_errno(r, "Invalid port \"%s\".", port + 1); address = strndupa(address, port - address); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 8d5a5c6b79..74603f9311 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -112,7 +112,7 @@ int main(int argc, char *argv[]) { sd_event_get_exit_code(m->event, &r); finish: - /* systemd-nspawn checks for private resov.conf to decide whether + /* systemd-nspawn checks for private resolv.conf to decide whether or not to mount it into the container. So just delete it. */ (void) unlink(PRIVATE_RESOLV_CONF); 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-calendarspec.c b/src/test/test-calendarspec.c index b3d1160ea7..b8320b081b 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -186,6 +186,9 @@ int main(int argc, char* argv[]) { test_one("Monday *-*-*", "Mon *-*-* 00:00:00"); test_one("*-*-*", "*-*-* 00:00:00"); test_one("*:*:*", "*-*-* *:*:*"); + test_one("*:*", "*-*-* *:*:00"); + test_one("12:*", "*-*-* 12:*:00"); + test_one("*:30", "*-*-* *:30:00"); test_next("2016-03-27 03:17:00", "", 12345, 1459048620000000); test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000); 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/test/test-time.c b/src/test/test-time.c index 7078a0374d..8259491813 100644 --- a/src/test/test-time.c +++ b/src/test/test-time.c @@ -42,6 +42,10 @@ static void test_parse_sec(void) { assert_se(u == 2500 * USEC_PER_MSEC); assert_se(parse_sec(".7", &u) >= 0); assert_se(u == 700 * USEC_PER_MSEC); + assert_se(parse_sec("23us", &u) >= 0); + assert_se(u == 23); + assert_se(parse_sec("23µs", &u) >= 0); + assert_se(u == 23); assert_se(parse_sec("infinity", &u) >= 0); assert_se(u == USEC_INFINITY); assert_se(parse_sec(" infinity ", &u) >= 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/test/TEST-13-NSPAWN-SMOKE/test.sh b/test/TEST-13-NSPAWN-SMOKE/test.sh index 305866ae38..b8b8ec34bd 100755 --- a/test/TEST-13-NSPAWN-SMOKE/test.sh +++ b/test/TEST-13-NSPAWN-SMOKE/test.sh @@ -83,6 +83,14 @@ if unshare -U sh -c :; then is_user_ns_supported=yes fi +function check_bind_tmp_path { + # https://github.com/systemd/systemd/issues/4789 + local _root="/var/lib/machines/bind-tmp-path" + /create-busybox-container "$_root" + >/tmp/bind + systemd-nspawn --register=no -D "$_root" --bind=/tmp/bind /bin/sh -c 'test -e /tmp/bind' +} + function run { if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2 @@ -113,6 +121,8 @@ function run { return 0 } +check_bind_tmp_path + for api_vfs_writable in yes no network; do run no no $api_vfs_writable run yes no $api_vfs_writable diff --git a/test/networkd-test.py b/test/networkd-test.py index f8914a7895..39bd4f5b1b 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -30,6 +30,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see <http://www.gnu.org/licenses/>. +import errno import os import sys import time @@ -39,16 +40,109 @@ import subprocess import shutil import socket -networkd_active = subprocess.call(['systemctl', 'is-active', '--quiet', - 'systemd-networkd']) == 0 -have_dnsmasq = shutil.which('dnsmasq') +HAVE_DNSMASQ = shutil.which('dnsmasq') is not None + +NETWORK_UNITDIR = '/run/systemd/network' + +NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', + path='/usr/lib/systemd:/lib/systemd') RESOLV_CONF = '/run/systemd/resolve/resolv.conf' -@unittest.skipIf(networkd_active, - 'networkd is already active') -class ClientTestBase: +def setUpModule(): + """Initialize the environment, and perform sanity checks on it.""" + if NETWORKD_WAIT_ONLINE is None: + raise OSError(errno.ENOENT, 'systemd-networkd-wait-online not found') + + # Do not run any tests if the system is using networkd already. + if subprocess.call(['systemctl', 'is-active', '--quiet', + 'systemd-networkd.service']) == 0: + raise unittest.SkipTest('networkd is already active') + + # Avoid "Failed to open /dev/tty" errors in containers. + os.environ['SYSTEMD_LOG_TARGET'] = 'journal' + + # Ensure the unit directory exists so tests can dump files into it. + os.makedirs(NETWORK_UNITDIR, exist_ok=True) + + +class NetworkdTestingUtilities: + """Provide a set of utility functions to facilitate networkd tests. + + This class must be inherited along with unittest.TestCase to define + some required methods. + """ + + def add_veth_pair(self, veth, peer, veth_options=(), peer_options=()): + """Add a veth interface pair, and queue them to be removed.""" + subprocess.check_call(['ip', 'link', 'add', 'name', veth] + + list(veth_options) + + ['type', 'veth', 'peer', 'name', peer] + + list(peer_options)) + self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', peer]) + + def write_network(self, unit_name, contents): + """Write a network unit file, and queue it to be removed.""" + unit_path = os.path.join(NETWORK_UNITDIR, unit_name) + + with open(unit_path, 'w') as unit: + unit.write(contents) + self.addCleanup(os.remove, unit_path) + + def write_network_dropin(self, unit_name, dropin_name, contents): + """Write a network unit drop-in, and queue it to be removed.""" + dropin_dir = os.path.join(NETWORK_UNITDIR, "%s.d" % unit_name) + dropin_path = os.path.join(dropin_dir, "%s.conf" % dropin_name) + + os.makedirs(dropin_dir, exist_ok=True) + with open(dropin_path, 'w') as dropin: + dropin.write(contents) + self.addCleanup(os.remove, dropin_path) + + def assert_link_states(self, **kwargs): + """Match networkctl link states to the given ones. + + Each keyword argument should be the name of a network interface + with its expected value of the "SETUP" column in output from + networkctl. The interfaces have five seconds to come online + before the check is performed. Every specified interface must + be present in the output, and any other interfaces found in the + output are ignored. + + A special interface state "managed" is supported, which matches + any value in the "SETUP" column other than "unmanaged". + """ + if not kwargs: + return + interfaces = set(kwargs) + + # Wait for the requested interfaces, but don't fail for them. + subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + + ['--interface=%s' % iface for iface in kwargs]) + + # Validate each link state found in the networkctl output. + out = subprocess.check_output(['networkctl', '--no-legend']).rstrip() + for line in out.decode('utf-8').split('\n'): + fields = line.split() + if len(fields) >= 5 and fields[1] in kwargs: + iface = fields[1] + expected = kwargs[iface] + actual = fields[-1] + if (actual != expected and + not (expected == 'managed' and actual != 'unmanaged')): + self.fail("Link %s expects state %s, found %s" % + (iface, expected, actual)) + interfaces.remove(iface) + + # Ensure that all requested interfaces have been covered. + if interfaces: + self.fail("Missing links in status output: %s" % interfaces) + + +class ClientTestBase(NetworkdTestingUtilities): + """Provide common methods for testing networkd against servers.""" + @classmethod def setUpClass(klass): klass.orig_log_level = subprocess.check_output( @@ -65,19 +159,7 @@ class ClientTestBase: self.if_router = 'router_eth42' self.workdir_obj = tempfile.TemporaryDirectory() self.workdir = self.workdir_obj.name - self.config = '/run/systemd/network/test_eth42.network' - - # avoid "Failed to open /dev/tty" errors in containers - os.environ['SYSTEMD_LOG_TARGET'] = 'journal' - - # determine path to systemd-networkd-wait-online - for p in ['/usr/lib/systemd/systemd-networkd-wait-online', - '/lib/systemd/systemd-networkd-wait-online']: - if os.path.exists(p): - self.networkd_wait_online = p - break - else: - self.fail('systemd-networkd-wait-online not found') + self.config = 'test_eth42.network' # get current journal cursor subprocess.check_output(['journalctl', '--sync']) @@ -93,12 +175,6 @@ class ClientTestBase: subprocess.call(['ip', 'link', 'del', 'dummy0'], stderr=subprocess.DEVNULL) - def writeConfig(self, fname, contents): - os.makedirs(os.path.dirname(fname), exist_ok=True) - with open(fname, 'w') as f: - f.write(contents) - self.addCleanup(os.remove, fname) - def show_journal(self, unit): '''Show journal of given unit since start of the test''' @@ -126,7 +202,7 @@ class ClientTestBase: def do_test(self, coldplug=True, ipv6=False, extra_opts='', online_timeout=10, dhcp_mode='yes'): subprocess.check_call(['systemctl', 'start', 'systemd-resolved']) - self.writeConfig(self.config, '''\ + self.write_network(self.config, '''\ [Match] Name=%s [Network] @@ -146,14 +222,14 @@ DHCP=%s subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) try: - subprocess.check_call([self.networkd_wait_online, '--interface', + subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface, '--timeout=%i' % online_timeout]) if ipv6: # 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 @@ -166,33 +242,33 @@ 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(self.config) as f: + with open(os.path.join(NETWORK_UNITDIR, self.config)) as f: print('\n---- %s ----\n%s' % (self.config, f.read())) print('---- interface status ----') sys.stdout.flush() @@ -247,12 +323,12 @@ DHCP=%s self.do_test(coldplug=False, ipv6=True) def test_route_only_dns(self): - self.writeConfig('/run/systemd/network/myvpn.netdev', '''\ + self.write_network('myvpn.netdev', '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc''') - self.writeConfig('/run/systemd/network/myvpn.network', '''\ + self.write_network('myvpn.network', '''\ [Match] Name=dummy0 [Network] @@ -273,20 +349,16 @@ Domains= ~company''') self.assertNotIn('nameserver 192.168.42.1\n', contents) def test_route_only_dns_all_domains(self): - with open('/run/systemd/network/myvpn.netdev', 'w') as f: - f.write('''[NetDev] + self.write_network('myvpn.netdev', '''[NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc''') - with open('/run/systemd/network/myvpn.network', 'w') as f: - f.write('''[Match] + self.write_network('myvpn.network', '''[Match] Name=dummy0 [Network] Address=192.168.42.100 DNS=192.168.42.1 Domains= ~company ~.''') - self.addCleanup(os.remove, '/run/systemd/network/myvpn.netdev') - self.addCleanup(os.remove, '/run/systemd/network/myvpn.network') self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRouterAdvertisements=False') @@ -303,7 +375,7 @@ Domains= ~company ~.''') self.assertIn('nameserver 192.168.42.1\n', contents) -@unittest.skipUnless(have_dnsmasq, 'dnsmasq not installed') +@unittest.skipUnless(HAVE_DNSMASQ, 'dnsmasq not installed') class DnsmasqClientTest(ClientTestBase, unittest.TestCase): '''Test networkd client against dnsmasq''' @@ -366,7 +438,7 @@ class DnsmasqClientTest(ClientTestBase, unittest.TestCase): # create interface for generic connections; this will map all DNS names # to 192.168.42.1 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1']) - self.writeConfig('/run/systemd/network/general.network', '''\ + self.write_network('general.network', '''\ [Match] Name=%s [Network] @@ -375,9 +447,7 @@ IPv6AcceptRA=False''' % self.iface) # create second device/dnsmasq for a .company/.lab VPN interface # static IPs for simplicity - subprocess.check_call(['ip', 'link', 'add', 'name', 'testvpnclient', 'type', - 'veth', 'peer', 'name', 'testvpnrouter']) - self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', 'testvpnrouter']) + self.add_veth_pair('testvpnclient', 'testvpnrouter') subprocess.check_call(['ip', 'a', 'flush', 'dev', 'testvpnrouter']) subprocess.check_call(['ip', 'a', 'add', '10.241.3.1/24', 'dev', 'testvpnrouter']) subprocess.check_call(['ip', 'link', 'set', 'testvpnrouter', 'up']) @@ -392,7 +462,7 @@ IPv6AcceptRA=False''' % self.iface) self.addCleanup(vpn_dnsmasq.wait) self.addCleanup(vpn_dnsmasq.kill) - self.writeConfig('/run/systemd/network/vpn.network', '''\ + self.write_network('vpn.network', '''\ [Match] Name=testvpnclient [Network] @@ -402,7 +472,7 @@ DNS=10.241.3.1 Domains= ~company ~lab''') subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) - subprocess.check_call([self.networkd_wait_online, '--interface', self.iface, + subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface, '--interface=testvpnclient', '--timeout=20']) # ensure we start fresh with every test @@ -452,8 +522,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: @@ -549,7 +628,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 @@ -583,12 +662,12 @@ exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=// # we don't use this interface for this test self.if_router = None - self.writeConfig('/run/systemd/network/test.netdev', '''\ + self.write_network('test.netdev', '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc''') - self.writeConfig('/run/systemd/network/test.network', '''\ + self.write_network('test.network', '''\ [Match] Name=dummy0 [Network] @@ -615,12 +694,12 @@ Domains= one two three four five six seven eight nine ten''') name_prefix = 'a' * 60 - self.writeConfig('/run/systemd/network/test.netdev', '''\ + self.write_network('test.netdev', '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc''') - self.writeConfig('/run/systemd/network/test.network', '''\ + self.write_network('test.network', '''\ [Match] Name=dummy0 [Network] @@ -643,18 +722,18 @@ Domains={p}0 {p}1 {p}2 {p}3 {p}4'''.format(p=name_prefix)) # we don't use this interface for this test self.if_router = None - self.writeConfig('/run/systemd/network/test.netdev', '''\ + self.write_network('test.netdev', '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc''') - self.writeConfig('/run/systemd/network/test.network', '''\ + self.write_network('test.network', '''\ [Match] Name=dummy0 [Network] Address=192.168.42.100 DNS=192.168.42.1''') - self.writeConfig('/run/systemd/network/test.network.d/dns.conf', '''\ + self.write_network_dropin('test.network', 'dns', '''\ [Network] DNS=127.0.0.1''') @@ -695,6 +774,115 @@ DNS=127.0.0.1''') raise +class MatchClientTest(unittest.TestCase, NetworkdTestingUtilities): + """Test [Match] sections in .network files. + + Be aware that matching the test host's interfaces will wipe their + configuration, so as a precaution, all network files should have a + restrictive [Match] section to only ever interfere with the + temporary veth interfaces created here. + """ + + def tearDown(self): + """Stop networkd.""" + subprocess.call(['systemctl', 'stop', 'systemd-networkd']) + + def test_basic_matching(self): + """Verify the Name= line works throughout this class.""" + self.add_veth_pair('test_if1', 'fake_if2') + self.write_network('test.network', "[Match]\nName=test_*\n[Network]") + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.assert_link_states(test_if1='managed', fake_if2='unmanaged') + + def test_inverted_matching(self): + """Verify that a '!'-prefixed value inverts the match.""" + # Use a MAC address as the interfaces' common matching attribute + # to avoid depending on udev, to support testing in containers. + mac = '00:01:02:03:98:99' + self.add_veth_pair('test_veth', 'test_peer', + ['addr', mac], ['addr', mac]) + self.write_network('no-veth.network', """\ +[Match] +MACAddress=%s +Name=!nonexistent *peer* +[Network]""" % mac) + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.assert_link_states(test_veth='managed', test_peer='unmanaged') + + +class UnmanagedClientTest(unittest.TestCase, NetworkdTestingUtilities): + """Test if networkd manages the correct interfaces.""" + + def setUp(self): + """Write .network files to match the named veth devices.""" + # Define the veth+peer pairs to be created. + # Their pairing doesn't actually matter, only their names do. + self.veths = { + 'm1def': 'm0unm', + 'm1man': 'm1unm', + } + + # Define the contents of .network files to be read in order. + self.configs = ( + "[Match]\nName=m1def\n", + "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n", + "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n", + ) + + # Write out the .network files to be cleaned up automatically. + for i, config in enumerate(self.configs): + self.write_network("%02d-test.network" % i, config) + + def tearDown(self): + """Stop networkd.""" + subprocess.call(['systemctl', 'stop', 'systemd-networkd']) + + def create_iface(self): + """Create temporary veth pairs for interface matching.""" + for veth, peer in self.veths.items(): + self.add_veth_pair(veth, peer) + + def test_unmanaged_setting(self): + """Verify link states with Unmanaged= settings, hot-plug.""" + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.create_iface() + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='unmanaged') + + def test_unmanaged_setting_coldplug(self): + """Verify link states with Unmanaged= settings, cold-plug.""" + self.create_iface() + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='unmanaged') + + def test_catchall_config(self): + """Verify link states with a catch-all config, hot-plug.""" + # Don't actually catch ALL interfaces. It messes up the host. + self.write_network('all.network', "[Match]\nName=m[01]???\n") + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.create_iface() + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='managed') + + def test_catchall_config_coldplug(self): + """Verify link states with a catch-all config, cold-plug.""" + # Don't actually catch ALL interfaces. It messes up the host. + self.write_network('all.network', "[Match]\nName=m[01]???\n") + self.create_iface() + subprocess.check_call(['systemctl', 'start', 'systemd-networkd']) + self.assert_link_states(m1def='managed', + m1man='managed', + m1unm='unmanaged', + m0unm='managed') + + if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) 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): |