* expose IO accounting data on the bus, show it in systemd-run --wait and log
about it in the resource log message
-* add "systemctl purge" for flushing out configuration, state, logs, ... of a
- unit when it is stopped
-
* show whether a service has out-of-date configuration in "systemctl status" by
using mtime data of ConfigurationDirectory=.
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--what=</option></term>
+
+ <listitem>
+ <para>Select what type of per-unit resources to remove when the <command>clean</command> command is
+ invoked, see below. Takes one of <constant>configuration</constant>, <constant>state</constant>,
+ <constant>cache</constant>, <constant>logs</constant>, <constant>runtime</constant> to select the
+ type of resource. This option may be specified more than once, in which case all specified resource
+ types are removed. Also accepts the special value <constant>all</constant> as a shortcut for
+ specifiying all five resource types. If this option is not specified defaults to the combination of
+ <constant>cache</constant> and <constant>runtime</constant>, i.e. the two kinds of resources that
+ are generally considered to be redundant and can be reconstructed on next invocation.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-f</option></term>
<term><option>--force</option></term>
the signal to send.</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><command>clean <replaceable>PATTERN</replaceable>…</command></term>
+
+ <listitem>
+ <para>Remove the configuration, state, cache, logs or runtime data of the specified units. Use
+ <option>--what=</option> to select which kind of resource to remove. For service units this may
+ be used to remove the directories configured with <varname>ConfigurationDirectory=</varname>,
+ <varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>,
+ <varname>LogsDirectory=</varname> and <varname>RuntimeDirectory=</varname>, see
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ for details. For timer units this may be used to clear out the persistent timestamp data if
+ <varname>Persistent=</varname> is used and <option>--what=state</option> is selected, see
+ <citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>. This
+ command only applies to units that use either of these settings. If <option>--what=</option> is
+ not specified, both the cache and runtime data are removed (as these two types of data are
+ generally redundant and reproducible on the next invocation of the unit).</para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term><command>is-active <replaceable>PATTERN</replaceable>…</command></term>
the directories is tied directly to the lifetime of the unit, and it is not necessary to ensure that the
<filename>tmpfiles.d</filename> configuration is executed before the unit is started.</para>
+ <para>To remove any of the directories created by these settings, use the <command>systemctl clean
+ …</command> command on the relevant units, see
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ details.</para>
+
<para>Example: if a system service unit has the following,
<programlisting>RuntimeDirectory=foo/bar baz</programlisting>
the service manager creates <filename>/run/foo</filename> (if it does not exist),
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>AssignToLoopback=</varname></term>
+ <listitem>
+ <para>Takes a boolean. If set to <literal>yes</literal>, the loopback interface <literal>lo</literal>
+ is used as the underlying device of the tunnel interface. Defaults to <literal>no</literal>.</para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term><varname>AllowLocalRemote=</varname></term>
<listitem>
specified through DHCP is not used for name resolution.
See option <option>UseDomains=</option> below.</para>
- <para>See the <literal>[DHCP]</literal> section below for further configuration options for the DHCP client
- support.</para>
+ <para>See the <literal>[DHCPv4]</literal> or <literal>[DHCPv6]</literal> section below for
+ further configuration options for the DHCP client support.</para>
</listitem>
</varlistentry>
<varlistentry>
</listitem>
</varlistentry>
<varlistentry>
- <term><varname>MACAddress=</varname></term>
+ <term><varname>LinkLayerAddress=</varname></term>
<listitem>
- <para>The hardware address of the neighbor.</para>
+ <para>The link layer address (MAC address or IP address) of the neighbor.</para>
</listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><varname>Table=</varname></term>
<listitem>
- <para>Specifies the routing table identifier to lookup if the rule
- selector matches. The table identifier for a route (a number between 1 and 4294967295).</para>
+ <para>Specifies the routing table identifier to lookup if the rule selector matches. Takes
+ one of <literal>default</literal>, <literal>main</literal>, and <literal>local</literal>,
+ or a number between 1 and 4294967295. Defaults to <literal>main</literal>.</para>
</listitem>
</varlistentry>
<varlistentry>
<varlistentry>
<term><varname>Type=</varname></term>
<listitem>
- <para>Specifies the type for the route. If <literal>unicast</literal>, a regular route is defined, i.e. a
+ <para>Specifies the type for the route. Takes one of <literal>unicast</literal>,
+ <literal>local</literal>, <literal>broadcast</literal>, <literal>anycast</literal>,
+ <literal>multicast</literal>, <literal>blackhole</literal>, <literal>unreachable</literal>,
+ <literal>prohibit</literal>, <literal>throw</literal>, <literal>nat</literal>, and
+ <literal>xresolve</literal>. If <literal>unicast</literal>, a regular route is defined, i.e. a
route indicating the path to take to a destination network address. If <literal>blackhole</literal>, packets
to the defined route are discarded silently. If <literal>unreachable</literal>, packets to the defined route
are discarded and the ICMP message "Host Unreachable" is generated. If <literal>prohibit</literal>, packets
</refsect1>
<refsect1>
- <title>[DHCP] Section Options</title>
- <para>The <literal>[DHCP]</literal> section configures the
- DHCPv4 and DHCP6 client, if it is enabled with the
+ <title>[DHCPv4] Section Options</title>
+ <para>The <literal>[DHCPv4]</literal> section configures the
+ DHCPv4 client, if it is enabled with the
<varname>DHCP=</varname> setting described above:</para>
<variablelist class='network-directives'>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>BlackList=</varname></term>
+ <listitem>
+ <para>A whitespace-separated list of IPv4 addresses. DHCP offers from servers in the list are rejected.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>[DHCPv6] Section Options</title>
+ <para>The <literal>[DHCPv6]</literal> section configures the DHCPv6 client, if it is enabled with the
+ <varname>DHCP=</varname> setting described above, or invoked by the IPv6 Router Advertisement:</para>
+
+ <variablelist class='network-directives'>
+ <varlistentry>
+ <term><varname>UseDNS=</varname></term>
+ <term><varname>UseNTP=</varname></term>
+ <listitem>
+ <para>As in the <literal>[DHCP]</literal> section.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>RapidCommit=</varname></term>
<listitem>
</listitem>
</varlistentry>
- <varlistentry>
- <term><varname>BlackList=</varname></term>
- <listitem>
- <para>A whitespace-separated list of IPv4 addresses. DHCP offers from servers in the list are rejected.</para>
- </listitem>
- </varlistentry>
-
</variablelist>
- </refsect1>
+ </refsect1>
<refsect1>
<title>[IPv6AcceptRA] Section Options</title>
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>TimeoutCleanSec=</varname></term>
+ <listitem><para>Configures a timeout on the clean-up operation requested through <command>systemctl
+ clean …</command>, see
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ details. Takes the usual time values and defaults to <constant>infinity</constant>, i.e. by default
+ no time-out is applied. If a time-out is configured the clean operation will be aborted forcibly when
+ the time-out is reached, potentially leaving resources on disk.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>RuntimeMaxSec=</varname></term>
<varlistentry>
<term><varname>Persistent=</varname></term>
- <listitem><para>Takes a boolean argument. If true, the time
- when the service unit was last triggered is stored on disk.
- When the timer is activated, the service unit is triggered
- immediately if it would have been triggered at least once
- during the time when the timer was inactive. This is useful to
- catch up on missed runs of the service when the machine was
- off. Note that this setting only has an effect on timers
- configured with <varname>OnCalendar=</varname>. Defaults
- to <varname>false</varname>.
- </para></listitem>
+ <listitem><para>Takes a boolean argument. If true, the time when the service unit was last triggered
+ is stored on disk. When the timer is activated, the service unit is triggered immediately if it
+ would have been triggered at least once during the time when the timer was inactive. This is useful
+ to catch up on missed runs of the service when the system was powered down. Note that this setting
+ only has an effect on timers configured with <varname>OnCalendar=</varname>. Defaults to
+ <varname>false</varname>.</para>
+
+ <para>Use <command>systemctl clean --what=state …</command> on the timer unit to remove the timestamp
+ file maintained by this option from disk. In particular, use this command before uninstalling a timer
+ unit. See
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
+ details.</para></listitem>
</varlistentry>
<varlistentry>
<filename>…</filename>
<filename>/usr/share/user-tmpfiles.d/*.conf</filename>
</literallayout></para>
+
+ <programlisting>#Type Path Mode User Group Age Argument
+f /file/to/create mode user group - content
+F /file/to/create-or-truncate mode user group - content
+w /file/to/write-to - - - - content
+d /directory/to/create-and-cleanup mode user group cleanup-age -
+D /directory/to/create-and-remove mode user group cleanup-age -
+e /directory/to/cleanup mode user group cleanup-age -
+v /subvolume/to/create mode user group - -
+v /subvolume-or-directory/to/create mode user group - -
+Q /subvolume/to/create mode user group - -
+p /fifo/to/create mode user group - -
+L /symlink/to/create - - - - symlink/target/path
+c /dev/char-device-to-create mode user group - -
+b /dev/block-device-to-create mode user group - -
+# p+, L+, c+, b+ create target unconditionally
+C /target/to/create - - - - /source/to/copy
+x /path-or-glob/to/ignore - - - - -
+X /path-or-glob/to/ignore/recursively - - - - -
+r /empty/dir/to/remove - - - - -
+R /dir/to/remove/recursively - - - - -
+z /path-or-glob/to/adjust/mode mode user group - MAC context
+Z /path-or-glob/to/adjust/mode/recursively mode user group - MAC context
+t /path-or-glob/to/set/xattrs - - - - xattrs
+T /path-or-glob/to/set/xattrs/recursively - - - - xattrs
+h /path-or-glob/to/set/attrs - - - - file attrs
+H /path-or-glob/to/set/attrs/recursively - - - - file attrs
+a /path-or-glob/to/set/acls - - - - POSIX ACLs
+A /path-or-glob/to/set/acls/recursively - - - - POSIX ACLs
+# a+, A+ append ACLs
+</programlisting>
</refsynopsisdiv>
<refsect1>
_cleanup_free_ char *p;
const char *n;
- p = strappend(*s, "=");
+ p = strjoin(*s, "=");
if (!p)
return log_oom();
if (!names)
return log_oom();
- e = strappend("LISTEN_FDNAMES=", names);
+ e = strjoin("LISTEN_FDNAMES=", names);
if (!e)
return log_oom();
if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0)
return -errno;
- /* The kernel returns an empty name if the
- * subvolume is in the top-level directory,
- * and otherwise appends a slash, so that we
- * can just concatenate easily here, without
- * adding a slash. */
- c = strappend(ino_args.name, p);
+ c = path_join(ino_args.name, p);
if (!c)
return -ENOMEM;
if (feof(f))
return 0;
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
}
if (ul <= 0)
if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) {
if (errno == ENOENT)
r = 0;
- else if (errno > 0)
- r = -errno;
else
- r = -EIO;
+ r = errno_or_else(EIO);
}
if (delete_root) {
}
if (need_prefix)
- return strappend("_", p);
+ return strjoin("_", p);
return strdup(p);
}
if (feof(f))
break;
- if (ferror(f) && errno > 0)
- return -errno;
+ if (ferror(f))
+ return errno_or_else(EIO);
return -EBADMSG;
}
t = strv_env_get_n(env, word+2, e-word-2, flags);
- k = strappend(r, t);
+ k = strjoin(r, t);
if (!k)
return NULL;
else if (!t && state == DEFAULT_VALUE)
t = v = replace_env_n(test_value, e-test_value, env, flags);
- k = strappend(r, t);
+ k = strjoin(r, t);
if (!k)
return NULL;
t = strv_env_get_n(env, word+1, e-word-1, flags);
- k = strappend(r, t);
+ k = strjoin(r, t);
if (!k)
return NULL;
assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
t = strv_env_get_n(env, word+1, e-word-1, flags);
- return strappend(r, t);
+ return strjoin(r, t);
} else
return strnappend(r, word, e-word);
}
return strerror(abs(error));
}
+static inline int errno_or_else(int fallback) {
+ /* To be used when invoking library calls where errno handling is not defined clearly: we return
+ * errno if it is set, and the specified error otherwise. The idea is that the caller initializes
+ * errno to zero before doing an API call, and then uses this helper to retrieve a somewhat useful
+ * error code */
+ if (errno > 0)
+ return -errno;
+
+ return -abs(fallback);
+}
+
/* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5.
*
* Hint #2: The kernel sends e.g., EHOSTUNREACH or ENONET to userspace in some ICMP error cases. See the
errno = 0;
k = fread(buf, 1, l + accept_extra_nl + 1, f);
if (ferror(f))
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
if (k != l && k != l + accept_extra_nl)
return 0;
l += k;
if (ferror(f)) {
- r = errno > 0 ? -errno : -EIO;
+ r = errno_or_else(EIO);
goto finalize;
}
fflush(f);
if (ferror(f))
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
k = fgetc(f);
if (k == EOF) {
if (ferror(f))
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
if (ret)
*ret = 0;
#include <unistd.h>
#include "dirent-util.h"
+#include "errno-util.h"
#include "glob-util.h"
#include "macro.h"
#include "path-util.h"
errno = 0;
k = glob(path, flags | GLOB_ALTDIRFUNC, NULL, pglob);
-
if (k == GLOB_NOMATCH)
return -ENOENT;
if (k == GLOB_NOSPACE)
return -ENOMEM;
if (k != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
if (strv_isempty(pglob->gl_pathv))
return -ENOENT;
#include <stdlib.h>
#include "alloc-util.h"
+#include "errno-util.h"
#include "in-addr-util.h"
#include "macro.h"
#include "parse-util.h"
return -EAFNOSUPPORT;
}
+bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b) {
+ assert(a);
+ assert(b);
+
+ return a->s_addr == b->s_addr;
+}
+
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) {
assert(a);
assert(b);
if (family == AF_INET)
- return a->in.s_addr == b->in.s_addr;
+ return in4_addr_equal(&a->in, &b->in);
if (family == AF_INET6)
return
errno = 0;
if (!inet_ntop(family, u, x, l))
- return errno > 0 ? -errno : -EINVAL;
+ return errno_or_else(EINVAL);
*ret = TAKE_PTR(x);
return 0;
errno = 0;
if (!inet_ntop(family, u, x, l))
- return errno > 0 ? -errno : -EINVAL;
+ return errno_or_else(EINVAL);
p = x + strlen(x);
l -= strlen(x);
errno = 0;
if (!inet_ntop(family, u, x, l))
- return errno > 0 ? -errno : -EINVAL;
+ return errno_or_else(EINVAL);
sprintf(strchr(x, 0), "%%%i", ifindex);
errno = 0;
if (inet_pton(family, s, ret ?: &buffer) <= 0)
- return errno > 0 ? -errno : -EINVAL;
+ return errno_or_else(EINVAL);
return 0;
}
bool in4_addr_is_non_local(const struct in_addr *a);
+bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b);
int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b);
int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen);
int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen);
char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value) {
char *x;
- x = strappend(field, value);
+ x = strjoin(field, value);
if (x)
iovec[(*n_iovec)++] = IOVEC_MAKE_STRING(x);
return x;
_cleanup_free_ char *x = NULL;
int r;
- x = strappend(field, value);
+ x = strjoin(field, value);
if (!x)
return log_oom();
if (!e)
return -ENOMEM;
- g = strappend("sd-", e);
+ g = strjoin("sd-", e);
if (!g)
return -ENOMEM;
if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES|REMOVE_SUBVOLUME))
return -EINVAL;
- /* We refuse to clean the root file system with this
- * call. This is extra paranoia to never cause a really
- * seriously broken system. */
+ /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
+ * really seriously broken system. */
if (path_equal_or_files_same(path, "/", AT_SYMLINK_NOFOLLOW))
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
if (r >= 0)
return r;
+ if (FLAGS_SET(flags, REMOVE_MISSING_OK) && r == -ENOENT)
+ return 0;
+
if (!IN_SET(r, -ENOTTY, -EINVAL, -ENOTDIR))
return r;
fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
if (fd < 0) {
+ if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
+ return 0;
+
if (!IN_SET(errno, ENOTDIR, ELOOP))
return -errno;
- if (!(flags & REMOVE_PHYSICAL)) {
- if (statfs(path, &s) < 0)
- return -errno;
+ if (FLAGS_SET(flags, REMOVE_ONLY_DIRECTORIES))
+ return 0;
- if (is_physical_fs(&s))
- return log_error_errno(SYNTHETIC_ERRNO(EPERM),
- "Attempted to remove files from a disk file system under \"%s\", refusing.",
- path);
- }
+ if (FLAGS_SET(flags, REMOVE_ROOT)) {
+
+ if (!FLAGS_SET(flags, REMOVE_PHYSICAL)) {
+ if (statfs(path, &s) < 0)
+ return -errno;
+
+ if (is_physical_fs(&s))
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Attempted to remove files from a disk file system under \"%s\", refusing.",
+ path);
+ }
+
+ if (unlink(path) < 0) {
+ if (FLAGS_SET(flags, REMOVE_MISSING_OK) && errno == ENOENT)
+ return 0;
- if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES))
- if (unlink(path) < 0 && errno != ENOENT)
return -errno;
+ }
+ }
return 0;
}
r = rm_rf_children(fd, flags, NULL);
- if (flags & REMOVE_ROOT) {
- if (rmdir(path) < 0) {
- if (r == 0 && errno != ENOENT)
- r = -errno;
- }
- }
+ if (FLAGS_SET(flags, REMOVE_ROOT) &&
+ rmdir(path) < 0 &&
+ r >= 0 &&
+ (!FLAGS_SET(flags, REMOVE_MISSING_OK) || errno != ENOENT))
+ r = -errno;
return r;
}
#include "errno-util.h"
typedef enum RemoveFlags {
- REMOVE_ONLY_DIRECTORIES = 1 << 0,
- REMOVE_ROOT = 1 << 1,
- REMOVE_PHYSICAL = 1 << 2, /* if not set, only removes files on tmpfs, never physical file systems */
- REMOVE_SUBVOLUME = 1 << 3,
+ REMOVE_ONLY_DIRECTORIES = 1 << 0, /* Only remove empty directories, no files */
+ REMOVE_ROOT = 1 << 1, /* Remove the specified directory itself too, not just the contents of it */
+ REMOVE_PHYSICAL = 1 << 2, /* If not set, only removes files on tmpfs, never physical file systems */
+ REMOVE_SUBVOLUME = 1 << 3, /* Drop btrfs subvolumes in the tree too */
+ REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
} RemoveFlags;
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
errno = 0;
if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0)
- return errno > 0 ? -errno : -EINVAL;
+ return errno_or_else(EINVAL);
e++;
if (*e != ':')
return r;
}
-char *strappend(const char *s, const char *suffix) {
- return strnappend(s, suffix, strlen_ptr(suffix));
-}
-
char *strjoin_real(const char *x, ...) {
va_list ap;
size_t l;
#define _FOREACH_WORD(word, length, s, separator, flags, state) \
for ((state) = (s), (word) = split(&(state), &(length), (separator), (flags)); (word); (word) = split(&(state), &(length), (separator), (flags)))
-char *strappend(const char *s, const char *suffix);
char *strnappend(const char *s, const char *suffix, size_t length);
char *strjoin_real(const char *x, ...) _sentinel_;
STRV_FOREACH(s, b) {
char *v;
- v = strappend(*s, suffix);
+ v = strjoin(*s, suffix);
if (!v)
return -ENOMEM;
if (!e)
return NULL;
- return strappend("/org/freedesktop/systemd1/unit/", e);
+ return strjoin("/org/freedesktop/systemd1/unit/", e);
}
int unit_name_from_dbus_path(const char *path, char **name) {
[UNIT_INACTIVE] = "inactive",
[UNIT_FAILED] = "failed",
[UNIT_ACTIVATING] = "activating",
- [UNIT_DEACTIVATING] = "deactivating"
+ [UNIT_DEACTIVATING] = "deactivating",
+ [UNIT_MAINTENANCE] = "maintenance",
};
DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState);
[SERVICE_FINAL_SIGKILL] = "final-sigkill",
[SERVICE_FAILED] = "failed",
[SERVICE_AUTO_RESTART] = "auto-restart",
+ [SERVICE_CLEANING] = "cleaning",
};
DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState);
UNIT_FAILED,
UNIT_ACTIVATING,
UNIT_DEACTIVATING,
+ UNIT_MAINTENANCE,
_UNIT_ACTIVE_STATE_MAX,
_UNIT_ACTIVE_STATE_INVALID = -1
} UnitActiveState;
SERVICE_FINAL_SIGKILL,
SERVICE_FAILED,
SERVICE_AUTO_RESTART,
+ SERVICE_CLEANING,
_SERVICE_STATE_MAX,
_SERVICE_STATE_INVALID = -1
} ServiceState;
}
int unit_name_path_unescape(const char *f, char **ret) {
- char *s;
+ _cleanup_free_ char *s = NULL;
int r;
assert(f);
if (!s)
return -ENOMEM;
} else {
- char *w;
+ _cleanup_free_ char *w = NULL;
r = unit_name_unescape(f, &w);
if (r < 0)
return r;
/* Don't accept trailing or leading slashes */
- if (startswith(w, "/") || endswith(w, "/")) {
- free(w);
+ if (startswith(w, "/") || endswith(w, "/"))
return -EINVAL;
- }
/* Prefix a slash again */
- s = strappend("/", w);
- free(w);
+ s = strjoin("/", w);
if (!s)
return -ENOMEM;
- if (!path_is_normalized(s)) {
- free(s);
+ if (!path_is_normalized(s))
return -EINVAL;
- }
}
if (ret)
- *ret = s;
- else
- free(s);
+ *ret = TAKE_PTR(s);
return 0;
}
if (r < 0)
return r;
- s = strappend(p, suffix);
+ s = strjoin(p, suffix);
if (!s)
return -ENOMEM;
return -EINVAL;
if (streq(slice, SPECIAL_ROOT_SLICE))
- subslice = strappend(name, ".slice");
+ subslice = strjoin(name, ".slice");
else {
char *e;
#include <utmp.h>
#include "alloc-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
p = getpwnam(*username);
}
if (!p) {
- r = errno > 0 ? -errno : -ESRCH;
+ r = errno_or_else(ESRCH);
/* If the user requested that we only synthesize as fallback, do so now */
if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
}
if (!g)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
if (gid) {
if (!gid_is_valid(g->gr_gid))
errno = 0;
p = getpwuid(u);
if (!p)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
if (!path_is_valid(p->pw_dir) ||
!path_is_absolute(p->pw_dir))
errno = 0;
p = getpwuid(u);
if (!p)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
if (!path_is_valid(p->pw_shell) ||
!path_is_absolute(p->pw_shell))
errno = 0;
if (putpwent(pw, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
if (putspent(sp, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
if (putgrent(gr, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
if (putsgent(sg, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
p = fgetpwent(stream);
if (!p && errno != ENOENT)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
*pw = p;
return !!p;
errno = 0;
s = fgetspent(stream);
if (!s && errno != ENOENT)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
*sp = s;
return !!s;
errno = 0;
g = fgetgrent(stream);
if (!g && errno != ENOENT)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
*gr = g;
return !!g;
errno = 0;
s = fgetsgent(stream);
if (!s && errno != ENOENT)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
*sg = s;
return !!s;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Rule file name '%s' is not valid, refusing.", x + 1);
- fn = strappend("/proc/sys/fs/binfmt_misc/", x+1);
+ fn = path_join("/proc/sys/fs/binfmt_misc", x+1);
if (!fn)
return log_oom();
#include "cgroup.h"
#include "dbus-cgroup.h"
#include "dbus-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "limits-util.h"
errno = 0;
if (!inet_ntop(item->family, &item->address, buffer, sizeof(buffer)))
- return errno > 0 ? -errno : -EINVAL;
+ return errno_or_else(EINVAL);
fprintf(f, "%s=%s/%u\n", name, buffer, item->prefixlen);
}
return method_generic_unit_operation(message, userdata, error, bus_unit_method_kill, 0);
}
+static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ /* Load the unit if necessary, in order to load it, and insist on the unit being loaded to be
+ * cleaned */
+ return method_generic_unit_operation(message, userdata, error, bus_unit_method_clean, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
+}
+
static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Don't load the unit (because unloaded units can't be in failed state), and don't insist on the
* unit to be loaded properly (since a failed unit might have its unit file disappeared) */
SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("EnqueueUnitJob", "sss", "uososa(uosos)", method_enqueue_unit_job, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CleanUnit", "sas", NULL, method_clean_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RefUnit", "s", NULL, method_ref_unit, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("TimeoutAbortUSec", "t", property_get_timeout_abort_usec, 0, 0),
+ SD_BUS_PROPERTY("TimeoutCleanUSec", "t", bus_property_get_usec, offsetof(Service, timeout_clean_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST),
BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0),
SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("ReloadResult", "s", property_get_result, offsetof(Service, reload_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Service, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
static BUS_DEFINE_PROPERTY_GET(property_get_need_daemon_reload, "b", Unit, unit_need_daemon_reload);
static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_strv, "as", 0);
+static int property_get_can_clean(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ Unit *u = userdata;
+ ExecCleanMask mask;
+ int r;
+
+ assert(bus);
+ assert(reply);
+
+ r = unit_can_clean(u, &mask);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
+ if (!FLAGS_SET(mask, 1U << t))
+ continue;
+
+ r = sd_bus_message_append(reply, "s", exec_resource_type_to_string(t));
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
static int property_get_names(
sd_bus *bus,
const char *path,
return sd_bus_reply_method_return(message, NULL);
}
+int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+ ExecCleanMask mask = 0;
+ Unit *u = userdata;
+ int r;
+
+ assert(message);
+ assert(u);
+
+ r = mac_selinux_unit_access_check(u, message, "stop", error);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(message, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *i;
+
+ r = sd_bus_message_read(message, "s", &i);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (streq(i, "all"))
+ mask |= EXEC_CLEAN_ALL;
+ else {
+ ExecDirectoryType t;
+
+ t = exec_resource_type_from_string(i);
+ if (t < 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid resource type: %s", i);
+
+ mask |= 1U << t;
+ }
+ }
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ r = bus_verify_manage_units_async_full(
+ u,
+ "clean",
+ CAP_DAC_OVERRIDE,
+ N_("Authentication is required to delete files and directories associated with '$(unit)'."),
+ true,
+ message,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ r = unit_clean(u, mask);
+ if (r == -EOPNOTSUPP)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not supporting cleaning.", u->id);
+ if (r == -EUNATCH)
+ return sd_bus_error_setf(error, BUS_ERROR_NOTHING_TO_CLEAN, "No matching resources found.");
+ if (r == -EBUSY)
+ return sd_bus_error_setf(error, BUS_ERROR_UNIT_BUSY, "Unit is not inactive or has pending job.");
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
static int property_get_refs(
sd_bus *bus,
const char *path,
SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("CanClean", "as", property_get_can_clean, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Job", "(uo)", property_get_job, offsetof(Unit, job), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Ref", NULL, NULL, bus_unit_method_ref, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Unref", NULL, NULL, bus_unit_method_unref, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("Clean", "as", NULL, bus_unit_method_clean, SD_BUS_VTABLE_UNPRIVILEGED),
/* For dependency types we don't support anymore always return an empty array */
SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_empty_strv, 0, SD_BUS_VTABLE_HIDDEN),
int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_unit_method_unref(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error *error);
typedef enum BusUnitQueueFlags {
BUS_UNIT_QUEUE_RELOAD_IF_POSSIBLE = 1 << 0,
errno = 0;
p = getpwuid(num);
if (!p)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
gid = p->pw_gid;
}
*/
errno = 0;
ngroups_max = (int) sysconf(_SC_NGROUPS_MAX);
- if (ngroups_max <= 0) {
- if (errno > 0)
- return -errno;
- else
- return -EOPNOTSUPP; /* For all other values */
- }
+ if (ngroups_max <= 0)
+ return errno_or_else(EOPNOTSUPP);
l_gids = new(gid_t, ngroups_max);
if (!l_gids)
}
if (home) {
- x = strappend("HOME=", home);
+ x = strjoin("HOME=", home);
if (!x)
return -ENOMEM;
}
if (username) {
- x = strappend("LOGNAME=", username);
+ x = strjoin("LOGNAME=", username);
if (!x)
return -ENOMEM;
our_env[n_env++] = x;
- x = strappend("USER=", username);
+ x = strjoin("USER=", username);
if (!x)
return -ENOMEM;
our_env[n_env++] = x;
}
if (shell) {
- x = strappend("SHELL=", shell);
+ x = strjoin("SHELL=", shell);
if (!x)
return -ENOMEM;
if (!term)
term = default_term_for_tty(tty_path);
- x = strappend("TERM=", term);
+ x = strjoin("TERM=", term);
if (!x)
return -ENOMEM;
our_env[n_env++] = x;
}
}
+int exec_context_get_clean_directories(
+ ExecContext *c,
+ char **prefix,
+ ExecCleanMask mask,
+ char ***ret) {
+
+ _cleanup_strv_free_ char **l = NULL;
+ ExecDirectoryType t;
+ int r;
+
+ assert(c);
+ assert(prefix);
+ assert(ret);
+
+ for (t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
+ char **i;
+
+ if (!FLAGS_SET(mask, 1U << t))
+ continue;
+
+ if (!prefix[t])
+ continue;
+
+ STRV_FOREACH(i, c->directories[t].paths) {
+ char *j;
+
+ j = path_join(prefix[t], *i);
+ if (!j)
+ return -ENOMEM;
+
+ r = strv_consume(&l, j);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *ret = TAKE_PTR(l);
+ return 0;
+}
+
+int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) {
+ ExecCleanMask mask = 0;
+
+ assert(c);
+ assert(ret);
+
+ for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
+ if (!strv_isempty(c->directories[t].paths))
+ mask |= 1U << t;
+
+ *ret = mask;
+ return 0;
+}
+
void exec_status_start(ExecStatus *s, pid_t pid) {
assert(s);
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EXEC_PRESERVE_YES);
+/* This table maps ExecDirectoryType to the setting it is configured with in the unit */
static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory",
[EXEC_DIRECTORY_STATE] = "StateDirectory",
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
+/* And this table maps ExecDirectoryType too, but to a generic term identifying the type of resource. This
+ * one is supposed to be generic enough to be used for unit types that don't use ExecContext and per-unit
+ * directories, specifically .timer units with their timestamp touch file. */
+static const char* const exec_resource_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
+ [EXEC_DIRECTORY_RUNTIME] = "runtime",
+ [EXEC_DIRECTORY_STATE] = "state",
+ [EXEC_DIRECTORY_CACHE] = "cache",
+ [EXEC_DIRECTORY_LOGS] = "logs",
+ [EXEC_DIRECTORY_CONFIGURATION] = "configuration",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType);
+
+/* And this table also maps ExecDirectoryType, to the environment variable we pass the selected directory to
+ * the service payload in. */
static const char* const exec_directory_env_name_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_RUNTIME] = "RUNTIME_DIRECTORY",
[EXEC_DIRECTORY_STATE] = "STATE_DIRECTORY",
mode_t mode;
} ExecDirectory;
+typedef enum ExecCleanMask {
+ /* In case you wonder why the bitmask below doesn't use "directory" in its name: we want to keep this
+ * generic so that .timer timestamp files can nicely be covered by this too, and similar. */
+ EXEC_CLEAN_RUNTIME = 1U << EXEC_DIRECTORY_RUNTIME,
+ EXEC_CLEAN_STATE = 1U << EXEC_DIRECTORY_STATE,
+ EXEC_CLEAN_CACHE = 1U << EXEC_DIRECTORY_CACHE,
+ EXEC_CLEAN_LOGS = 1U << EXEC_DIRECTORY_LOGS,
+ EXEC_CLEAN_CONFIGURATION = 1U << EXEC_DIRECTORY_CONFIGURATION,
+ EXEC_CLEAN_NONE = 0,
+ EXEC_CLEAN_ALL = (1U << _EXEC_DIRECTORY_TYPE_MAX) - 1,
+ _EXEC_CLEAN_MASK_INVALID = -1,
+} ExecCleanMask;
+
/* Encodes configuration parameters applied to invoked commands. Does not carry runtime data, but only configuration
* changes sourced from unit files and suchlike. ExecContext objects are usually embedded into Unit objects, and do not
* change after being loaded. */
void exec_context_revert_tty(ExecContext *c);
+int exec_context_get_clean_directories(ExecContext *c, char **prefix, ExecCleanMask mask, char ***ret);
+int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret);
+
void exec_status_start(ExecStatus *s, pid_t pid);
void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status);
void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix);
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
+
+const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
+ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;
if (j->type == JOB_NOP)
return true;
- if (IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) {
- /* Immediate result is that the job is or might be
- * started. In this case let's wait for the
- * dependencies, regardless whether they are
- * starting or stopping something. */
-
- HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i)
- if (other->job)
- return false;
- }
-
- /* Also, if something else is being stopped and we should
- * change state after it, then let's wait. */
+ HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i)
+ if (other->job && job_compare(j, other->job, UNIT_AFTER) > 0)
+ return false;
HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i)
- if (other->job &&
- IN_SET(other->job->type, JOB_STOP, JOB_RESTART))
+ if (other->job && job_compare(j, other->job, UNIT_BEFORE) > 0)
return false;
- /* This means that for a service a and a service b where b
- * shall be started after a:
- *
- * start a + start b → 1st step start a, 2nd step start b
- * start a + stop b → 1st step stop b, 2nd step start a
- * stop a + start b → 1st step stop a, 2nd step start b
- * stop a + stop b → 1st step stop b, 2nd step stop a
- *
- * This has the side effect that restarts are properly
- * synchronized too. */
-
return true;
}
if (j->type == JOB_NOP)
return false;
- /* If a job is ordered after ours, and is to be started, then it needs to wait for us, regardless if we stop or
- * start, hence let's not GC in that case. */
- HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i) {
- if (!other->job)
- continue;
-
- if (other->job->ignore_order)
- continue;
-
- if (IN_SET(other->job->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD))
+ /* The logic is inverse to job_is_runnable, we cannot GC as long as we block any job. */
+ HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i)
+ if (other->job && job_compare(j, other->job, UNIT_BEFORE) < 0)
return false;
- }
-
- /* If we are going down, but something else is ordered After= us, then it needs to wait for us */
- if (IN_SET(j->type, JOB_STOP, JOB_RESTART))
- HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) {
- if (!other->job)
- continue;
-
- if (other->job->ignore_order)
- continue;
+ HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i)
+ if (other->job && job_compare(j, other->job, UNIT_AFTER) < 0)
return false;
- }
-
- /* The logic above is kinda the inverse of the job_is_runnable() logic. Specifically, if the job "we" is
- * ordered before the job "other":
- *
- * we start + other start → stay
- * we start + other stop → gc
- * we stop + other start → stay
- * we stop + other stop → gc
- *
- * "we" are ordered after "other":
- *
- * we start + other start → gc
- * we start + other stop → gc
- * we stop + other start → stay
- * we stop + other stop → stay
- */
return true;
}
j->in_gc_queue = true;
}
-static int job_compare(Job * const *a, Job * const *b) {
+static int job_compare_id(Job * const *a, Job * const *b) {
return CMP((*a)->id, (*b)->id);
}
size_t a, b;
/* Order by numeric IDs */
- typesafe_qsort(list, n, job_compare);
+ typesafe_qsort(list, n, job_compare_id);
/* Filter out duplicates */
for (a = 0, b = 0; a < n; a++) {
return 0;
}
- if (IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD)) {
-
- HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) {
- if (!other->job)
- continue;
+ HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) {
+ if (!other->job)
+ continue;
+ if (job_compare(j, other->job, UNIT_AFTER) <= 0)
+ continue;
- if (!GREEDY_REALLOC(list, n_allocated, n+1))
- return -ENOMEM;
- list[n++] = other->job;
- }
+ if (!GREEDY_REALLOC(list, n_allocated, n+1))
+ return -ENOMEM;
+ list[n++] = other->job;
}
HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_BEFORE], i) {
if (!other->job)
continue;
-
- if (!IN_SET(other->job->type, JOB_STOP, JOB_RESTART))
+ if (job_compare(j, other->job, UNIT_BEFORE) <= 0)
continue;
if (!GREEDY_REALLOC(list, n_allocated, n+1))
if (other->job->ignore_order)
continue;
- if (!IN_SET(other->job->type, JOB_START, JOB_VERIFY_ACTIVE, JOB_RELOAD))
+ if (job_compare(j, other->job, UNIT_BEFORE) >= 0)
continue;
if (!GREEDY_REALLOC(list, n_allocated, n+1))
list[n++] = other->job;
}
- if (IN_SET(j->type, JOB_STOP, JOB_RESTART)) {
- HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) {
- if (!other->job)
- continue;
+ HASHMAP_FOREACH_KEY(v, other, j->unit->dependencies[UNIT_AFTER], i) {
+ if (!other->job)
+ continue;
+
+ if (other->job->ignore_order)
+ continue;
- if (other->job->ignore_order)
- continue;
+ if (job_compare(j, other->job, UNIT_AFTER) >= 0)
+ continue;
- if (!GREEDY_REALLOC(list, n_allocated, n+1))
- return -ENOMEM;
- list[n++] = other->job;
- }
+ if (!GREEDY_REALLOC(list, n_allocated, n+1))
+ return -ENOMEM;
+ list[n++] = other->job;
}
n = sort_job_list(list, n);
else
return "reload";
}
+
+/*
+ * assume_dep assumed dependency between units (a is before/after b)
+ *
+ * Returns
+ * 0 jobs are independent,
+ * >0 a should run after b,
+ * <0 a should run before b,
+ *
+ * The logic means that for a service a and a service b where b.After=a:
+ *
+ * start a + start b → 1st step start a, 2nd step start b
+ * start a + stop b → 1st step stop b, 2nd step start a
+ * stop a + start b → 1st step stop a, 2nd step start b
+ * stop a + stop b → 1st step stop b, 2nd step stop a
+ *
+ * This has the side effect that restarts are properly
+ * synchronized too.
+ */
+int job_compare(Job *a, Job *b, UnitDependency assume_dep) {
+ assert(a->type < _JOB_TYPE_MAX_IN_TRANSACTION);
+ assert(b->type < _JOB_TYPE_MAX_IN_TRANSACTION);
+ assert(IN_SET(assume_dep, UNIT_AFTER, UNIT_BEFORE));
+
+ /* Trivial cases first */
+ if (a->type == JOB_NOP || b->type == JOB_NOP)
+ return 0;
+
+ if (a->ignore_order || b->ignore_order)
+ return 0;
+
+ if (assume_dep == UNIT_AFTER)
+ return -job_compare(b, a, UNIT_BEFORE);
+
+ /* Let's make it simple, JOB_STOP goes always first (in case both ua and ub stop,
+ * then ub's stop goes first anyway).
+ * JOB_RESTART is JOB_STOP in disguise (before it is patched to JOB_START). */
+ if (IN_SET(b->type, JOB_STOP, JOB_RESTART))
+ return 1;
+ else
+ return -1;
+}
JobResult job_result_from_string(const char *s) _pure_;
const char* job_type_to_access_method(JobType t);
+
+int job_compare(Job *a, Job *b, UnitDependency assume_dep);
Service.TimeoutStartSec, config_parse_service_timeout, 0, 0
Service.TimeoutStopSec, config_parse_sec_fix_0, 0, offsetof(Service, timeout_stop_usec)
Service.TimeoutAbortSec, config_parse_service_timeout_abort, 0, 0
+Service.TimeoutCleanSec, config_parse_sec, 0, offsetof(Service, timeout_clean_usec)
Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec)
Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec)
m4_dnl The following five only exist for compatibility, they moved into Unit, see above
fd_inc_rcvbuf(fd, NOTIFY_RCVBUF_SIZE);
- m->notify_socket = strappend(m->prefix[EXEC_DIRECTORY_RUNTIME], "/systemd/notify");
+ m->notify_socket = path_join(m->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/notify");
if (!m->notify_socket)
return log_oom();
if (!options)
options = TAKE_PTR(controller);
- where = strappend("/sys/fs/cgroup/", options);
+ where = path_join("/sys/fs/cgroup", options);
if (!where)
return log_oom();
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
+#include "rm-rf.h"
#include "serialize.h"
#include "service.h"
#include "signal-util.h"
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
- [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
+ [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
+ [SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
/* For Type=idle we never want to delay any other jobs, hence we
[SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING,
[SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING,
[SERVICE_FAILED] = UNIT_FAILED,
- [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING
+ [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING,
+ [SERVICE_CLEANING] = UNIT_MAINTENANCE,
};
static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata);
s->timeout_abort_set = u->manager->default_timeout_abort_set;
s->restart_usec = u->manager->default_restart_usec;
s->runtime_max_usec = USEC_INFINITY;
+ s->timeout_clean_usec = USEC_INFINITY;
s->type = _SERVICE_TYPE_INVALID;
s->socket_fd = -1;
s->stdin_fd = s->stdout_fd = s->stderr_fd = -1;
}
static void service_dump(Unit *u, FILE *f, const char *prefix) {
- char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX];
- char buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX];
+ char buf_restart[FORMAT_TIMESPAN_MAX], buf_start[FORMAT_TIMESPAN_MAX], buf_stop[FORMAT_TIMESPAN_MAX],
+ buf_runtime[FORMAT_TIMESPAN_MAX], buf_watchdog[FORMAT_TIMESPAN_MAX], buf_abort[FORMAT_TIMESPAN_MAX],
+ buf_clean[FORMAT_TIMESPAN_MAX];
ServiceExecCommand c;
Service *s = SERVICE(u);
const char *prefix2;
"%sService State: %s\n"
"%sResult: %s\n"
"%sReload Result: %s\n"
+ "%sClean Result: %s\n"
"%sPermissionsStartOnly: %s\n"
"%sRootDirectoryStartOnly: %s\n"
"%sRemainAfterExit: %s\n"
prefix, service_state_to_string(s->state),
prefix, service_result_to_string(s->result),
prefix, service_result_to_string(s->reload_result),
+ prefix, service_result_to_string(s->clean_result),
prefix, yes_no(s->permissions_start_only),
prefix, yes_no(s->root_directory_start_only),
prefix, yes_no(s->remain_after_exit),
prefix, format_timespan(buf_abort, sizeof(buf_abort), s->timeout_abort_usec, USEC_PER_SEC));
fprintf(f,
+ "%sTimeoutCleanSec: %s\n"
"%sRuntimeMaxSec: %s\n"
"%sWatchdogSec: %s\n",
+ prefix, format_timespan(buf_clean, sizeof(buf_clean), s->timeout_clean_usec, USEC_PER_SEC),
prefix, format_timespan(buf_runtime, sizeof(buf_runtime), s->runtime_max_usec, USEC_PER_SEC),
prefix, format_timespan(buf_watchdog, sizeof(buf_watchdog), s->watchdog_usec, USEC_PER_SEC));
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
- SERVICE_AUTO_RESTART))
+ SERVICE_AUTO_RESTART,
+ SERVICE_CLEANING))
s->timer_event_source = sd_event_source_unref(s->timer_event_source);
if (!IN_SET(state,
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
- SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
+ SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
+ SERVICE_CLEANING)) {
service_unwatch_control_pid(s);
s->control_command = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
case SERVICE_AUTO_RESTART:
return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec);
+ case SERVICE_CLEANING:
+ return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_clean_usec);
+
default:
return USEC_INFINITY;
}
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RELOAD,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
- SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) {
+ SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
+ SERVICE_CLEANING)) {
r = unit_watch_pid(UNIT(s), s->control_pid, false);
if (r < 0)
return r;
}
- if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
+ if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART, SERVICE_CLEANING)) {
(void) unit_enqueue_rewatch_pids(u);
(void) unit_setup_dynamic_creds(u);
(void) unit_setup_exec_runtime(u);
if (r < 0)
return r;
- t = strappend("REMOTE_ADDR=", addr);
+ t = strjoin("REMOTE_ADDR=", addr);
if (!t)
return -ENOMEM;
our_env[n_env++] = t;
* please! */
if (IN_SET(s->state,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
- SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))
+ SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, SERVICE_CLEANING))
return -EAGAIN;
/* Already on it! */
return 0;
}
+ /* If we are currently cleaning, then abort it, brutally. */
+ if (s->state == SERVICE_CLEANING) {
+ service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS);
+ return 0;
+ }
+
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
service_enter_stop(s, SERVICE_SUCCESS);
service_enter_dead(s, f, true);
break;
+ case SERVICE_CLEANING:
+
+ if (s->clean_result == SERVICE_SUCCESS)
+ s->clean_result = f;
+
+ service_enter_dead(s, SERVICE_SUCCESS, false);
+ break;
+
default:
assert_not_reached("Uh, control process died at wrong time.");
}
service_enter_restart(s);
break;
+ case SERVICE_CLEANING:
+ log_unit_warning(UNIT(s), "Cleaning timed out. killing.");
+
+ if (s->clean_result == SERVICE_SUCCESS)
+ s->clean_result = SERVICE_FAILURE_TIMEOUT;
+
+ service_enter_signal(s, SERVICE_FINAL_SIGKILL, 0);
+ break;
+
default:
assert_not_reached("Timeout at wrong time.");
}
s->result = SERVICE_SUCCESS;
s->reload_result = SERVICE_SUCCESS;
+ s->clean_result = SERVICE_SUCCESS;
s->n_restarts = 0;
s->flush_n_restarts = false;
}
return s->main_exec_status.status;
}
+static int service_clean(Unit *u, ExecCleanMask mask) {
+ _cleanup_strv_free_ char **l = NULL;
+ Service *s = SERVICE(u);
+ pid_t pid;
+ int r;
+
+ assert(s);
+ assert(mask != 0);
+
+ if (s->state != SERVICE_DEAD)
+ return -EBUSY;
+
+ r = exec_context_get_clean_directories(&s->exec_context, u->manager->prefix, mask, &l);
+ if (r < 0)
+ return r;
+
+ if (strv_isempty(l))
+ return -EUNATCH;
+
+ service_unwatch_control_pid(s);
+ s->clean_result = SERVICE_SUCCESS;
+ s->control_command = NULL;
+ s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
+
+ r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_clean_usec));
+ if (r < 0)
+ goto fail;
+
+ r = unit_fork_helper_process(UNIT(s), "(sd-rmrf)", &pid);
+ if (r < 0)
+ goto fail;
+ if (r == 0) {
+ int ret = EXIT_SUCCESS;
+ char **i;
+
+ STRV_FOREACH(i, l) {
+ r = rm_rf(*i, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK);
+ if (r < 0) {
+ log_error_errno(r, "Failed to remove '%s': %m", *i);
+ ret = EXIT_FAILURE;
+ }
+ }
+
+ _exit(ret);
+ }
+
+ r = unit_watch_pid(u, pid, true);
+ if (r < 0)
+ goto fail;
+
+ s->control_pid = pid;
+
+ service_set_state(s, SERVICE_CLEANING);
+
+ return 0;
+
+fail:
+ log_unit_warning_errno(UNIT(s), r, "Failed to initiate cleaning: %m");
+ s->clean_result = SERVICE_FAILURE_RESOURCES;
+ s->timer_event_source = sd_event_source_unref(s->timer_event_source);
+ return r;
+}
+
+static int service_can_clean(Unit *u, ExecCleanMask *ret) {
+ Service *s = SERVICE(u);
+
+ assert(s);
+
+ return exec_context_get_clean_mask(&s->exec_context, ret);
+}
+
static const char* const service_restart_table[_SERVICE_RESTART_MAX] = {
[SERVICE_RESTART_NO] = "no",
[SERVICE_RESTART_ON_SUCCESS] = "on-success",
.can_reload = service_can_reload,
.kill = service_kill,
+ .clean = service_clean,
+ .can_clean = service_can_clean,
.serialize = service_serialize,
.deserialize_item = service_deserialize_item,
usec_t timeout_stop_usec;
usec_t timeout_abort_usec;
bool timeout_abort_set;
+ usec_t timeout_clean_usec;
usec_t runtime_max_usec;
dual_timestamp watchdog_timestamp;
/* If we shut down, remember why */
ServiceResult result;
ServiceResult reload_result;
+ ServiceResult clean_result;
bool main_pid_known:1;
bool main_pid_alien:1;
if (r < 0)
return r;
- t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id);
+ t->stamp_path = strjoin("/var/lib/systemd/timers/stamp-", UNIT(t)->id);
} else {
const char *e;
}
}
+static int timer_clean(Unit *u, ExecCleanMask mask) {
+ Timer *t = TIMER(u);
+ int r;
+
+ assert(t);
+ assert(mask != 0);
+
+ if (t->state != TIMER_DEAD)
+ return -EBUSY;
+
+ if (!IN_SET(mask, EXEC_CLEAN_STATE))
+ return -EUNATCH;
+
+ r = timer_setup_persistent(t);
+ if (r < 0)
+ return r;
+
+ if (!t->stamp_path)
+ return -EUNATCH;
+
+ if (unlink(t->stamp_path) && errno != ENOENT)
+ return log_unit_error_errno(u, errno, "Failed to clean stamp file of timer: %m");
+
+ return 0;
+}
+
+static int timer_can_clean(Unit *u, ExecCleanMask *ret) {
+ Timer *t = TIMER(u);
+
+ assert(t);
+
+ *ret = t->persistent ? EXEC_CLEAN_STATE : 0;
+ return 0;
+}
+
static const char* const timer_base_table[_TIMER_BASE_MAX] = {
[TIMER_ACTIVE] = "OnActiveSec",
[TIMER_BOOT] = "OnBootSec",
.start = timer_start,
.stop = timer_stop,
+ .clean = timer_clean,
+ .can_clean = timer_can_clean,
+
.serialize = timer_serialize,
.deserialize_item = timer_deserialize_item,
Unit *u;
void *v;
int r;
+ static const UnitDependency directions[] = {
+ UNIT_BEFORE,
+ UNIT_AFTER,
+ };
+ size_t d;
assert(tr);
assert(j);
j->marker = from ? from : j;
j->generation = generation;
- /* We assume that the dependencies are bidirectional, and
- * hence can ignore UNIT_AFTER */
- HASHMAP_FOREACH_KEY(v, u, j->unit->dependencies[UNIT_BEFORE], i) {
- Job *o;
-
- /* Is there a job for this unit? */
- o = hashmap_get(tr->jobs, u);
- if (!o) {
- /* Ok, there is no job for this in the
- * transaction, but maybe there is already one
- * running? */
- o = u->job;
- if (!o)
+ /* Actual ordering of jobs depends on the unit ordering dependency and job types. We need to traverse
+ * the graph over 'before' edges in the actual job execution order. We traverse over both unit
+ * ordering dependencies and we test with job_compare() whether it is the 'before' edge in the job
+ * execution ordering. */
+ for (d = 0; d < ELEMENTSOF(directions); d++) {
+ HASHMAP_FOREACH_KEY(v, u, j->unit->dependencies[directions[d]], i) {
+ Job *o;
+
+ /* Is there a job for this unit? */
+ o = hashmap_get(tr->jobs, u);
+ if (!o) {
+ /* Ok, there is no job for this in the
+ * transaction, but maybe there is already one
+ * running? */
+ o = u->job;
+ if (!o)
+ continue;
+ }
+
+ /* Cut traversing if the job j is not really *before* o. */
+ if (job_compare(j, o, directions[d]) >= 0)
continue;
- }
- r = transaction_verify_order_one(tr, o, j, generation, e);
- if (r < 0)
- return r;
+ r = transaction_verify_order_one(tr, o, j, generation, e);
+ if (r < 0)
+ return r;
+ }
}
/* Ok, let's backtrack, and remember that this entry is not on
state = unit_active_state(u);
if (UNIT_IS_ACTIVE_OR_RELOADING(state))
return -EALREADY;
+ if (state == UNIT_MAINTENANCE)
+ return -EAGAIN;
/* Units that aren't loaded cannot be started */
if (u->load_state != UNIT_LOADED)
return 0;
}
+int unit_clean(Unit *u, ExecCleanMask mask) {
+ UnitActiveState state;
+
+ assert(u);
+
+ /* Special return values:
+ *
+ * -EOPNOTSUPP → cleaning not supported for this unit type
+ * -EUNATCH → cleaning not defined for this resource type
+ * -EBUSY → unit currently can't be cleaned since it's running or not properly loaded, or has
+ * a job queued or similar
+ */
+
+ if (!UNIT_VTABLE(u)->clean)
+ return -EOPNOTSUPP;
+
+ if (mask == 0)
+ return -EUNATCH;
+
+ if (u->load_state != UNIT_LOADED)
+ return -EBUSY;
+
+ if (u->job)
+ return -EBUSY;
+
+ state = unit_active_state(u);
+ if (!IN_SET(state, UNIT_INACTIVE))
+ return -EBUSY;
+
+ return UNIT_VTABLE(u)->clean(u, mask);
+}
+
+int unit_can_clean(Unit *u, ExecCleanMask *ret) {
+ assert(u);
+
+ if (!UNIT_VTABLE(u)->clean ||
+ u->load_state != UNIT_LOADED) {
+ *ret = 0;
+ return 0;
+ }
+
+ /* When the clean() method is set, can_clean() really should be set too */
+ assert(UNIT_VTABLE(u)->can_clean);
+
+ return UNIT_VTABLE(u)->can_clean(u, ret);
+}
+
static const char* const collect_mode_table[_COLLECT_MODE_MAX] = {
[COLLECT_INACTIVE] = "inactive",
[COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed",
int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error);
+ /* Clear out the various runtime/state/cache/logs/configuration data */
+ int (*clean)(Unit *u, ExecCleanMask m);
+
+ /* Return which kind of data can be cleaned */
+ int (*can_clean)(Unit *u, ExecCleanMask *ret);
+
bool (*can_reload)(Unit *u);
/* Write all data that cannot be restored from other sources
int unit_test_trigger_loaded(Unit *u);
+int unit_clean(Unit *u, ExecCleanMask mask);
+int unit_can_clean(Unit *u, ExecCleanMask *ret_mask);
+
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \
_cleanup_free_ char *fn_compressed = NULL, *tmp_compressed = NULL;
_cleanup_close_ int fd_compressed = -1;
- fn_compressed = strappend(fn, COMPRESSED_EXT);
+ fn_compressed = strjoin(fn, COMPRESSED_EXT);
if (!fn_compressed) {
log_oom();
goto uncompressed;
continue;
if (!d->name) {
- d->name = strappend("luks-", d->uuid);
+ d->name = strjoin("luks-", d->uuid);
if (!d->name)
return log_oom();
}
- device = strappend("UUID=", d->uuid);
+ device = strjoin("UUID=", d->uuid);
if (!device)
return log_oom();
if (!p)
return log_oom();
- f = strappend(SYSTEM_DATA_UNIT_PATH "/", *u);
+ f = path_join(SYSTEM_DATA_UNIT_PATH, *u);
if (!f)
return log_oom();
#include <unistd.h>
#include "alloc-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "generator.h"
errno = 0;
if (isatty(fd) <= 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
if (device) {
char *ret;
- ret = strappend("/dev/mapper/", id);
+ ret = path_join("/dev/mapper", id);
if (!ret)
return log_oom();
assert(c);
if (!isempty(c->data[PROP_CHASSIS]))
- return strappend("computer-", c->data[PROP_CHASSIS]);
+ return strjoin("computer-", c->data[PROP_CHASSIS]);
chassis = fallback_chassis();
if (chassis)
- return strappend("computer-", chassis);
+ return strjoin("computer-", chassis);
return strdup("computer");
}
if (!cc)
return -ENOMEM;
- hdr = strappend("If-None-Match: ", cc);
+ hdr = strjoin("If-None-Match: ", cc);
if (!hdr)
return -ENOMEM;
if (!comm)
return log_oom();
- t = strappend("_COMM=", comm);
+ t = strjoin("_COMM=", comm);
if (!t)
return log_oom();
/* Append _EXE only if the interpreter is not a link.
Otherwise, it might be outdated often. */
if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) {
- t2 = strappend("_EXE=", interpreter);
+ t2 = strjoin("_EXE=", interpreter);
if (!t2)
return log_oom();
}
} else {
- t = strappend("_EXE=", p);
+ t = strjoin("_EXE=", p);
if (!t)
return log_oom();
}
char *b;
if (sd_device_get_devname(d, &g) >= 0) {
- b = strappend("_UDEV_DEVNODE=", g);
+ b = strjoin("_UDEV_DEVNODE=", g);
if (b) {
iovec[n++] = IOVEC_MAKE_STRING(b);
z++;
}
if (sd_device_get_sysname(d, &g) >= 0) {
- b = strappend("_UDEV_SYSNAME=", g);
+ b = strjoin("_UDEV_SYSNAME=", g);
if (b) {
iovec[n++] = IOVEC_MAKE_STRING(b);
z++;
if (j >= N_IOVEC_UDEV_FIELDS)
break;
- b = strappend("_UDEV_DEVLINK=", g);
+ b = strjoin("_UDEV_DEVLINK=", g);
if (b) {
iovec[n++] = IOVEC_MAKE_STRING(b);
z++;
goto finish;
if (identifier) {
- syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier);
+ syslog_identifier = strjoin("SYSLOG_IDENTIFIER=", identifier);
if (syslog_identifier)
iovec[n++] = IOVEC_MAKE_STRING(syslog_identifier);
}
if (pid) {
- syslog_pid = strappend("SYSLOG_PID=", pid);
+ syslog_pid = strjoin("SYSLOG_PID=", pid);
if (syslog_pid)
iovec[n++] = IOVEC_MAKE_STRING(syslog_pid);
}
if (!t)
return;
- x = strappend("_HOSTNAME=", t);
+ x = strjoin("_HOSTNAME=", t);
if (!x)
return;
}
if (s->identifier) {
- syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier);
+ syslog_identifier = strjoin("SYSLOG_IDENTIFIER=", s->identifier);
if (syslog_identifier)
iovec[n++] = IOVEC_MAKE_STRING(syslog_identifier);
}
iovec[n++] = IOVEC_MAKE_STRING(c);
}
- message = strappend("MESSAGE=", p);
+ message = strjoin("MESSAGE=", p);
if (message)
iovec[n++] = IOVEC_MAKE_STRING(message);
assert(fname);
if (!stream->state_file) {
- stream->state_file = strappend("/run/systemd/journal/streams/", fname);
+ stream->state_file = path_join("/run/systemd/journal/streams", fname);
if (!stream->state_file)
return log_oom();
}
#include "journal-file.h"
#include "journal-internal.h"
#include "macro.h"
+#include "path-util.h"
#include "string-util.h"
int main(int argc, char *argv[]) {
assert_se(mkdtemp(dn));
(void) chattr_path(dn, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
- fn = strappend(dn, "/test.journal");
+ fn = path_join(dn, "test.journal");
r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0644, false, 0, false, NULL, NULL, NULL, NULL, &new_journal);
assert_se(r >= 0);
#define BUS_ERROR_NO_SUCH_DYNAMIC_USER "org.freedesktop.systemd1.NoSuchDynamicUser"
#define BUS_ERROR_NOT_REFERENCED "org.freedesktop.systemd1.NotReferenced"
#define BUS_ERROR_DISK_FULL "org.freedesktop.systemd1.DiskFull"
+#define BUS_ERROR_NOTHING_TO_CLEAN "org.freedesktop.systemd1.NothingToClean"
+#define BUS_ERROR_UNIT_BUSY "org.freedesktop.systemd1.UnitBusy"
#define BUS_ERROR_NO_SUCH_MACHINE "org.freedesktop.machine1.NoSuchMachine"
#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage"
int bus_add_match_internal(
sd_bus *bus,
- const char *match) {
+ const char *match,
+ uint64_t *ret_counter) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *e;
+ int r;
assert(bus);
e = append_eavesdrop(bus, match);
- return sd_bus_call_method(
+ r = sd_bus_call_method(
bus,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"AddMatch",
NULL,
- NULL,
+ &reply,
"s",
e);
+ if (r < 0)
+ return r;
+
+ /* If the caller asked for it, return the read counter of the reply */
+ if (ret_counter)
+ *ret_counter = reply->read_counter;
+
+ return r;
}
int bus_add_match_internal_async(
#include "sd-bus.h"
-int bus_add_match_internal(sd_bus *bus, const char *match);
+int bus_add_match_internal(sd_bus *bus, const char *match, uint64_t *ret_counter);
int bus_add_match_internal_async(sd_bus *bus, sd_bus_slot **ret, const char *match, sd_bus_message_handler_t callback, void *userdata);
int bus_remove_match_internal(sd_bus *bus, const char *match);
if (!name)
return 0;
- n = strappend("System.Error.", name);
+ n = strjoin("System.Error.", name);
if (!n)
return -ENOMEM;
unsigned last_iteration;
+ /* Don't dispatch this slot with with messages that arrived in any iteration before or at the this
+ * one. We use this to ensure that matches don't apply "retroactively" and thus can confuse the
+ * caller: matches will only match incoming messages from the moment on the match was installed. */
+ uint64_t after;
+
char *match_string;
struct bus_match_node *match_node;
size_t wqueue_allocated;
uint64_t cookie;
+ uint64_t read_counter; /* A counter for each incoming msg */
char *unique_name;
uint64_t unique_id;
case BUS_MATCH_LEAF:
if (bus) {
- if (node->leaf.callback->last_iteration == bus->iteration_counter)
- return 0;
+ /* Don't run this match as long as the AddMatch() call is not complete yet.
+ *
+ * Don't run this match unless the 'after' counter has been reached.
+ *
+ * Don't run this match more than once per iteration */
+
+ if (node->leaf.callback->install_slot ||
+ m->read_counter <= node->leaf.callback->after ||
+ node->leaf.callback->last_iteration == bus->iteration_counter)
+ return bus_match_run(bus, node->next, m);
node->leaf.callback->last_iteration = bus->iteration_counter;
}
size_t header_offsets[_BUS_MESSAGE_HEADER_MAX];
unsigned n_header_offsets;
+
+ uint64_t read_counter;
};
static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) {
bus->n_fds = 0;
if (t) {
+ t->read_counter = ++bus->read_counter;
bus->rqueue[bus->rqueue_size++] = bus_message_ref_queued(t, bus);
sd_bus_message_unref(t);
}
return r;
bus_message_set_sender_local(bus, m);
+ m->read_counter = ++bus->read_counter;
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
if (r < 0)
return r;
+ m->read_counter = ++bus->read_counter;
+
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
return r;
synthetic_reply->realtime = m->realtime;
synthetic_reply->monotonic = m->monotonic;
synthetic_reply->seqnum = m->seqnum;
+ synthetic_reply->read_counter = m->read_counter;
r = bus_seal_synthetic_message(bus, synthetic_reply);
if (r < 0)
if (r < 0)
return r;
+ m->read_counter = ++bus->read_counter;
+
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
return r;
return r;
bus_message_set_sender_local(bus, m);
+ m->read_counter = ++bus->read_counter;
r = bus_seal_synthetic_message(bus, m);
if (r < 0)
bus->current_slot = match_slot->match_callback.install_slot;
bus->current_handler = add_match_callback;
bus->current_userdata = userdata;
-
- match_slot->match_callback.install_slot = sd_bus_slot_unref(match_slot->match_callback.install_slot);
} else {
if (failed) /* Generic failure handling: destroy the connection */
bus_enter_closing(sd_bus_message_get_bus(m));
r = 1;
}
+ /* We don't need the install method reply slot anymore, let's free it */
+ match_slot->match_callback.install_slot = sd_bus_slot_unref(match_slot->match_callback.install_slot);
+
if (failed && match_slot->floating)
bus_slot_disconnect(match_slot, true);
* then make it floating. */
r = sd_bus_slot_set_floating(s->match_callback.install_slot, true);
} else
- r = bus_add_match_internal(bus, s->match_callback.match_string);
+ r = bus_add_match_internal(bus, s->match_callback.match_string, &s->match_callback.after);
if (r < 0)
goto finish;
if (!filename_is_valid(seat))
return -EINVAL;
- p = strappend("/run/systemd/seats/", seat);
+ p = path_join("/run/systemd/seats", seat);
} else {
_cleanup_free_ char *buf = NULL;
if (r < 0)
return r;
- p = strappend("/run/systemd/seats/", buf);
+ p = path_join("/run/systemd/seats", buf);
}
-
if (!p)
return -ENOMEM;
if (!session_id_valid(session))
return -EINVAL;
- p = strappend("/run/systemd/sessions/", session);
+ p = path_join("/run/systemd/sessions", session);
} else {
_cleanup_free_ char *buf = NULL;
if (r < 0)
return r;
- p = strappend("/run/systemd/sessions/", buf);
+ p = path_join("/run/systemd/sessions", buf);
}
if (!p)
if (data)
memcpy(data, attr_data, size);
- return 0;
+ return r;
}
int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data) {
static const NLType rtnl_neigh_types[] = {
[NDA_DST] = { .type = NETLINK_TYPE_IN_ADDR },
- [NDA_LLADDR] = { .type = NETLINK_TYPE_ETHER_ADDR },
+ [NDA_LLADDR] = { /* struct ether_addr or struct in_addr */ },
[NDA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) },
[NDA_PROBES] = { .type = NETLINK_TYPE_U32 },
[NDA_VLAN] = { .type = NETLINK_TYPE_U16 },
if (r < 0)
return r;
- fn = strappend(c, "/user-dirs.dirs");
+ fn = path_join(c, "user-dirs.dirs");
if (!fn)
return -ENOMEM;
if (r < 0)
return r;
- cc = strappend(h, p+5);
+ cc = path_join(h, p+5);
if (!cc)
return -ENOMEM;
if (r < 0)
return r;
- cc = strappend(h, "/Desktop");
+ cc = path_join(h, "Desktop");
if (!cc)
return -ENOMEM;
_cleanup_free_ char *s = NULL;
char **u;
- s = strappend("KEYMAP=", c->vc_keymap);
+ s = strjoin("KEYMAP=", c->vc_keymap);
if (!s)
return -ENOMEM;
_cleanup_free_ char *s = NULL;
char **u;
- s = strappend("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
+ s = strjoin("KEYMAP_TOGGLE=", c->vc_keymap_toggle);
if (!s)
return -ENOMEM;
if (cunescape(dent->d_name, UNESCAPE_RELAX, &unescaped_devname) < 0)
return -ENOMEM;
- n = strappend("/dev/", unescaped_devname);
+ n = path_join("/dev", unescaped_devname);
if (!n)
return -ENOMEM;
#include "cgroup-util.h"
#include "conf-parser.h"
#include "device-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "limits-util.h"
#include "logind.h"
errno = 0;
p = getpwuid(uid);
if (!p)
- return errno > 0 ? -errno : -ENOENT;
+ return errno_or_else(ENOENT);
return manager_add_user(m, uid, p->pw_gid, p->pw_name, p->pw_dir, _user);
}
errno = 0;
pw = getpwuid(uid);
if (!pw)
- return errno > 0 ? -errno : -ENOENT;
+ return errno_or_else(ENOENT);
r = bus_verify_polkit_async(
message,
if (r < 0)
return r;
- t = strappend(p, "/uevent");
+ t = path_join(p, "uevent");
if (!t)
return -ENOMEM;
#include "logind-inhibit.h"
#include "mkdir.h"
#include "parse-util.h"
+#include "path-util.h"
#include "string-table.h"
#include "string-util.h"
#include "tmpfile-util.h"
if (!i)
return NULL;
- i->state_file = strappend("/run/systemd/inhibit/", id);
+ i->state_file = path_join("/run/systemd/inhibit", id);
if (!i->state_file)
return mfree(i);
if (!t)
return NULL;
- return strappend("/org/freedesktop/login1/seat/", t);
+ return strjoin("/org/freedesktop/login1/seat/", t);
}
int seat_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
#include "logind-session-dbus.h"
#include "mkdir.h"
#include "parse-util.h"
+#include "path-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "terminal-util.h"
.manager = m,
};
- s->state_file = strappend("/run/systemd/seats/", id);
+ s->state_file = path_join("/run/systemd/seats", id);
if (!s->state_file)
return -ENOMEM;
if (!t)
return NULL;
- return strappend("/org/freedesktop/login1/session/", t);
+ return strjoin("/org/freedesktop/login1/session/", t);
}
int session_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
.tty_validity = _TTY_VALIDITY_INVALID,
};
- s->state_file = strappend("/run/systemd/sessions/", id);
+ s->state_file = path_join("/run/systemd/sessions", id);
if (!s->state_file)
return -ENOMEM;
assert(atime);
if (!path_is_absolute(tty)) {
- p = strappend("/dev/", tty);
+ p = path_join("/dev", tty);
if (!p)
return -ENOMEM;
if (++(*i_dev) < n_dev) {
_cleanup_free_ char *p = NULL;
- p = strappend(prefix, lookahead < n_dev ? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL) : " ");
+ p = strjoin(prefix, lookahead < n_dev ? special_glyph(SPECIAL_GLYPH_TREE_VERTICAL) : " ");
if (!p)
return -ENOMEM;
if (!e)
return NULL;
- return strappend("/org/freedesktop/machine1/image/", e);
+ return strjoin("/org/freedesktop/machine1/image/", e);
}
int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
if (!e)
return NULL;
- return strappend("/org/freedesktop/machine1/machine/", e);
+ return strjoin("/org/freedesktop/machine1/machine/", e);
}
int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
#include "bus-error.h"
#include "bus-util.h"
#include "env-file.h"
+#include "errno-util.h"
#include "escape.h"
#include "extract-word.h"
#include "fd-util.h"
#include "machine.h"
#include "mkdir.h"
#include "parse-util.h"
+#include "path-util.h"
#include "process-util.h"
#include "serialize.h"
#include "special.h"
goto fail;
if (class != MACHINE_HOST) {
- m->state_file = strappend("/run/systemd/machines/", m->name);
+ m->state_file = path_join("/run/systemd/machines", m->name);
if (!m->state_file)
goto fail;
}
k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range);
if (k != 3) {
if (ferror(f))
- return -errno;
+ return errno_or_else(EIO);
return -EBADMSG;
}
k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT "\n", &gid_base, &gid_shift, &gid_range);
if (k != 3) {
if (ferror(f))
- return -errno;
+ return errno_or_else(EIO);
return -EBADMSG;
}
#include "bus-common-errors.h"
#include "bus-util.h"
#include "cgroup-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
errno = 0;
n = fread(&success, 1, sizeof(success), f);
if (n != sizeof(success))
- return ret < 0 ? ret : (errno != 0 ? -errno : -EIO);
+ return ret < 0 ? ret : errno_or_else(EIO);
if (ret < 0) {
_cleanup_free_ char *name = NULL;
errno = 0;
n = fread(&size, 1, sizeof(size), f);
if (n != sizeof(size))
- return errno != 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
r = sd_bus_message_append(reply, "(st)", name, size);
if (r < 0)
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno > 0)
- return -errno;
+ if (ferror(f))
+ return errno_or_else(EIO);
return -EIO;
}
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno > 0)
- return -errno;
+ if (ferror(f))
+ return errno_or_else(EIO);
return -EIO;
}
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno > 0)
- return -errno;
+ if (ferror(f))
+ return errno_or_else(EIO);
return -EIO;
}
if (k < 0 && feof(f))
break;
if (k != 3) {
- if (ferror(f) && errno > 0)
- return -errno;
+ if (ferror(f))
+ return errno_or_else(EIO);
return -EIO;
}
Tunnel.CopyDSCP, config_parse_bool, 0, offsetof(Tunnel, copy_dscp)
Tunnel.EncapsulationLimit, config_parse_encap_limit, 0, offsetof(Tunnel, encap_limit)
Tunnel.Independent, config_parse_bool, 0, offsetof(Tunnel, independent)
+Tunnel.AssignToLoopback, config_parse_bool, 0, offsetof(Tunnel, assign_to_loopback)
Tunnel.AllowLocalRemote, config_parse_tristate, 0, offsetof(Tunnel, allow_localremote)
Tunnel.FooOverUDP, config_parse_bool, 0, offsetof(Tunnel, fou_tunnel)
Tunnel.FOUDestinationPort, config_parse_ip_port, 0, offsetof(Tunnel, fou_destination_port)
assert(m);
assert(t);
- assert(t->family == AF_INET);
- if (link) {
- r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex);
+ if (link || t->assign_to_loopback) {
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link ? link->ifindex : LOOPBACK_IFINDEX);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
}
}
assert(t);
- assert(t->family == AF_INET);
- if (link) {
- r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link->ifindex);
+ if (link || t->assign_to_loopback) {
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link ? link->ifindex : LOOPBACK_IFINDEX);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LINK attribute: %m");
}
t = IP6GRETAP(netdev);
assert(t);
- assert(t->family == AF_INET6);
assert(m);
- if (link) {
- r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link->ifindex);
+ if (link || t->assign_to_loopback) {
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link ? link->ifindex : LOOPBACK_IFINDEX);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LINK attribute: %m");
}
t = VTI6(netdev);
assert(t);
- assert((netdev->kind == NETDEV_KIND_VTI && t->family == AF_INET) ||
- (netdev->kind == NETDEV_KIND_VTI6 && t->family == AF_INET6));
- if (link) {
- r = sd_netlink_message_append_u32(m, IFLA_VTI_LINK, link->ifindex);
+ if (link || t->assign_to_loopback) {
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_LINK, link ? link->ifindex : LOOPBACK_IFINDEX);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_LINK attribute: %m");
}
assert(netdev);
assert(m);
assert(t);
- assert(t->family == AF_INET6);
- if (link) {
- r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex);
+ if (link || t->assign_to_loopback) {
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link ? link->ifindex : LOOPBACK_IFINDEX);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
}
assert(t);
- if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE)) {
- if (t->family == AF_UNSPEC)
- t->family = AF_INET;
- if (t->family != AF_INET)
- return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
- "vti/ipip/sit/gre tunnel without a local/remote IPv4 address configured in %s. Ignoring", filename);
- }
+ if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE) &&
+ !IN_SET(t->family, AF_UNSPEC, AF_INET))
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "vti/ipip/sit/gre tunnel without a local/remote IPv4 address configured in %s. Ignoring", filename);
if (IN_SET(netdev->kind, NETDEV_KIND_GRETAP, NETDEV_KIND_ERSPAN) &&
(t->family != AF_INET || in_addr_is_null(t->family, &t->remote)))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
"gretap/erspan tunnel without a remote IPv4 address configured in %s. Ignoring", filename);
- if (IN_SET(netdev->kind, NETDEV_KIND_VTI6, NETDEV_KIND_IP6TNL, NETDEV_KIND_IP6GRE)) {
- if (t->family == AF_UNSPEC)
- t->family = AF_INET6;
- if (t->family != AF_INET6)
- return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
- "vti6/ip6tnl/ip6gre tunnel without a local/remote IPv6 address configured in %s. Ignoring", filename);
- }
+ if ((IN_SET(netdev->kind, NETDEV_KIND_VTI6, NETDEV_KIND_IP6TNL) && t->family != AF_INET6) ||
+ (netdev->kind == NETDEV_KIND_IP6GRE && !IN_SET(t->family, AF_UNSPEC, AF_INET6)))
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "vti6/ip6tnl/ip6gre tunnel without a local/remote IPv6 address configured in %s. Ignoring", filename);
if (netdev->kind == NETDEV_KIND_IP6GRETAP &&
(t->family != AF_INET6 || in_addr_is_null(t->family, &t->remote)))
if (netdev->kind == NETDEV_KIND_ERSPAN && (t->erspan_index >= (1 << 20) || t->erspan_index == 0))
return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), "Invalid erspan index %d. Ignoring", t->erspan_index);
+ /* netlink_message_append_in_addr_union() is used for vti/vti6. So, t->family cannot be AF_UNSPEC. */
+ if (netdev->kind == NETDEV_KIND_VTI)
+ t->family = AF_INET;
+
return 0;
}
bool copy_dscp;
bool independent;
bool fou_tunnel;
+ bool assign_to_loopback;
uint16_t encap_src_port;
uint16_t fou_destination_port;
#include "netdev/xfrm.h"
static int xfrm_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *message) {
- int if_idx, r;
Xfrm *x;
+ int r;
assert(netdev);
assert(message);
x = XFRM(netdev);
- if (x->independent)
- if_idx = LOOPBACK_IFINDEX;
- else {
- assert(link);
- if (link->ifindex == 0)
- return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(ENODEV), "Could not get interface index: %m");
- if_idx = link->ifindex;
- }
+ assert(link || x->independent);
- r = sd_netlink_message_append_u32(message, IFLA_XFRM_LINK, if_idx);
+ r = sd_netlink_message_append_u32(message, IFLA_XFRM_LINK, link ? link->ifindex : LOOPBACK_IFINDEX);
if (r < 0)
return log_netdev_error_errno(netdev, r, "Could not append IFLA_XFRM_LINK: %m");
if (!in_addr_equal(fam, &gw, gateway))
continue;
- r = sd_netlink_message_read_ether_addr(m, NDA_LLADDR, &mac);
+ r = sd_netlink_message_read(m, NDA_LLADDR, sizeof(mac), &mac);
if (r < 0)
continue;
return 0;
}
+int config_parse_dhcp_use_dns(
+ 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) {
+
+ Network *network = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ network->dhcp_use_dns = r;
+ network->dhcp6_use_dns = r;
+
+ return 0;
+}
+
+int config_parse_dhcp_use_ntp(
+ 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) {
+
+ Network *network = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ network->dhcp_use_ntp = r;
+ network->dhcp6_use_ntp = r;
+
+ return 0;
+}
+
int config_parse_section_route_table(
const char *unit,
const char *filename,
DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_;
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_ntp);
CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
CONFIG_PARSER_PROTOTYPE(config_parse_section_route_table);
assert(route);
assert(self_addr);
- if (in_addr_is_localhost(AF_INET, &route->dst) ||
- (self_addr->s_addr && route->dst.in.s_addr == self_addr->s_addr))
+ if (in4_addr_is_localhost(&route->dst.in) ||
+ (!in4_addr_is_null(self_addr) && in4_addr_equal(&route->dst.in, self_addr)))
return RT_SCOPE_HOST;
else if (in4_addr_is_null(&route->gw.in))
return RT_SCOPE_LINK;
r = route_configure(route, link, dhcp4_route_handler);
if (r < 0)
return log_link_error_errno(link, r, "Could not set host route: %m");
-
- link->dhcp4_messages++;
+ if (r > 0)
+ link->dhcp4_messages++;
}
r = sd_dhcp_lease_get_router(link->dhcp_lease, &router);
r = route_configure(route_gw, link, dhcp4_route_handler);
if (r < 0)
return log_link_error_errno(link, r, "Could not set host route: %m");
-
- link->dhcp4_messages++;
+ if (r > 0)
+ link->dhcp4_messages++;
r = route_new(&route);
if (r < 0)
r = route_configure(route, link, dhcp4_route_handler);
if (r < 0)
return log_link_error_errno(link, r, "Could not set routes: %m");
-
- link->dhcp4_messages++;
+ if (r > 0)
+ link->dhcp4_messages++;
}
return 0;
link_enter_failed(link);
return 1;
}
-
- manager_rtnl_process_address(rtnl, m, link->manager);
+ if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
r = link_set_dhcp_routes(link);
if (r < 0) {
log_link_error_errno(link, r, "Could not set DHCPv6 address: %m");
link_enter_failed(link);
-
- } else if (r >= 0)
+ return 1;
+ }
+ if (r >= 0)
manager_rtnl_process_address(rtnl, m, link->manager);
+ link_request_set_routes(link);
+
return 1;
}
_cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
_cleanup_(fdb_entry_freep) FdbEntry *fdb_entry = NULL;
- _cleanup_free_ struct ether_addr *mac_addr = NULL;
int r;
assert(network);
if (network->n_static_fdb_entries >= STATIC_FDB_ENTRIES_PER_NETWORK_MAX)
return -E2BIG;
- /* allocate space for MAC address. */
- mac_addr = new0(struct ether_addr, 1);
- if (!mac_addr)
- return -ENOMEM;
-
/* allocate space for and FDB entry. */
fdb_entry = new(FdbEntry, 1);
if (!fdb_entry)
/* init FDB structure. */
*fdb_entry = (FdbEntry) {
.network = network,
- .mac_addr = TAKE_PTR(mac_addr),
.vni = VXLAN_VID_MAX + 1,
.fdb_ntf_flags = NEIGHBOR_CACHE_ENTRY_FLAGS_SELF,
};
if (r < 0)
return rtnl_log_create_error(r);
- r = sd_netlink_message_append_ether_addr(req, NDA_LLADDR, fdb_entry->mac_addr);
+ r = sd_netlink_message_append_data(req, NDA_LLADDR, &fdb_entry->mac_addr, sizeof(fdb_entry->mac_addr));
if (r < 0)
return rtnl_log_create_error(r);
}
network_config_section_free(fdb_entry->section);
- free(fdb_entry->mac_addr);
free(fdb_entry);
}
if (r < 0)
return log_oom();
- /* read in the MAC address for the FDB table. */
- r = sscanf(rvalue, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
- &fdb_entry->mac_addr->ether_addr_octet[0],
- &fdb_entry->mac_addr->ether_addr_octet[1],
- &fdb_entry->mac_addr->ether_addr_octet[2],
- &fdb_entry->mac_addr->ether_addr_octet[3],
- &fdb_entry->mac_addr->ether_addr_octet[4],
- &fdb_entry->mac_addr->ether_addr_octet[5]);
-
- if (r != ETHER_ADDR_LEN) {
- log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring assignment: %s", rvalue);
+ r = ether_addr_from_string(rvalue, &fdb_entry->mac_addr);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Not a valid MAC address, ignoring assignment: %s", rvalue);
return 0;
}
int family;
uint16_t vlan_id;
- struct ether_addr *mac_addr;
+ struct ether_addr mac_addr;
union in_addr_union destination_addr;
NeighborCacheEntryFlags fdb_ntf_flags;
space = true;
}
- if (link->network->dhcp_use_dns && dhcp6_lease) {
+ if (link->network->dhcp6_use_dns && dhcp6_lease) {
struct in6_addr *in6_addrs;
r = sd_dhcp6_lease_get_dns(dhcp6_lease, &in6_addrs);
space = true;
}
- if (link->network->dhcp_use_ntp && dhcp6_lease) {
+ if (link->network->dhcp6_use_ntp && dhcp6_lease) {
struct in6_addr *in6_addrs;
char **hosts;
r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
if (r == -ENODATA) {
- log_debug("rtnl: received route without ifindex, ignoring");
+ log_debug("rtnl: received route message without ifindex, ignoring");
return 0;
} else if (r < 0) {
log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m");
/* when enumerating we might be out of sync, but we will
* get the route again, so just ignore it */
if (!m->enumerating)
- log_warning("rtnl: received route for link (%d) we do not know about, ignoring", ifindex);
+ log_warning("rtnl: received route message for link (%d) we do not know about, ignoring", ifindex);
return 0;
}
r = sd_rtnl_message_route_get_dst_prefixlen(message, &dst_prefixlen);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid destination prefixlen, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m");
return 0;
}
r = sd_rtnl_message_route_get_src_prefixlen(message, &src_prefixlen);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid source prefixlen, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m");
return 0;
}
r = sd_rtnl_message_route_get_scope(message, &scope);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid scope, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m");
return 0;
}
r = sd_rtnl_message_route_get_tos(message, &tos);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid tos, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m");
return 0;
}
r = sd_rtnl_message_route_get_type(message, &rt_type);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid type, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m");
return 0;
}
r = sd_rtnl_message_route_get_table(message, &table);
if (r < 0) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid table, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m");
return 0;
}
r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &priority);
if (r < 0 && r != -ENODATA) {
- log_link_warning_errno(link, r, "rtnl: received route with invalid priority, ignoring: %m");
+ log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m");
return 0;
}
if (DEBUG_LOGGING) {
_cleanup_free_ char *buf_dst = NULL, *buf_dst_prefixlen = NULL,
*buf_src = NULL, *buf_gw = NULL, *buf_prefsrc = NULL;
+ char buf_scope[ROUTE_SCOPE_STR_MAX], buf_table[ROUTE_TABLE_STR_MAX],
+ buf_protocol[ROUTE_PROTOCOL_STR_MAX];
if (!in_addr_is_null(family, &dst)) {
(void) in_addr_to_string(family, &dst, &buf_dst);
(void) in_addr_to_string(family, &prefsrc, &buf_prefsrc);
log_link_debug(link,
- "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s",
+ "%s route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s",
type == RTM_DELROUTE ? "Forgetting" : route ? "Updating remembered" : "Remembering",
strna(buf_dst), strempty(buf_dst_prefixlen),
- strna(buf_src), strna(buf_gw), strna(buf_prefsrc));
+ strna(buf_src), strna(buf_gw), strna(buf_prefsrc),
+ format_route_scope(scope, buf_scope, sizeof buf_scope),
+ format_route_table(table, buf_table, sizeof buf_table),
+ format_route_protocol(protocol, buf_protocol, sizeof buf_protocol),
+ strna(route_type_to_string(rt_type)));
}
switch (type) {
#include "missing_network.h"
#include "networkd-dhcp6.h"
+#include "networkd-manager.h"
#include "networkd-ndisc.h"
#include "networkd-route.h"
#include "strv.h"
#define NDISC_RDNSS_MAX 64U
#define NDISC_PREFIX_LFT_MIN 7200U
-static int ndisc_netlink_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+static int ndisc_netlink_route_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
int r;
assert(link);
if (link->ndisc_messages == 0) {
link->ndisc_configured = true;
+ link_request_set_routes(link);
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+static int ndisc_netlink_address_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->ndisc_messages > 0);
+
+ link->ndisc_messages--;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_error_errno(link, r, "Could not set NDisc route or address: %m");
+ else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ if (link->ndisc_messages == 0) {
+ link->ndisc_configured = true;
+ link_request_set_routes(link);
link_check_ready(link);
}
route->lifetime = time_now + lifetime * USEC_PER_SEC;
route->mtu = mtu;
- r = route_configure(route, link, ndisc_netlink_message_handler);
+ r = route_configure(route, link, ndisc_netlink_route_message_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set default route: %m");
link_enter_failed(link);
if (address->cinfo.ifa_valid == 0)
return 0;
- r = address_configure(address, link, ndisc_netlink_message_handler, true);
+ r = address_configure(address, link, ndisc_netlink_address_message_handler, true);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set SLAAC address: %m");
link_enter_failed(link);
if (r < 0)
return log_link_error_errno(link, r, "Failed to get prefix address: %m");
- r = route_configure(route, link, ndisc_netlink_message_handler);
+ r = route_configure(route, link, ndisc_netlink_route_message_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set prefix route: %m");
link_enter_failed(link);
if (r < 0)
return log_link_error_errno(link, r, "Failed to get route address: %m");
- r = route_configure(route, link, ndisc_netlink_message_handler);
+ r = route_configure(route, link, ndisc_netlink_route_message_handler);
if (r < 0) {
log_link_warning_errno(link, r, "Could not set additional route: %m");
link_enter_failed(link);
*neighbor = (Neighbor) {
.network = network,
.family = AF_UNSPEC,
+ .lladdr_type = _NEIGHBOR_LLADDR_INVALID,
};
LIST_APPEND(neighbors, network->neighbors, neighbor);
assert(link->manager);
assert(link->manager->rtnl);
- if (neighbor->family == AF_UNSPEC)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor without Address= configured");
- if (!neighbor->mac_configured)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor without MACAddress= configured");
-
r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH,
link->ifindex, neighbor->family);
if (r < 0)
if (r < 0)
return log_error_errno(r, "Could not set flags: %m");
- r = sd_netlink_message_append_ether_addr(req, NDA_LLADDR, &neighbor->mac);
+ if (neighbor->lladdr_type == NEIGHBOR_LLADDR_MAC)
+ r = sd_netlink_message_append_data(req, NDA_LLADDR, &neighbor->lladdr.mac, sizeof(neighbor->lladdr.mac));
+ else
+ r = sd_netlink_message_append_data(req, NDA_LLADDR, &neighbor->lladdr.ip.in, sizeof(neighbor->lladdr.ip.in));
if (r < 0)
return log_error_errno(r, "Could not append NDA_LLADDR attribute: %m");
return 0;
}
-int config_parse_neighbor_address(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 neighbor_section_verify(Neighbor *neighbor) {
+ if (section_is_invalid(neighbor->section))
+ return -EINVAL;
+
+ if (neighbor->family == AF_UNSPEC)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Neighbor section without Address= configured. "
+ "Ignoring [Neighbor] section from line %u.",
+ neighbor->section->filename, neighbor->section->line);
+
+ if (neighbor->lladdr_type < 0)
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "%s: Neighbor section without LinkLayerAddress= configured. "
+ "Ignoring [Neighbor] section from line %u.",
+ neighbor->section->filename, neighbor->section->line);
+
+ return 0;
+}
+
+int config_parse_neighbor_address(
+ 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) {
Network *network = userdata;
_cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
return 0;
}
-int config_parse_neighbor_hwaddr(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_neighbor_lladdr(
+ 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) {
+
+ Network *network = userdata;
+ _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = neighbor_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = ether_addr_from_string(rvalue, &n->lladdr.mac);
+ if (r >= 0)
+ n->lladdr_type = NEIGHBOR_LLADDR_MAC;
+ else {
+ r = in_addr_from_string(AF_INET, rvalue, &n->lladdr.ip);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor LinkLayerAddress= is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ n->lladdr_type = NEIGHBOR_LLADDR_IP;
+ }
+
+ TAKE_PTR(n);
+
+ return 0;
+}
+
+int config_parse_neighbor_hwaddr(
+ 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) {
Network *network = userdata;
_cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL;
if (r < 0)
return r;
- r = ether_addr_from_string(rvalue, &n->mac);
+ r = ether_addr_from_string(rvalue, &n->lladdr.mac);
if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor MACAddress is invalid, ignoring assignment: %s", rvalue);
+ log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor MACAddress= is invalid, ignoring assignment: %s", rvalue);
return 0;
}
- n->mac_configured = true;
+ n->lladdr_type = NEIGHBOR_LLADDR_MAC;
TAKE_PTR(n);
return 0;
#include "networkd-network.h"
#include "networkd-util.h"
+typedef enum {
+ NEIGHBOR_LLADDR_MAC,
+ NEIGHBOR_LLADDR_IP,
+ _NEIGHBOR_LLADDR_MAX,
+ _NEIGHBOR_LLADDR_INVALID = -1,
+} NeighborLLAddressType;
+
struct Neighbor {
Network *network;
Link *link;
int family;
union in_addr_union in_addr;
- bool mac_configured;
- struct ether_addr mac;
+ union {
+ struct ether_addr mac;
+ union in_addr_union ip;
+ } lladdr;
+ NeighborLLAddressType lladdr_type;
LIST_FIELDS(Neighbor, neighbors);
};
int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback);
+int neighbor_section_verify(Neighbor *neighbor);
+
CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_address);
CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_hwaddr);
+CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_lladdr);
IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
IPv6AddressLabel.Label, config_parse_address_label, 0, 0
Neighbor.Address, config_parse_neighbor_address, 0, 0
-Neighbor.MACAddress, config_parse_neighbor_hwaddr, 0, 0
+Neighbor.LinkLayerAddress, config_parse_neighbor_lladdr, 0, 0
+Neighbor.MACAddress, config_parse_neighbor_hwaddr, 0, 0 /* deprecated */
RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0
RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0
RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0
Route.QuickAck, config_parse_quickack, 0, 0
Route.FastOpenNoCookie, config_parse_fast_open_no_cookie, 0, 0
Route.TTLPropagate, config_parse_route_ttl_propagate, 0, 0
-DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
-DHCP.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
-DHCP.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_use_ntp)
-DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
-DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
-DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
-DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
-DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
-DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
-DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
-DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
-DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) /* deprecated */
-DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
-DHCP.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0
-DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
-DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
-DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid)
-DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
-DHCP.RouteTable, config_parse_section_route_table, 0, 0
-DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
-DHCP.IAID, config_parse_iaid, 0, 0
-DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
-DHCP.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release)
-DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
-DHCP.BlackList, config_parse_dhcp_black_listed_ip_address, 0, 0
-DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
+DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
+DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
+DHCPv4.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_use_ntp)
+DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
+DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
+DHCPv4.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCPv4.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
+DHCPv4.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
+DHCPv4.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
+DHCPv4.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
+DHCPv4.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
+DHCPv4.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
+DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0
+DHCPv4.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
+DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
+DHCPv4.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid)
+DHCPv4.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
+DHCPv4.RouteTable, config_parse_section_route_table, 0, 0
+DHCPv4.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
+DHCPv4.IAID, config_parse_iaid, 0, 0
+DHCPv4.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
+DHCPv4.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release)
+DHCPv4.BlackList, config_parse_dhcp_black_listed_ip_address, 0, 0
+DHCPv6.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp6_use_dns)
+DHCPv6.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp6_use_ntp)
+DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
+DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix)
IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns)
CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling)
/* backwards compatibility: do not add new entries to this section */
Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
-DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
-DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
-DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
+DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
+DHCP.UseDNS, config_parse_dhcp_use_dns, 0, 0
+DHCP.UseNTP, config_parse_dhcp_use_ntp, 0, 0
+DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
+DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
+DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
DHCP.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
+DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
+DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
+DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
+DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
+DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical)
+DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
+DHCP.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0
+DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
+DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
+DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid)
+DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
+DHCP.RouteTable, config_parse_section_route_table, 0, 0
+DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
+DHCP.IAID, config_parse_iaid, 0, 0
+DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
+DHCP.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release)
+DHCP.BlackList, config_parse_dhcp_black_listed_ip_address, 0, 0
+DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
+DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
-DHCPv4.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
+DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical)
fdb_entry_free(fdb);
LIST_FOREACH_SAFE(neighbors, neighbor, neighbor_next, network->neighbors)
- if (section_is_invalid(neighbor->section))
+ if (neighbor_section_verify(neighbor) < 0)
neighbor_free(neighbor);
LIST_FOREACH_SAFE(labels, label, label_next, network->address_labels)
.dhcp_use_timezone = false,
.rapid_commit = true,
+ .dhcp6_use_ntp = true,
+ .dhcp6_use_dns = true,
+
.dhcp_server_emit_dns = true,
.dhcp_server_emit_ntp = true,
.dhcp_server_emit_router = true,
"Route\0"
"DHCP\0"
"DHCPv4\0" /* compat */
+ "DHCPv6\0"
"DHCPServer\0"
"IPv6AcceptRA\0"
"IPv6NDPProxyAddress\0"
DHCPUseDomains dhcp_use_domains;
Set *dhcp_black_listed_ip;
+ /* DHCPv6 Client support*/
+ bool dhcp6_use_dns;
+ bool dhcp6_use_ntp;
+
/* DHCP Server Support */
bool dhcp_server;
bool dhcp_server_emit_dns;
#include "networkd-route.h"
#include "parse-util.h"
#include "set.h"
+#include "string-table.h"
#include "string-util.h"
+#include "strxcpyx.h"
#include "sysctl-util.h"
#include "util.h"
if (r < 0)
return log_link_error_errno(link, r, "Could not create RTM_DELROUTE message: %m");
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL;
+ char scope[ROUTE_SCOPE_STR_MAX], table[ROUTE_TABLE_STR_MAX], protocol[ROUTE_PROTOCOL_STR_MAX];
+
+ if (!in_addr_is_null(route->family, &route->dst)) {
+ (void) in_addr_to_string(route->family, &route->dst, &dst);
+ (void) asprintf(&dst_prefixlen, "/%u", route->dst_prefixlen);
+ }
+ if (!in_addr_is_null(route->family, &route->src))
+ (void) in_addr_to_string(route->family, &route->src, &src);
+ if (!in_addr_is_null(route->family, &route->gw))
+ (void) in_addr_to_string(route->family, &route->gw, &gw);
+ if (!in_addr_is_null(route->family, &route->prefsrc))
+ (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc);
+
+ log_link_debug(link, "Removing route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s",
+ strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc),
+ format_route_scope(route->scope, scope, sizeof(scope)),
+ format_route_table(route->table, table, sizeof(table)),
+ format_route_protocol(route->protocol, protocol, sizeof(protocol)),
+ strna(route_type_to_string(route->type)));
+ }
+
if (in_addr_is_null(route->family, &route->gw) == 0) {
r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->family, &route->gw);
if (r < 0)
if (DEBUG_LOGGING) {
_cleanup_free_ char *dst = NULL, *dst_prefixlen = NULL, *src = NULL, *gw = NULL, *prefsrc = NULL;
+ char scope[ROUTE_SCOPE_STR_MAX], table[ROUTE_TABLE_STR_MAX], protocol[ROUTE_PROTOCOL_STR_MAX];
if (!in_addr_is_null(route->family, &route->dst)) {
(void) in_addr_to_string(route->family, &route->dst, &dst);
if (!in_addr_is_null(route->family, &route->prefsrc))
(void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc);
- log_link_debug(link, "Configuring route: dst: %s%s, src: %s, gw: %s, prefsrc: %s",
- strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc));
+ log_link_debug(link, "Configuring route: dst: %s%s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, proto: %s, type: %s",
+ strna(dst), strempty(dst_prefixlen), strna(src), strna(gw), strna(prefsrc),
+ format_route_scope(route->scope, scope, sizeof(scope)),
+ format_route_table(route->table, table, sizeof(table)),
+ format_route_protocol(route->protocol, protocol, sizeof(protocol)),
+ strna(route_type_to_string(route->type)));
}
r = sd_rtnl_message_new_route(link->manager->rtnl, &req,
n->family = AF_INET;
n->dst_prefixlen = 16;
n->scope = RT_SCOPE_LINK;
+ n->scope_set = true;
+ n->table_set = true;
n->priority = IPV4LL_ROUTE_METRIC;
n->protocol = RTPROT_STATIC;
return 0;
}
+static const char * const route_type_table[__RTN_MAX] = {
+ [RTN_UNICAST] = "unicast",
+ [RTN_LOCAL] = "local",
+ [RTN_BROADCAST] = "broadcast",
+ [RTN_ANYCAST] = "anycast",
+ [RTN_MULTICAST] = "multicast",
+ [RTN_BLACKHOLE] = "blackhole",
+ [RTN_UNREACHABLE] = "unreachable",
+ [RTN_PROHIBIT] = "prohibit",
+ [RTN_THROW] = "throw",
+ [RTN_NAT] = "nat",
+ [RTN_XRESOLVE] = "xresolve",
+};
+
+assert_cc(__RTN_MAX <= UCHAR_MAX);
+DEFINE_STRING_TABLE_LOOKUP(route_type, int);
+
+static const char * const route_scope_table[] = {
+ [RT_SCOPE_UNIVERSE] = "global",
+ [RT_SCOPE_SITE] = "site",
+ [RT_SCOPE_LINK] = "link",
+ [RT_SCOPE_HOST] = "host",
+ [RT_SCOPE_NOWHERE] = "nowhere",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_scope, int);
+
+const char *format_route_scope(int scope, char *buf, size_t size) {
+ const char *s;
+ char *p = buf;
+
+ s = route_scope_to_string(scope);
+ if (s)
+ strpcpy(&p, size, s);
+ else
+ strpcpyf(&p, size, "%d", scope);
+
+ return buf;
+}
+
+static const char * const route_table_table[] = {
+ [RT_TABLE_DEFAULT] = "default",
+ [RT_TABLE_MAIN] = "main",
+ [RT_TABLE_LOCAL] = "local",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_table, int);
+
+const char *format_route_table(int table, char *buf, size_t size) {
+ const char *s;
+ char *p = buf;
+
+ s = route_table_to_string(table);
+ if (s)
+ strpcpy(&p, size, s);
+ else
+ strpcpyf(&p, size, "%d", table);
+
+ return buf;
+}
+
+static const char * const route_protocol_table[] = {
+ [RTPROT_KERNEL] = "kernel",
+ [RTPROT_BOOT] = "boot",
+ [RTPROT_STATIC] = "static",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(route_protocol, int);
+
+const char *format_route_protocol(int protocol, char *buf, size_t size) {
+ const char *s;
+ char *p = buf;
+
+ s = route_protocol_to_string(protocol);
+ if (s)
+ strpcpy(&p, size, s);
+ else
+ strpcpyf(&p, size, "%d", protocol);
+
+ return buf;
+}
+
int config_parse_gateway(
const char *unit,
const char *filename,
if (r < 0)
return r;
- if (streq(rvalue, "host"))
- n->scope = RT_SCOPE_HOST;
- else if (streq(rvalue, "link"))
- n->scope = RT_SCOPE_LINK;
- else if (streq(rvalue, "global"))
- n->scope = RT_SCOPE_UNIVERSE;
- else {
+ r = route_scope_from_string(rvalue);
+ if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route scope: %s", rvalue);
return 0;
}
+ n->scope = r;
+ n->scope_set = true;
TAKE_PTR(n);
return 0;
}
if (r < 0)
return r;
- r = safe_atou32(rvalue, &n->table);
- if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, r,
- "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue);
- return 0;
+ r = route_table_from_string(rvalue);
+ if (r >= 0)
+ n->table = r;
+ else {
+ r = safe_atou32(rvalue, &n->table);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
}
+ n->table_set = true;
TAKE_PTR(n);
return 0;
}
if (r < 0)
return r;
- if (streq(rvalue, "kernel"))
- n->protocol = RTPROT_KERNEL;
- else if (streq(rvalue, "boot"))
- n->protocol = RTPROT_BOOT;
- else if (streq(rvalue, "static"))
- n->protocol = RTPROT_STATIC;
+ r = route_protocol_from_string(rvalue);
+ if (r >= 0)
+ n->protocol = r;
else {
r = safe_atou8(rvalue , &n->protocol);
if (r < 0) {
Network *network = userdata;
_cleanup_(route_free_or_set_invalidp) Route *n = NULL;
- int r;
+ int t, r;
r = route_new_static(network, filename, section_line, &n);
if (r < 0)
return r;
- if (streq(rvalue, "unicast"))
- n->type = RTN_UNICAST;
- else if (streq(rvalue, "blackhole"))
- n->type = RTN_BLACKHOLE;
- else if (streq(rvalue, "unreachable"))
- n->type = RTN_UNREACHABLE;
- else if (streq(rvalue, "prohibit"))
- n->type = RTN_PROHIBIT;
- else if (streq(rvalue, "throw"))
- n->type = RTN_THROW;
- else {
- log_syntax(unit, LOG_ERR, filename, line, r,
+ t = route_type_from_string(rvalue);
+ if (t < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
"Could not parse route type \"%s\", ignoring assignment: %m", rvalue);
return 0;
}
+ n->type = (unsigned char) t;
+
TAKE_PTR(n);
return 0;
}
route->section->filename, route->section->line);
}
+ if (route->family != AF_INET6) {
+ if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT))
+ route->table = RT_TABLE_LOCAL;
+
+ if (!route->scope_set) {
+ if (IN_SET(route->type, RTN_LOCAL, RTN_NAT))
+ route->scope = RT_SCOPE_HOST;
+ else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST))
+ route->scope = RT_SCOPE_LINK;
+ }
+ }
+
if (network->n_static_addresses == 0 &&
in_addr_is_null(route->family, &route->gw) == 0 &&
route->gateway_onlink < 0) {
#pragma once
#include "conf-parser.h"
+#include "macro.h"
typedef struct Route Route;
typedef struct NetworkConfigSection NetworkConfigSection;
unsigned char dst_prefixlen;
unsigned char src_prefixlen;
unsigned char scope;
+ bool scope_set;
unsigned char protocol; /* RTPROT_* */
unsigned char type; /* RTN_* */
unsigned char tos;
uint32_t priority; /* note that ip(8) calls this 'metric' */
uint32_t table;
+ bool table_set;
uint32_t mtu;
uint32_t initcwnd;
uint32_t initrwnd;
int network_add_ipv4ll_route(Network *network);
int network_add_default_route_on_device(Network *network);
+const char* route_type_to_string(int t) _const_;
+int route_type_from_string(const char *s) _pure_;
+
+#define ROUTE_SCOPE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("nowhere") + 1)
+const char *format_route_scope(int scope, char *buf, size_t size);
+
+#define ROUTE_TABLE_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("default") + 1)
+const char *format_route_table(int table, char *buf, size_t size);
+
+#define ROUTE_PROTOCOL_STR_MAX CONST_MAX(DECIMAL_STR_MAX(int), STRLEN("kernel") + 1)
+const char *format_route_protocol(int protocol, char *buf, size_t size);
+
CONFIG_PARSER_PROTOTYPE(config_parse_gateway);
CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src);
CONFIG_PARSER_PROTOTYPE(config_parse_destination);
our_env[i++] = (char*) "READY=1";
if (arg_status) {
- status = strappend("STATUS=", arg_status);
+ status = strjoin("STATUS=", arg_status);
if (!status)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to add netlink interface index: %m");
- n = strappend("mv-", *i);
+ n = strjoin("mv-", *i);
if (!n)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to add netlink interface index: %m");
- n = strappend("iv-", *i);
+ n = strjoin("iv-", *i);
if (!n)
return log_oom();
assert(lvalue);
assert(rvalue);
- j = strappend("vz-", rvalue);
+ j = strjoin("vz-", rvalue);
if (!ifname_valid(j)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid network zone name, ignoring: %s", rvalue);
return 0;
case ARG_NETWORK_ZONE: {
char *j;
- j = strappend("vz-", optarg);
+ j = strjoin("vz-", optarg);
if (!j)
return log_oom();
}
if (route_only)
- str = strappend("~", domain);
+ str = strjoin("~", domain);
else
str = strdup(domain);
if (!str)
if (!soa->soa.mname)
return -ENOMEM;
- soa->soa.rname = strappend("root.", name);
+ soa->soa.rname = strjoin("root.", name);
if (!soa->soa.rname)
return -ENOMEM;
return -EBADMSG;
/* Replace the label we stripped off with an asterisk */
- wc = strappend("*.", name);
+ wc = strjoin("*.", name);
if (!wc)
return -ENOMEM;
continue;
rm:
- p = strappend("/run/systemd/resolve/netif/", de->d_name);
+ p = path_join("/run/systemd/resolve/netif", de->d_name);
if (!p) {
log_oom();
return;
special = special_glyph(more ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE);
- pp = strappend(prefix, special);
+ pp = strjoin(prefix, special);
if (!pp)
return -ENOMEM;
if (!is_ex_prop && (flags & (EXEC_COMMAND_NO_ENV_EXPAND|EXEC_COMMAND_FULLY_PRIVILEGED|EXEC_COMMAND_NO_SETUID|EXEC_COMMAND_AMBIENT_MAGIC))) {
/* Upgrade the ExecXYZ= property to ExecXYZEx= for convenience */
is_ex_prop = true;
- upgraded_name = strappend(field, "Ex");
+ upgraded_name = strjoin(field, "Ex");
if (!upgraded_name)
return log_oom();
}
return log_oom();
}
- name_with_equal = strappend(name, "=");
+ name_with_equal = strjoin(name, "=");
if (!name_with_equal)
return log_oom();
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "bus-util.h"
+#include "bus-wait-for-units.h"
+#include "hashmap.h"
+#include "string-util.h"
+#include "strv.h"
+#include "unit-def.h"
+
+typedef struct WaitForItem {
+ BusWaitForUnits *parent;
+
+ BusWaitForUnitsFlags flags;
+
+ char *bus_path;
+
+ sd_bus_slot *slot_get_all;
+ sd_bus_slot *slot_properties_changed;
+
+ bus_wait_for_units_unit_callback unit_callback;
+ void *userdata;
+
+ char *active_state;
+ uint32_t job_id;
+ char *clean_result;
+} WaitForItem;
+
+typedef struct BusWaitForUnits {
+ sd_bus *bus;
+ sd_bus_slot *slot_disconnected;
+
+ Hashmap *items;
+
+ bus_wait_for_units_ready_callback ready_callback;
+ void *userdata;
+
+ WaitForItem *current;
+
+ BusWaitForUnitsState state;
+ bool has_failed:1;
+} BusWaitForUnits;
+
+static WaitForItem *wait_for_item_free(WaitForItem *item) {
+ int r;
+
+ if (!item)
+ return NULL;
+
+ if (item->parent) {
+ if (FLAGS_SET(item->flags, BUS_WAIT_REFFED) && item->bus_path && item->parent->bus) {
+ r = sd_bus_call_method_async(
+ item->parent->bus,
+ NULL,
+ "org.freedesktop.systemd1",
+ item->bus_path,
+ "org.freedesktop.systemd1.Unit",
+ "Unref",
+ NULL,
+ NULL,
+ NULL);
+ if (r < 0)
+ log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path);
+ }
+
+ assert_se(hashmap_remove(item->parent->items, item->bus_path) == item);
+
+ if (item->parent->current == item)
+ item->parent->current = NULL;
+ }
+
+ sd_bus_slot_unref(item->slot_properties_changed);
+ sd_bus_slot_unref(item->slot_get_all);
+
+ free(item->bus_path);
+ free(item->active_state);
+ free(item->clean_result);
+
+ return mfree(item);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free);
+
+static void bus_wait_for_units_clear(BusWaitForUnits *d) {
+ WaitForItem *item;
+
+ assert(d);
+
+ d->slot_disconnected = sd_bus_slot_unref(d->slot_disconnected);
+ d->bus = sd_bus_unref(d->bus);
+
+ while ((item = hashmap_first(d->items))) {
+ d->current = item;
+
+ item->unit_callback(d, item->bus_path, false, item->userdata);
+ wait_for_item_free(item);
+ }
+
+ d->items = hashmap_free(d->items);
+}
+
+static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ BusWaitForUnits *d = userdata;
+
+ assert(m);
+ assert(d);
+
+ log_error("Warning! D-Bus connection terminated.");
+
+ bus_wait_for_units_clear(d);
+
+ if (d->ready_callback)
+ d->ready_callback(d, false, d->userdata);
+ else /* If no ready callback is specified close the connection so that the event loop exits */
+ sd_bus_close(sd_bus_message_get_bus(m));
+
+ return 0;
+}
+
+int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret) {
+ _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *d = NULL;
+ int r;
+
+ assert(bus);
+ assert(ret);
+
+ d = new(BusWaitForUnits, 1);
+ if (!d)
+ return -ENOMEM;
+
+ *d = (BusWaitForUnits) {
+ .state = BUS_WAIT_SUCCESS,
+ .bus = sd_bus_ref(bus),
+ };
+
+ r = sd_bus_match_signal_async(
+ bus,
+ &d->slot_disconnected,
+ "org.freedesktop.DBus.Local",
+ NULL,
+ "org.freedesktop.DBus.Local",
+ "Disconnected",
+ match_disconnected, NULL, d);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(d);
+ return 0;
+}
+
+BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d) {
+ if (!d)
+ return NULL;
+
+ bus_wait_for_units_clear(d);
+ sd_bus_slot_unref(d->slot_disconnected);
+ sd_bus_unref(d->bus);
+
+ return mfree(d);
+}
+
+static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) {
+ assert(d);
+
+ if (!d->bus) /* Disconnected? */
+ return true;
+
+ return hashmap_isempty(d->items);
+}
+
+void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) {
+ assert(d);
+
+ d->ready_callback = callback;
+ d->userdata = userdata;
+}
+
+static void bus_wait_for_units_check_ready(BusWaitForUnits *d) {
+ assert(d);
+
+ if (!bus_wait_for_units_is_ready(d))
+ return;
+
+ d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS;
+
+ if (d->ready_callback)
+ d->ready_callback(d, d->state, d->userdata);
+}
+
+static void wait_for_item_check_ready(WaitForItem *item) {
+ BusWaitForUnits *d;
+
+ assert(item);
+ assert(d = item->parent);
+
+ if (FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END)) {
+
+ if (item->clean_result && !streq(item->clean_result, "success"))
+ d->has_failed = true;
+
+ if (!item->active_state || streq(item->active_state, "maintenance"))
+ return;
+ }
+
+ if (FLAGS_SET(item->flags, BUS_WAIT_NO_JOB) && item->job_id != 0)
+ return;
+
+ if (FLAGS_SET(item->flags, BUS_WAIT_FOR_INACTIVE)) {
+
+ if (streq_ptr(item->active_state, "failed"))
+ d->has_failed = true;
+ else if (!streq_ptr(item->active_state, "inactive"))
+ return;
+ }
+
+ if (item->unit_callback) {
+ d->current = item;
+ item->unit_callback(d, item->bus_path, true, item->userdata);
+ }
+
+ wait_for_item_free(item);
+
+ bus_wait_for_units_check_ready(d);
+}
+
+static int property_map_job(
+ sd_bus *bus,
+ const char *member,
+ sd_bus_message *m,
+ sd_bus_error *error,
+ void *userdata) {
+
+ WaitForItem *item = userdata;
+ const char *path;
+ uint32_t id;
+ int r;
+
+ assert(item);
+
+ r = sd_bus_message_read(m, "(uo)", &id, &path);
+ if (r < 0)
+ return r;
+
+ item->job_id = id;
+ return 0;
+}
+
+static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) {
+
+ static const struct bus_properties_map map[] = {
+ { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) },
+ { "Job", "(uo)", property_map_job, 0 },
+ { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) },
+ {}
+ };
+
+ int r;
+
+ assert(item);
+ assert(m);
+
+ r = bus_message_map_all_properties(m, map, BUS_MAP_STRDUP, NULL, item);
+ if (r < 0)
+ return r;
+
+ wait_for_item_check_ready(item);
+ return 0;
+}
+
+static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ WaitForItem *item = userdata;
+ const char *interface;
+ int r;
+
+ assert(item);
+
+ r = sd_bus_message_read(m, "s", &interface);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse PropertiesChanged signal: %m");
+ return 0;
+ }
+
+ if (!streq(interface, "org.freedesktop.systemd1.Unit"))
+ return 0;
+
+ r = wait_for_item_parse_properties(item, m);
+ if (r < 0)
+ log_debug_errno(r, "Failed to process PropertiesChanged signal: %m");
+
+ return 0;
+}
+
+static int on_get_all_properties(sd_bus_message *m, void *userdata, sd_bus_error *error) {
+ WaitForItem *item = userdata;
+ int r;
+
+ assert(item);
+
+ if (sd_bus_error_is_set(error)) {
+ BusWaitForUnits *d = item->parent;
+
+ d->has_failed = true;
+
+ log_debug_errno(sd_bus_error_get_errno(error), "GetAll() failed for %s: %s",
+ item->bus_path, error->message);
+
+ d->current = item;
+ item->unit_callback(d, item->bus_path, false, item->userdata);
+ wait_for_item_free(item);
+
+ bus_wait_for_units_check_ready(d);
+ return 0;
+ }
+
+ r = wait_for_item_parse_properties(item, m);
+ if (r < 0)
+ log_debug_errno(r, "Failed to process GetAll method reply: %m");
+
+ return 0;
+}
+
+int bus_wait_for_units_add_unit(
+ BusWaitForUnits *d,
+ const char *unit,
+ BusWaitForUnitsFlags flags,
+ bus_wait_for_units_unit_callback callback,
+ void *userdata) {
+
+ _cleanup_(wait_for_item_freep) WaitForItem *item = NULL;
+ int r;
+
+ assert(d);
+ assert(unit);
+
+ assert(flags != 0);
+
+ r = hashmap_ensure_allocated(&d->items, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ item = new(WaitForItem, 1);
+ if (!item)
+ return -ENOMEM;
+
+ *item = (WaitForItem) {
+ .flags = flags,
+ .bus_path = unit_dbus_path_from_name(unit),
+ .unit_callback = callback,
+ .userdata = userdata,
+ .job_id = UINT32_MAX,
+ };
+
+ if (!item->bus_path)
+ return -ENOMEM;
+
+ if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) {
+ r = sd_bus_call_method_async(
+ d->bus,
+ NULL,
+ "org.freedesktop.systemd1",
+ item->bus_path,
+ "org.freedesktop.systemd1.Unit",
+ "Ref",
+ NULL,
+ NULL,
+ NULL);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to add reference to unit %s: %m", unit);
+
+
+ item->flags |= BUS_WAIT_REFFED;
+ }
+
+ r = sd_bus_match_signal_async(
+ d->bus,
+ &item->slot_properties_changed,
+ "org.freedesktop.systemd1",
+ item->bus_path,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ on_properties_changed,
+ NULL,
+ item);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to request match for PropertiesChanged signal: %m");
+
+ r = sd_bus_call_method_async(
+ d->bus,
+ &item->slot_get_all,
+ "org.freedesktop.systemd1",
+ item->bus_path,
+ "org.freedesktop.DBus.Properties",
+ "GetAll",
+ on_get_all_properties,
+ item,
+ "s", FLAGS_SET(item->flags, BUS_WAIT_FOR_MAINTENANCE_END) ? NULL : "org.freedesktop.systemd1.Unit");
+ if (r < 0)
+ return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit);
+
+ r = hashmap_put(d->items, item->bus_path, item);
+ if (r < 0)
+ return r;
+
+ d->state = BUS_WAIT_RUNNING;
+ item->parent = d;
+ TAKE_PTR(item);
+ return 0;
+}
+
+int bus_wait_for_units_run(BusWaitForUnits *d) {
+ int r;
+
+ assert(d);
+
+ while (d->state == BUS_WAIT_RUNNING) {
+
+ r = sd_bus_process(d->bus, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
+
+ r = sd_bus_wait(d->bus, (uint64_t) -1);
+ if (r < 0)
+ return r;
+ }
+
+ return d->state;
+}
+
+BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d) {
+ assert(d);
+
+ return d->state;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "macro.h"
+#include "sd-bus.h"
+
+typedef struct BusWaitForUnits BusWaitForUnits;
+
+typedef enum BusWaitForUnitsState {
+ BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */
+ BUS_WAIT_FAILURE, /* dito, but something failed */
+ BUS_WAIT_RUNNING, /* Still something to wait for */
+ _BUS_WAIT_FOR_UNITS_STATE_MAX,
+ _BUS_WAIT_FOR_UNITS_STATE_INVALID = -1,
+} BusWaitForUnitsState;
+
+typedef enum BusWaitForUnitsFlags {
+ BUS_WAIT_FOR_MAINTENANCE_END = 1 << 0, /* Wait until the unit is no longer in maintenance state */
+ BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */
+ BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */
+ BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */
+} BusWaitForUnitsFlags;
+
+typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata);
+typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata);
+
+int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret);
+BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d);
+
+BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d);
+void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata);
+int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata);
+int bus_wait_for_units_run(BusWaitForUnits *d);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free);
printf("%s%s%s\n", prefix, special_glyph(SPECIAL_GLYPH_TREE_BRANCH), cg_unescape(basename(last)));
if (!p1) {
- p1 = strappend(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
+ p1 = strjoin(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
if (!p1)
return -ENOMEM;
}
printf("%s%s%s\n", prefix, special_glyph(SPECIAL_GLYPH_TREE_RIGHT), cg_unescape(basename(last)));
if (!p2) {
- p2 = strappend(prefix, " ");
+ p2 = strjoin(prefix, " ");
if (!p2)
return -ENOMEM;
}
errno = 0;
b = blkid_new_probe_from_filename(node);
if (!b)
- return -errno ?: -ENOMEM;
+ return errno_or_else(ENOMEM);
blkid_probe_enable_superblocks(b, 1);
blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
return -EUCLEAN;
}
if (r != 0)
- return -errno ?: -EIO;
+ return errno_or_else(EIO);
(void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
errno = 0;
r = blkid_probe_set_device(b, fd, 0, 0);
if (r != 0)
- return -errno ?: -ENOMEM;
+ return errno_or_else(ENOMEM);
if ((flags & DISSECT_IMAGE_GPT_ONLY) == 0) {
/* Look for file system superblocks, unless we only shall look for GPT partition tables */
if (IN_SET(r, -2, 1))
return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to identify any partition table.");
if (r != 0)
- return -errno ?: -EIO;
+ return errno_or_else(EIO);
m = new0(DissectedImage, 1);
if (!m)
errno = 0;
pl = blkid_probe_get_partitions(b);
if (!pl)
- return -errno ?: -ENOMEM;
+ return errno_or_else(ENOMEM);
r = loop_wait_for_partitions_to_appear(fd, d, blkid_partlist_numof_partitions(pl), flags, &e);
if (r < 0)
if (path_equal(where, "/")) {
const char *lnk;
- lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/"SPECIAL_FSCK_ROOT_SERVICE);
+ lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/" SPECIAL_FSCK_ROOT_SERVICE);
(void) mkdir_parents(lnk, 0755);
- if (symlink(SYSTEM_DATA_UNIT_PATH "/"SPECIAL_FSCK_ROOT_SERVICE, lnk) < 0)
+ if (symlink(SYSTEM_DATA_UNIT_PATH "/" SPECIAL_FSCK_ROOT_SERVICE, lnk) < 0)
return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
} else {
_cleanup_free_ char *_fsck = NULL;
- const char *fsck;
+ const char *fsck, *dep;
if (in_initrd() && path_equal(where, "/sysroot")) {
r = write_fsck_sysroot_service(dir, what);
return r;
fsck = SPECIAL_FSCK_ROOT_SERVICE;
+ dep = "Requires";
} else {
+ /* When this is /usr, then let's add a Wants= dependency, otherwise a Requires=
+ * dependency. Why? We can't possibly unmount /usr during shutdown, but if we have a
+ * Requires= from /usr onto a fsck@.service unit and that unit is shut down, then
+ * we'd have to unmount /usr too. */
+
+ dep = !in_initrd() && path_equal(where, "/usr") ? "Wants" : "Requires";
+
r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck);
if (r < 0)
return log_error_errno(r, "Failed to create fsck service name: %m");
}
fprintf(f,
- "Requires=%1$s\n"
- "After=%1$s\n",
- fsck);
+ "%1$s=%2$s\n"
+ "After=%2$s\n",
+ dep, fsck);
}
return 0;
has_vendor = true;
}
- dropin = strappend(path, ".d");
+ dropin = strjoin(path, ".d");
if (!dropin)
return -ENOMEM;
assert_return(ret, -EINVAL);
- v = malloc0(offsetof(JsonVariant, value) + space);
+ v = malloc0(MAX(sizeof(JsonVariant),
+ offsetof(JsonVariant, value) + space));
if (!v)
return -ENOMEM;
default:
/* Everything else copy by reference */
- c = malloc0(offsetof(JsonVariant, reference) + sizeof(JsonVariant*));
+ c = malloc0(MAX(sizeof(JsonVariant),
+ offsetof(JsonVariant, reference) + sizeof(JsonVariant*)));
if (!c)
return -ENOMEM;
return 0;
}
- c = malloc0(offsetof(JsonVariant, value) + k);
+ c = malloc0(MAX(sizeof(JsonVariant),
+ offsetof(JsonVariant, value) + k));
if (!c)
return -ENOMEM;
fn = strjoina(image->name, ".nspawn");
- FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") {
- l[i] = strappend(s, fn);
+ FOREACH_STRING(s, "/etc/systemd/nspawn", "/run/systemd/nspawn") {
+ l[i] = path_join(s, fn);
if (!l[i])
return NULL;
if (errno != ENOENT)
return -errno;
- raw = strappend(name, ".raw");
+ raw = strjoin(name, ".raw");
if (!raw)
return -ENOMEM;
bus-util.h
bus-wait-for-jobs.c
bus-wait-for-jobs.h
+ bus-wait-for-units.c
+ bus-wait-for-units.h
calendarspec.c
calendarspec.h
cgroup-show.c
if (!e)
return -ENXIO;
- j = strappend(e, suffix);
+ j = strjoin(e, suffix);
if (!j)
return -ENOMEM;
e = getenv("XDG_CONFIG_HOME");
if (e)
- j = strappend(e, suffix);
+ j = strjoin(e, suffix);
else {
_cleanup_free_ char *home = NULL;
e = getenv("XDG_DATA_HOME");
if (e)
- j = strappend(e, suffix);
+ j = strjoin(e, suffix);
else {
_cleanup_free_ char *home = NULL;
prefix = strjoina(e, "/systemd");
}
- x = strappend(prefix, "/generator");
+ x = path_join(prefix, "generator");
if (!x)
return -ENOMEM;
- y = strappend(prefix, "/generator.early");
+ y = path_join(prefix, "generator.early");
if (!y)
return -ENOMEM;
- z = strappend(prefix, "/generator.late");
+ z = path_join(prefix, "generator.late");
if (!z)
return -ENOMEM;
if (path_startswith(optarg, "/proc/sys"))
p = strdup(optarg);
else
- p = strappend("/proc/sys/", optarg);
+ p = path_join("/proc/sys", optarg);
if (!p)
return log_oom();
#include "bus-unit-util.h"
#include "bus-util.h"
#include "bus-wait-for-jobs.h"
+#include "bus-wait-for-units.h"
#include "cgroup-show.h"
#include "cgroup-util.h"
#include "copy.h"
static bool arg_now = false;
static bool arg_jobs_before = false;
static bool arg_jobs_after = false;
+static char **arg_clean_what = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_wall, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_types, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_states, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_properties, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_clean_what, strv_freep);
static int daemon_reload(int argc, char *argv[], void* userdata);
static int trivial_method(int argc, char *argv[], void *userdata);
return "start";
}
-typedef struct {
- sd_bus_slot *match;
- sd_event *event;
- Set *unit_paths;
- bool any_failed;
-} WaitContext;
-
-static void wait_context_free(WaitContext *c) {
- c->match = sd_bus_slot_unref(c->match);
- c->event = sd_event_unref(c->event);
- c->unit_paths = set_free_free(c->unit_paths);
-}
-
-static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
- const char *path, *interface, *active_state = NULL, *job_path = NULL;
- WaitContext *c = userdata;
- bool is_failed;
- int r;
-
- /* Called whenever we get a PropertiesChanged signal. Checks if ActiveState changed to inactive/failed.
- *
- * Signal parameters: (s interface, a{sv} changed_properties, as invalidated_properties) */
-
- path = sd_bus_message_get_path(m);
- if (!set_contains(c->unit_paths, path))
- return 0;
-
- r = sd_bus_message_read(m, "s", &interface);
- if (r < 0)
- return bus_log_parse_error(r);
-
- if (!streq(interface, "org.freedesktop.systemd1.Unit")) /* ActiveState is on the Unit interface */
- return 0;
-
- r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
- if (r < 0)
- return bus_log_parse_error(r);
-
- for (;;) {
- const char *s;
-
- r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv");
- if (r < 0)
- return bus_log_parse_error(r);
- if (r == 0) /* end of array */
- break;
-
- r = sd_bus_message_read(m, "s", &s); /* Property name */
- if (r < 0)
- return bus_log_parse_error(r);
-
- if (streq(s, "ActiveState")) {
- r = sd_bus_message_read(m, "v", "s", &active_state);
- if (r < 0)
- return bus_log_parse_error(r);
-
- if (job_path) /* Found everything we need */
- break;
-
- } else if (streq(s, "Job")) {
- uint32_t job_id;
-
- r = sd_bus_message_read(m, "v", "(uo)", &job_id, &job_path);
- if (r < 0)
- return bus_log_parse_error(r);
-
- /* There's still a job pending for this unit, let's ignore this for now, and return right-away. */
- if (job_id != 0)
- return 0;
-
- if (active_state) /* Found everything we need */
- break;
-
- } else {
- r = sd_bus_message_skip(m, "v"); /* Other property */
- if (r < 0)
- return bus_log_parse_error(r);
- }
-
- r = sd_bus_message_exit_container(m);
- if (r < 0)
- return bus_log_parse_error(r);
- }
-
- /* If this didn't contain the ActiveState property we can't do anything */
- if (!active_state)
- return 0;
-
- is_failed = streq(active_state, "failed");
- if (streq(active_state, "inactive") || is_failed) {
- log_debug("%s became %s, dropping from --wait tracking", path, active_state);
- free(set_remove(c->unit_paths, path));
- c->any_failed = c->any_failed || is_failed;
- } else
- log_debug("ActiveState on %s changed to %s", path, active_state);
-
- if (set_isempty(c->unit_paths))
- sd_event_exit(c->event, EXIT_SUCCESS);
-
- return 0;
-}
-
-static int wait_context_watch(
- WaitContext *wait_context,
- sd_bus *bus,
- const char *name) {
-
- _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- _cleanup_free_ char *unit_path = NULL;
- int r;
-
- assert(wait_context);
- assert(name);
-
- log_debug("Watching for property changes of %s", name);
- r = sd_bus_call_method(
- bus,
- "org.freedesktop.systemd1",
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "RefUnit",
- &error,
- NULL,
- "s", name);
- if (r < 0)
- return log_error_errno(r, "Failed to add reference to unit %s: %s", name, bus_error_message(&error, r));
-
- unit_path = unit_dbus_path_from_name(name);
- if (!unit_path)
- return log_oom();
-
- r = set_ensure_allocated(&wait_context->unit_paths, &string_hash_ops);
- if (r < 0)
- return log_oom();
-
- r = set_put_strdup(wait_context->unit_paths, unit_path);
- if (r < 0)
- return log_error_errno(r, "Failed to add unit path %s to set: %m", unit_path);
-
- r = sd_bus_match_signal_async(bus,
- &wait_context->match,
- NULL,
- unit_path,
- "org.freedesktop.DBus.Properties",
- "PropertiesChanged",
- on_properties_changed, NULL, wait_context);
- if (r < 0)
- return log_error_errno(r, "Failed to request match for PropertiesChanged signal: %m");
-
- return 0;
-}
-
static int start_unit_one(
sd_bus *bus,
const char *method, /* When using classic per-job bus methods */
const char *mode,
sd_bus_error *error,
BusWaitForJobs *w,
- WaitContext *wait_context) {
+ BusWaitForUnits *wu) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *path;
assert(mode);
assert(error);
- if (wait_context) {
- r = wait_context_watch(wait_context, bus, name);
- if (r < 0)
- return r;
- }
-
log_debug("%s dbus call org.freedesktop.systemd1.Manager %s(%s, %s)",
arg_dry_run ? "Would execute" : "Executing",
method, name, mode);
return log_error_errno(r, "Failed to watch job for %s: %m", name);
}
+ if (wu) {
+ r = bus_wait_for_units_add_unit(wu, name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch unit %s: %m", name);
+ }
+
return 0;
fail:
}
static int start_unit(int argc, char *argv[], void *userdata) {
+ _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *wu = NULL;
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
- _cleanup_(wait_context_free) WaitContext wait_context = {};
const char *method, *job_type, *mode, *one_name, *suffix = NULL;
_cleanup_free_ char **stopped_units = NULL; /* Do not use _cleanup_strv_free_ */
_cleanup_strv_free_ char **names = NULL;
if (r < 0)
return log_error_errno(r, "Failed to enable subscription: %m");
- r = sd_event_default(&wait_context.event);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate event loop: %m");
-
- r = sd_bus_attach_event(bus, wait_context.event, 0);
+ r = bus_wait_for_units_new(bus, &wu);
if (r < 0)
- return log_error_errno(r, "Failed to attach bus to event loop: %m");
+ return log_error_errno(r, "Failed to allocate unit watch context: %m");
}
STRV_FOREACH(name, names) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- r = start_unit_one(bus, method, job_type, *name, mode, &error, w, arg_wait ? &wait_context : NULL);
+ r = start_unit_one(bus, method, job_type, *name, mode, &error, w, wu);
if (ret == EXIT_SUCCESS && r < 0)
ret = translate_bus_error_to_exit_status(r, &error);
(void) check_triggering_units(bus, *name);
}
- if (ret == EXIT_SUCCESS && arg_wait && !set_isempty(wait_context.unit_paths)) {
- r = sd_event_loop(wait_context.event);
+ if (arg_wait) {
+ r = bus_wait_for_units_run(wu);
if (r < 0)
- return log_error_errno(r, "Failed to run event loop: %m");
- if (wait_context.any_failed)
+ return log_error_errno(r, "Failed to wait for units: %m");
+ if (r == BUS_WAIT_FAILURE && ret == EXIT_SUCCESS)
ret = EXIT_FAILURE;
}
return r;
}
+static int clean_unit(int argc, char *argv[], void *userdata) {
+ _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL;
+ _cleanup_strv_free_ char **names = NULL;
+ int r, ret = EXIT_SUCCESS;
+ char **name;
+ sd_bus *bus;
+
+ r = acquire_bus(BUS_FULL, &bus);
+ if (r < 0)
+ return r;
+
+ polkit_agent_open_maybe();
+
+ if (!arg_clean_what) {
+ arg_clean_what = strv_new("cache", "runtime");
+ if (!arg_clean_what)
+ return log_oom();
+ }
+
+ r = expand_names(bus, strv_skip(argv, 1), NULL, &names);
+ if (r < 0)
+ return log_error_errno(r, "Failed to expand names: %m");
+
+ if (!arg_no_block) {
+ r = bus_wait_for_units_new(bus, &w);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate unit waiter: %m");
+ }
+
+ STRV_FOREACH(name, names) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+ if (w) {
+ /* If we shall wait for the cleaning to complete, let's add a ref on the unit first */
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "RefUnit",
+ &error,
+ NULL,
+ "s", *name);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add reference to unit %s: %s", *name, bus_error_message(&error, r));
+ if (ret == EXIT_SUCCESS)
+ ret = r;
+ continue;
+ }
+ }
+
+ r = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "CleanUnit");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", *name);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_strv(m, arg_clean_what);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, 0, &error, NULL);
+ if (r < 0) {
+ log_error_errno(r, "Failed to clean unit %s: %s", *name, bus_error_message(&error, r));
+ if (ret == EXIT_SUCCESS) {
+ ret = r;
+ continue;
+ }
+ }
+
+ if (w) {
+ r = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_REFFED|BUS_WAIT_FOR_MAINTENANCE_END, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to watch unit %s: %m", *name);
+ }
+ }
+
+ r = bus_wait_for_units_run(w);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for units: %m");
+ if (r == BUS_WAIT_FAILURE)
+ ret = EXIT_FAILURE;
+
+ return ret;
+}
+
typedef struct ExecStatusInfo {
char *name;
}
if (!isempty(arg_root)) {
- q = strappend("--root=", arg_root);
+ q = strjoin("--root=", arg_root);
if (!q)
return log_oom();
" When shutting down or sleeping, ignore inhibitors\n"
" --kill-who=WHO Who to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
+ " --what=RESOURCES Which types of resources to remove\n"
" --now Start or stop unit in addition to enabling or disabling it\n"
" --dry-run Only print what would be done\n"
" -q --quiet Suppress output\n"
" if supported, otherwise restart\n"
" isolate UNIT Start one unit and stop all others\n"
" kill UNIT... Send signal to processes of a unit\n"
+ " clean UNIT... Clean runtime, cache, state, logs or\n"
+ " or configuration of unit\n"
" is-active PATTERN... Check whether units are active\n"
" is-failed PATTERN... Check whether units are failed\n"
" status [PATTERN...|PID...] Show runtime status of one or more units\n"
ARG_NOW,
ARG_MESSAGE,
ARG_WAIT,
+ ARG_WHAT,
};
static const struct option options[] = {
{ "now", no_argument, NULL, ARG_NOW },
{ "message", required_argument, NULL, ARG_MESSAGE },
{ "show-transaction", no_argument, NULL, 'T' },
+ { "what", required_argument, NULL, ARG_WHAT },
{}
};
arg_show_transaction = true;
break;
+ case ARG_WHAT: {
+ const char *p;
+
+ if (isempty(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--what= requires arguments.");
+
+ for (p = optarg;;) {
+ _cleanup_free_ char *k = NULL;
+
+ r = extract_first_word(&p, &k, ",", 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse directory type: %s", optarg);
+ if (r == 0)
+ break;
+
+ if (streq(k, "help")) {
+ puts("runtime\n"
+ "state\n"
+ "cache\n"
+ "logs\n"
+ "configuration");
+ return 0;
+ }
+
+ r = strv_consume(&arg_clean_what, TAKE_PTR(k));
+ if (r < 0)
+ return log_oom();
+ }
+
+ break;
+ }
+
case '.':
/* Output an error mimicking getopt, and print a hint afterwards */
log_error("%s: invalid option -- '.'", program_invocation_name);
{ "condrestart", 2, VERB_ANY, VERB_ONLINE_ONLY, start_unit }, /* For compatibility with RH */
{ "isolate", 2, 2, VERB_ONLINE_ONLY, start_unit },
{ "kill", 2, VERB_ANY, VERB_ONLINE_ONLY, kill_unit },
+ { "clean", 2, VERB_ANY, VERB_ONLINE_ONLY, clean_unit },
{ "is-active", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active },
{ "check", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_active }, /* deprecated alias of is-active */
{ "is-failed", 2, VERB_ANY, VERB_ONLINE_ONLY, check_unit_failed },
if (description) {
char *d;
- d = strappend(s->has_lsb ? "LSB: " : "SYSV: ", description);
+ d = strjoin(s->has_lsb ? "LSB: " : "SYSV: ", description);
if (!d)
return log_oom();
while (files) {
_cleanup_free_ char *path;
- assert_se(path = strappend(tmp_dir, files));
+ assert_se(path = path_join(tmp_dir, files));
(void) mkdir_parents(path, 0755);
assert_se(write_string_file(path, "foobar", WRITE_STRING_FILE_CREATE) >= 0);
STRV_FOREACH(p, files) {
_cleanup_free_ char *f;
- assert_se(f = strappend(original_dir, *p));
+ assert_se(f = path_join(original_dir, *p));
assert_se(mkdir_parents(f, 0755) >= 0);
assert_se(write_string_file(f, "file", WRITE_STRING_FILE_CREATE) == 0);
STRV_FOREACH_PAIR(link, p, links) {
_cleanup_free_ char *f, *l;
- assert_se(f = strappend(original_dir, *p));
- assert_se(l = strappend(original_dir, *link));
+ assert_se(f = path_join(original_dir, *p));
+ assert_se(l = path_join(original_dir, *link));
assert_se(mkdir_parents(l, 0755) >= 0);
assert_se(symlink(f, l) == 0);
_cleanup_free_ char *buf, *f;
size_t sz;
- assert_se(f = strappend(copy_dir, *p));
+ assert_se(f = path_join(copy_dir, *p));
assert_se(access(f, F_OK) == 0);
assert_se(read_full_file(f, &buf, &sz) == 0);
#include "rm-rf.h"
#include "test-helper.h"
#include "tests.h"
+#include "service.h"
int main(int argc, char *argv[]) {
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL;
_cleanup_(manager_freep) Manager *m = NULL;
- Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL, *h = NULL, *unit_with_multiple_dashes = NULL;
+ Unit *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *g = NULL,
+ *h = NULL, *i = NULL, *a_conj = NULL, *unit_with_multiple_dashes = NULL;
Job *j;
int r;
assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, NULL, NULL, &j) == 0);
manager_dump_jobs(m, stdout, "\t");
+ printf("Load5:\n");
+ manager_clear_jobs(m);
+ assert_se(manager_load_startable_unit_or_warn(m, "i.service", NULL, &i) >= 0);
+ SERVICE(a)->state = SERVICE_RUNNING;
+ SERVICE(d)->state = SERVICE_RUNNING;
+ manager_dump_units(m, stdout, "\t");
+
+ printf("Test11: (Start/stop job ordering, execution cycle)\n");
+ assert_se(manager_add_job(m, JOB_START, i, JOB_FAIL, NULL, NULL, &j) == 0);
+ assert_se(a->job && a->job->type == JOB_STOP);
+ assert_se(d->job && d->job->type == JOB_STOP);
+ assert_se(b->job && b->job->type == JOB_START);
+ manager_dump_jobs(m, stdout, "\t");
+
+ printf("Load6:\n");
+ manager_clear_jobs(m);
+ assert_se(manager_load_startable_unit_or_warn(m, "a-conj.service", NULL, &a_conj) >= 0);
+ SERVICE(a)->state = SERVICE_DEAD;
+ manager_dump_units(m, stdout, "\t");
+
+ printf("Test12: (Trivial cycle, Unfixable)\n");
+ assert_se(manager_add_job(m, JOB_START, a_conj, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK);
+ manager_dump_jobs(m, stdout, "\t");
+
assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], b));
assert_se(!hashmap_get(b->dependencies[UNIT_RELOAD_PROPAGATED_FROM], a));
assert_se(!hashmap_get(a->dependencies[UNIT_PROPAGATES_RELOAD_TO], c));
search_dirs = strv_new("/dir1", "/dir2", "/dir3");
assert_se(search_dirs);
STRV_FOREACH(d, search_dirs) {
- char *p = strappend(tmp_dir, *d);
+ char *p = path_join(tmp_dir, *d);
assert_se(p);
assert_se(strv_push(&absolute_dirs, p) == 0);
}
assert_se(streq(zero, ""));
}
-static void test_strappend(void) {
- _cleanup_free_ char *t1, *t2, *t3, *t4;
-
- t1 = strappend(NULL, NULL);
- assert_se(streq(t1, ""));
-
- t2 = strappend(NULL, "suf");
- assert_se(streq(t2, "suf"));
-
- t3 = strappend("pre", NULL);
- assert_se(streq(t3, "pre"));
-
- t4 = strappend("pre", "suf");
- assert_se(streq(t4, "presuf"));
-}
-
static void test_string_has_cc(void) {
assert_se(string_has_cc("abc\1", NULL));
assert_se(string_has_cc("abc\x7f", NULL));
test_strextend();
test_strextend_with_separator();
test_strrep();
- test_strappend();
test_string_has_cc();
test_ascii_strlower();
test_strshorten();
return r;
}
- p = strappend("../usr/share/zoneinfo/", c->zone);
+ p = path_join("../usr/share/zoneinfo", c->zone);
if (!p)
return log_oom();
case CREATE_SYMLINK:
if (!i.argument) {
- i.argument = strappend("/usr/share/factory/", i.path);
+ i.argument = path_join("/usr/share/factory", i.path);
if (!i.argument)
return log_oom();
}
case COPY_FILES:
if (!i.argument) {
- i.argument = strappend("/usr/share/factory/", i.path);
+ i.argument = path_join(arg_root, "/usr/share/factory", i.path);
if (!i.argument)
return log_oom();
+
} else if (!path_is_absolute(i.argument)) {
*invalid_config = true;
log_error("[%s:%u] Source path is not absolute.", fname, line);
return -EBADMSG;
+
+ } else if (arg_root) {
+ char *p;
+
+ p = path_join(arg_root, i.argument);
+ if (!p)
+ return log_oom();
+ free_and_replace(i.argument, p);
}
path_simplify(i.argument, false);
p = path_join(arg_root, i.path);
if (!p)
return log_oom();
-
free_and_replace(i.path, p);
}
if (!startswith(de->d_name, "ask."))
continue;
- p = strappend("/run/systemd/ask-password/", de->d_name);
+ p = path_join("/run/systemd/ask-password", de->d_name);
if (!p)
return log_oom();
#include "blkid-util.h"
#include "device-util.h"
#include "efivars.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "gpt.h"
#include "parse-util.h"
errno = 0;
pl = blkid_probe_get_partitions(pr);
if (!pl)
- return -errno ?: -ENOMEM;
+ return errno_or_else(ENOMEM);
nvals = blkid_partlist_numof_partitions(pl);
for (i = 0; i < nvals; i++) {
return log_error_errno(r, "Unknown device \"%s\": %m", *p);
if (arg_wait_for_initialization_timeout > 0) {
- r = device_wait_for_initialization(device, NULL, arg_wait_for_initialization_timeout, NULL);
+ sd_device *d;
+
+ r = device_wait_for_initialization(device, NULL, arg_wait_for_initialization_timeout, &d);
if (r < 0)
return r;
+
+ sd_device_unref(device);
+ device = d;
}
if (action == ACTION_QUERY)
--- /dev/null
+../TEST-01-BASIC/Makefile
\ No newline at end of file
--- /dev/null
+#!/bin/bash
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -e
+TEST_DESCRIPTION="test CleanUnit"
+
+. $TEST_BASE_DIR/test-functions
+
+test_setup() {
+ create_empty_image
+ mkdir -p $TESTDIR/root
+ mount ${LOOPDEV}p1 $TESTDIR/root
+
+ (
+ LOG_LEVEL=5
+ eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
+
+ setup_basic_environment
+
+ # mask some services that we do not want to run in these tests
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-networkd.socket
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-resolved.service
+ ln -fs /dev/null $initdir/etc/systemd/system/systemd-machined.service
+
+ # setup the testsuite service
+ cat >$initdir/etc/systemd/system/testsuite.service <<EOF
+[Unit]
+Description=Testsuite service
+
+[Service]
+ExecStart=/bin/bash -x /testsuite.sh
+Type=oneshot
+StandardOutput=tty
+StandardError=tty
+EOF
+ cp testsuite.sh $initdir/
+
+ setup_testsuite
+ ) || return 1
+ setup_nspawn_root
+
+ ddebug "umount $TESTDIR/root"
+ umount $TESTDIR/root
+}
+
+do_test "$@"
--- /dev/null
+#!/bin/bash
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+set -ex
+set -o pipefail
+
+cat > /etc/systemd/system/testservice.service <<EOF
+[Service]
+ConfigurationDirectory=testservice
+RuntimeDirectory=testservice
+StateDirectory=testservice
+CacheDirectory=testservice
+LogsDirectory=testservice
+RuntimeDirectoryPreserve=yes
+ExecStart=/bin/sleep infinity
+Type=exec
+EOF
+
+systemctl daemon-reload
+
+! test -e /etc/testservice
+! test -e /run/testservice
+! test -e /var/lib/testservice
+! test -e /var/cache/testservice
+! test -e /var/log/testservice
+
+systemctl start testservice
+
+test -d /etc/testservice
+test -d /run/testservice
+test -d /var/lib/testservice
+test -d /var/cache/testservice
+test -d /var/log/testservice
+
+! systemctl clean testservice
+
+systemctl stop testservice
+
+test -d /etc/testservice
+test -d /run/testservice
+test -d /var/lib/testservice
+test -d /var/cache/testservice
+test -d /var/log/testservice
+
+systemctl clean testservice --what=configuration
+
+! test -e /etc/testservice
+test -d /run/testservice
+test -d /var/lib/testservice
+test -d /var/cache/testservice
+test -d /var/log/testservice
+
+systemctl clean testservice
+
+! test -e /etc/testservice
+! test -e /run/testservice
+test -d /var/lib/testservice
+! test -e /var/cache/testservice
+test -d /var/log/testservice
+
+systemctl clean testservice --what=logs
+
+! test -e /etc/testservice
+! test -e /run/testservice
+test -d /var/lib/testservice
+! test -e /var/cache/testservice
+! test -e /var/log/testservice
+
+systemctl clean testservice --what=all
+
+! test -e /etc/testservice
+! test -e /run/testservice
+! test -e /var/lib/testservice
+! test -e /var/cache/testservice
+! test -e /var/log/testservice
+
+echo OK > /testok
+
+exit 0
--- /dev/null
+[Unit]
+Description=A conjugate
+Requires=a.service
+After=a.service
+Before=a.service
+
+[Service]
+ExecStart=/bin/true
Local=
TOS=
Independent=
+AssignToLoopback=
Key=
InputKey=
Encapsulation=
RouteTable=
BlackList=
SendRelease=
+MaxAttempts=
+[DHCPv4]
+UseDNS=
+UseDomains=
+UseRoutes=
+IAID=
+UserClass=
+UseNTP=
+UseMTU=
+UseDomainName=
+RouteMetric=
+SendHostname=
+Anonymize=
+VendorClassIdentifier=
+Hostname=
+DUIDType=
+UseHostname=
+CriticalConnection=
+DUIDRawData=
+RequestBroadcast=
+ClientIdentifier=
+ListenPort=
+UseTimezone=
+RouteTable=
+BlackList=
+SendRelease=
+MaxAttempts=
+[DHCPv6]
+UseNTP=
+UseDNS=
+RapidCommit=
+ForceDHCPv6PDOtherInformation=
[Route]
Destination=
Protocol=
OtherInformation=
[Neighbor]
Address=
+LinkLayerAddress=
MACAddress=
[IPv6AddressLabel]
Label=
DefaultLeaseTimeSec=
EmitTimezone=
DNS=
-MaxAttempts=
-[DHCPv4]
-UseHostname=
-UseMTU=
-UseDomainName=
-CriticalConnection=
-UseDNS=
--- /dev/null
+[Unit]
+Description=I
+Conflicts=a.service d.service
+Wants=b.service
+After=b.service
+
+[Service]
+ExecStart=/bin/true
test_data_files = '''
a.service
+ a-conj.service
b.service
basic.target
c.service
hello-after-sleep.target
hello.service
hwdb/10-bad.hwdb
+ i.service
journal-data/journal-1.txt
journal-data/journal-2.txt
nomem.slice
--- /dev/null
+[NetDev]
+Name=gretun96
+Kind=gre
+
+[Tunnel]
+Local=any
+Remote=any
+Key=106
+SerializeTunneledPackets=false
--- /dev/null
+[NetDev]
+Name=ip6gretun96
+Kind=ip6gre
+
+[Tunnel]
+Local=any
+Remote=any
--- /dev/null
+[NetDev]
+Name=ipiptun96
+Kind=ipip
+MTUBytes=1480
+
+[Tunnel]
+Local=any
+Remote=any
--- /dev/null
+[NetDev]
+Name=ipiptun99
+Kind=ipip
+MTUBytes=1480
+
+[Tunnel]
+Local=192.168.223.238
+Remote=192.169.224.239
+Independent=true
+AssignToLoopback=yes
--- /dev/null
+[Match]
+Name=dummy98
+
+[Network]
+IPv6AcceptRA=no
+Tunnel=gretun97
--- /dev/null
+[Match]
+Name=gretun97
+
+[Network]
+IPv6AcceptRA=no
+Address=10.0.0.21
+
+[Neighbor]
+Address=10.0.0.22
+LinkLayerAddress=10.65.223.239
[Neighbor]
Address=192.168.10.1
-MACAddress=00:00:5e:00:02:65
+LinkLayerAddress=00:00:5e:00:02:65
[Neighbor]
Address=2004:da8:1:0::1
-MACAddress=00:00:5e:00:02:66
+LinkLayerAddress=00:00:5e:00:02:66
[Route]
Type=prohibit
Destination=202.54.1.4
+
+[Route]
+Type=local
+Destination=149.10.123.1
+
+[Route]
+Type=anycast
+Destination=149.10.123.2
+
+[Route]
+Type=broadcast
+Destination=149.10.123.3
+
+[Route]
+Type=multicast
+Destination=149.10.123.4
--- /dev/null
+[NetDev]
+Name=sittun96
+Kind=sit
+
+[Tunnel]
+Local=any
+Remote=any
--- /dev/null
+[Match]
+Name=*tun96
+
+[Network]
+IPv6AcceptRA=no
+Address=2001:db8:0:f102::19/64
+Address=10.3.2.6/16
+LinkLocalAddressing=yes
--- /dev/null
+[NetDev]
+Name=vtitun96
+Kind=vti
+
+[Tunnel]
+Local=any
+Remote=any
--- /dev/null
+[NetDev]
+Kind=xfrm
+Name=xfrm99
+
+[Xfrm]
+Independent=yes
--- /dev/null
+[NetDev]
+Kind=xfrm
+Name=xfrm99
MulticastToUnicast = true
NeighborSuppression = true
Learning = false
+Priority = 23
+UseBPDU = true
+AllowPortToBeRoot=true
[Network]
Bridge=bridge99
+
+[Bridge]
+Priority=0
--- /dev/null
+[Match]
+Name=veth99
+
+[Network]
+IPv6AcceptRA=yes
+
+[Route]
+Destination=2600:0:0:1::/64
--- /dev/null
+[Match]
+Name=veth99
+
+[Network]
+DHCP=ipv4
+
+[DHCP]
+UseDNS=yes
+
+[IPv6AcceptRA]
+UseDNS=yes
--- /dev/null
+[Match]
+Name=veth99
+
+[Network]
+DHCP=ipv4
+
+[DHCPv4]
+UseDNS=yes
+
+[DHCPv6]
+UseDNS=no
+
+[IPv6AcceptRA]
+UseDNS=no
--- /dev/null
+[Match]
+Name=veth99
+
+[Network]
+DHCP=yes
+
+[DHCP]
+UseDNS=no
+
+[IPv6AcceptRA]
+UseDNS=no
--- /dev/null
+[Match]
+Name=veth99
+
+[Network]
+DHCP=yes
+
+[DHCP]
+UseDNS=yes
+
+[IPv6AcceptRA]
+UseDNS=no
Tunnel=gretun99
Tunnel=gretun98
Tunnel=gretun97
+Tunnel=gretun96
Tunnel=ip6gretun99
Tunnel=ip6gretun98
Tunnel=ip6gretun97
+Tunnel=ip6gretun96
Tunnel=ipiptun99
Tunnel=ipiptun98
Tunnel=ipiptun97
+Tunnel=ipiptun96
Name=geneve99
Name=ipiptun99
Name=nlmon99
+Name=xfrm99
[Network]
LinkLocalAddressing=yes
Tunnel=sittun99
Tunnel=sittun98
Tunnel=sittun97
+Tunnel=sittun96
Tunnel=vtitun99
Tunnel=vtitun98
Tunnel=vtitun97
+Tunnel=vtitun96
--- /dev/null
+[Match]
+Name=dummy98
+
+[Network]
+IPv6AcceptRA=no
+Xfrm=xfrm99
dnsmasq_log_file='/run/networkd-ci/test-dnsmasq-log-file'
networkd_bin='/usr/lib/systemd/systemd-networkd'
+resolved_bin='/usr/lib/systemd/systemd-resolved'
wait_online_bin='/usr/lib/systemd/systemd-networkd-wait-online'
networkctl_bin='/usr/bin/networkctl'
+resolvectl_bin='/usr/bin/resolvectl'
+timedatectl_bin='/usr/bin/timedatectl'
use_valgrind=False
enable_debug=True
env = {}
return f
-def expectedFailureIfEthtoolDoesNotSupportDriver():
- def f(func):
- support = False
- rc = call('ip link add name dummy99 type dummy')
- if rc == 0:
- ret = run('udevadm info -w10s /sys/class/net/dummy99', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- if ret.returncode == 0 and 'E: ID_NET_DRIVER=dummy' in ret.stdout.rstrip():
- support = True
- call('ip link del dummy99')
-
- if support:
- return func
- else:
- return unittest.expectedFailure(func)
-
- return f
-
def setUpModule():
os.makedirs(network_unit_file_path, exist_ok=True)
os.makedirs(networkd_ci_path, exist_ok=True)
check_output('systemctl stop systemd-networkd.socket')
check_output('systemctl stop systemd-networkd.service')
+ check_output('systemctl stop systemd-resolved.service')
drop_in = [
'[Service]',
with open('/run/systemd/system/systemd-networkd.service.d/00-override.conf', mode='w') as f:
f.write('\n'.join(drop_in))
+ drop_in = [
+ '[Service]',
+ 'Restart=no',
+ 'ExecStart=',
+ ]
+ if use_valgrind:
+ drop_in += ['ExecStart=!!valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all ' + resolved_bin]
+ else:
+ drop_in += ['ExecStart=!!' + resolved_bin]
+ if enable_debug:
+ drop_in += ['Environment=SYSTEMD_LOG_LEVEL=debug']
+ if asan_options:
+ drop_in += ['Environment=ASAN_OPTIONS="' + asan_options + '"']
+ if lsan_options:
+ drop_in += ['Environment=LSAN_OPTIONS="' + lsan_options + '"']
+ if ubsan_options:
+ drop_in += ['Environment=UBSAN_OPTIONS="' + ubsan_options + '"']
+ if asan_options or lsan_options or ubsan_options:
+ drop_in += ['SystemCallFilter=']
+ if use_valgrind or asan_options or lsan_options or ubsan_options:
+ drop_in += ['MemoryDenyWriteExecute=no']
+
+ os.makedirs('/run/systemd/system/systemd-resolved.service.d', exist_ok=True)
+ with open('/run/systemd/system/systemd-resolved.service.d/00-override.conf', mode='w') as f:
+ f.write('\n'.join(drop_in))
+
check_output('systemctl daemon-reload')
print(check_output('systemctl cat systemd-networkd.service'))
+ print(check_output('systemctl cat systemd-resolved.service'))
+ check_output('systemctl restart systemd-resolved')
def tearDownModule():
shutil.rmtree(networkd_ci_path)
check_output('systemctl stop systemd-networkd.service')
+ check_output('systemctl stop systemd-resolved.service')
shutil.rmtree('/run/systemd/system/systemd-networkd.service.d')
+ shutil.rmtree('/run/systemd/system/systemd-resolved.service.d')
check_output('systemctl daemon-reload')
check_output('systemctl start systemd-networkd.socket')
- check_output('systemctl start systemd-networkd.service')
+ check_output('systemctl start systemd-resolved.service')
def read_link_attr(link, dev, attribute):
with open(os.path.join(os.path.join(os.path.join('/sys/class/net/', link), dev), attribute)) as f:
self.assertRegex(output, r'Link File: (?:/usr)/lib/systemd/network/99-default.link')
self.assertRegex(output, r'Network File: n/a')
- @expectedFailureIfEthtoolDoesNotSupportDriver()
- def test_udev_driver(self):
- copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network',
- '25-veth.netdev', 'netdev-link-local-addressing-yes.network')
- start_networkd()
-
- wait_online(['test1:degraded', 'veth99:degraded', 'veth-peer:degraded'])
-
- output = check_output(*networkctl_cmd, 'status', 'test1', env=env)
- self.assertRegex(output, 'Driver: dummy')
-
- output = check_output(*networkctl_cmd, 'status', 'veth99', env=env)
- self.assertRegex(output, 'Driver: veth')
-
- output = check_output(*networkctl_cmd, 'status', 'veth-peer', env=env)
- self.assertRegex(output, 'Driver: veth')
-
def test_delete_links(self):
copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network',
'25-veth.netdev', 'netdev-link-local-addressing-yes.network')
class NetworkdNetDevTests(unittest.TestCase, Utilities):
- links =[
+ links_remove_earlier = [
+ 'xfrm99',
+ ]
+
+ links = [
'6rdtun99',
'bond99',
'bridge99',
'gretun99',
'ip6gretap98',
'ip6gretap99',
+ 'ip6gretun96',
'ip6gretun97',
'ip6gretun98',
'ip6gretun99',
'vti6tun97',
'vti6tun98',
'vti6tun99',
+ 'vtitun96',
'vtitun97',
'vtitun98',
'vtitun99',
'vxcan99',
'vxlan99',
'wg98',
- 'wg99']
+ 'wg99',
+ ]
units = [
'10-dropin-test.netdev',
'25-geneve.netdev',
'25-gretap-tunnel-local-any.netdev',
'25-gretap-tunnel.netdev',
+ '25-gre-tunnel-any-any.netdev',
'25-gre-tunnel-local-any.netdev',
'25-gre-tunnel-remote-any.netdev',
'25-gre-tunnel.netdev',
'25-ip6gretap-tunnel-local-any.netdev',
'25-ip6gretap-tunnel.netdev',
+ '25-ip6gre-tunnel-any-any.netdev',
'25-ip6gre-tunnel-local-any.netdev',
'25-ip6gre-tunnel-remote-any.netdev',
'25-ip6gre-tunnel.netdev',
- '25-ip6tnl-tunnel-remote-any.netdev',
+ '25-ip6tnl-tunnel-any-any.netdev',
'25-ip6tnl-tunnel-local-any.netdev',
+ '25-ip6tnl-tunnel-remote-any.netdev',
'25-ip6tnl-tunnel.netdev',
+ '25-ipip-tunnel-any-any.netdev',
'25-ipip-tunnel-independent.netdev',
+ '25-ipip-tunnel-independent-loopback.netdev',
'25-ipip-tunnel-local-any.netdev',
'25-ipip-tunnel-remote-any.netdev',
'25-ipip-tunnel.netdev',
'25-macsec.netdev',
'25-macsec.network',
'25-nlmon.netdev',
+ '25-sit-tunnel-any-any.netdev',
'25-sit-tunnel-local-any.netdev',
'25-sit-tunnel-remote-any.netdev',
'25-sit-tunnel.netdev',
'25-vcan.netdev',
'25-veth.netdev',
'25-vrf.netdev',
+ '25-vti6-tunnel-any-any.netdev',
'25-vti6-tunnel-local-any.netdev',
'25-vti6-tunnel-remote-any.netdev',
'25-vti6-tunnel.netdev',
+ '25-vti-tunnel-any-any.netdev',
'25-vti-tunnel-local-any.netdev',
'25-vti-tunnel-remote-any.netdev',
'25-vti-tunnel.netdev',
'25-wireguard-private-key.txt',
'25-wireguard.netdev',
'25-wireguard.network',
+ '25-xfrm.netdev',
+ '25-xfrm-independent.netdev',
'6rd.network',
'erspan.network',
'gre.network',
'vti6.network',
'vti.network',
'vxlan-test1.network',
- 'vxlan.network']
+ 'vxlan.network',
+ 'xfrm.network',
+ ]
fou_ports = [
'55555',
def setUp(self):
remove_fou_ports(self.fou_ports)
+ remove_links(self.links_remove_earlier)
remove_links(self.links)
stop_networkd(show_logs=False)
def tearDown(self):
remove_fou_ports(self.fou_ports)
+ remove_links(self.links_remove_earlier)
remove_links(self.links)
remove_unit_from_networkd_path(self.units)
stop_networkd(show_logs=True)
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'ipip.network',
'25-ipip-tunnel.netdev', '25-tunnel.network',
'25-ipip-tunnel-local-any.netdev', '25-tunnel-local-any.network',
- '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
+ '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+ '25-ipip-tunnel-any-any.netdev', '25-tunnel-any-any.network')
start_networkd()
- wait_online(['ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'dummy98:degraded'])
+ wait_online(['ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', 'dummy98:degraded'])
output = check_output('ip -d link show ipiptun99')
print(output)
output = check_output('ip -d link show ipiptun97')
print(output)
self.assertRegex(output, 'ipip (?:ipip |)remote any local 192.168.223.238 dev dummy98')
+ output = check_output('ip -d link show ipiptun96')
+ print(output)
+ self.assertRegex(output, 'ipip (?:ipip |)remote any local any dev dummy98')
def test_gre_tunnel(self):
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'gretun.network',
'25-gre-tunnel.netdev', '25-tunnel.network',
'25-gre-tunnel-local-any.netdev', '25-tunnel-local-any.network',
- '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
+ '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+ '25-gre-tunnel-any-any.netdev', '25-tunnel-any-any.network')
start_networkd()
- wait_online(['gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'dummy98:degraded'])
+ wait_online(['gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', 'dummy98:degraded'])
output = check_output('ip -d link show gretun99')
print(output)
self.assertRegex(output, 'okey 0.0.0.105')
self.assertNotRegex(output, 'iseq')
self.assertNotRegex(output, 'oseq')
+ output = check_output('ip -d link show gretun96')
+ print(output)
+ self.assertRegex(output, 'gre remote any local any dev dummy98')
+ self.assertRegex(output, 'ikey 0.0.0.106')
+ self.assertRegex(output, 'okey 0.0.0.106')
+ self.assertNotRegex(output, 'iseq')
+ self.assertNotRegex(output, 'oseq')
def test_ip6gre_tunnel(self):
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'ip6gretun.network',
'25-ip6gre-tunnel.netdev', '25-tunnel.network',
'25-ip6gre-tunnel-local-any.netdev', '25-tunnel-local-any.network',
- '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
+ '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+ '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network')
start_networkd(5)
# Old kernels seem not to support IPv6LL address on ip6gre tunnel, So please do not use wait_online() here.
self.check_link_exists('ip6gretun99')
self.check_link_exists('ip6gretun98')
self.check_link_exists('ip6gretun97')
+ self.check_link_exists('ip6gretun96')
output = check_output('ip -d link show ip6gretun99')
print(output)
output = check_output('ip -d link show ip6gretun97')
print(output)
self.assertRegex(output, 'ip6gre remote any local 2a00:ffde:4567:edde::4987 dev dummy98')
+ output = check_output('ip -d link show ip6gretun96')
+ print(output)
+ self.assertRegex(output, 'ip6gre remote any local any dev dummy98')
def test_gretap_tunnel(self):
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'gretap.network',
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'vti.network',
'25-vti-tunnel.netdev', '25-tunnel.network',
'25-vti-tunnel-local-any.netdev', '25-tunnel-local-any.network',
- '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
+ '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+ '25-vti-tunnel-any-any.netdev', '25-tunnel-any-any.network')
start_networkd()
- wait_online(['vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'dummy98:degraded'])
+ wait_online(['vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', 'dummy98:degraded'])
output = check_output('ip -d link show vtitun99')
print(output)
output = check_output('ip -d link show vtitun97')
print(output)
self.assertRegex(output, 'vti remote any local 10.65.223.238 dev dummy98')
+ output = check_output('ip -d link show vtitun96')
+ print(output)
+ self.assertRegex(output, 'vti remote any local any dev dummy98')
def test_vti6_tunnel(self):
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'vti6.network',
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'sit.network',
'25-sit-tunnel.netdev', '25-tunnel.network',
'25-sit-tunnel-local-any.netdev', '25-tunnel-local-any.network',
- '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
+ '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+ '25-sit-tunnel-any-any.netdev', '25-tunnel-any-any.network')
start_networkd()
- wait_online(['sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'dummy98:degraded'])
+ wait_online(['sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', 'dummy98:degraded'])
output = check_output('ip -d link show sittun99')
print(output)
output = check_output('ip -d link show sittun97')
print(output)
self.assertRegex(output, "sit (?:ip6ip |)remote any local 10.65.223.238 dev dummy98")
+ output = check_output('ip -d link show sittun96')
+ print(output)
+ self.assertRegex(output, "sit (?:ip6ip |)remote any local any dev dummy98")
def test_isatap_tunnel(self):
copy_unit_to_networkd_unit_path('12-dummy.netdev', 'isatap.network',
wait_online(['ipiptun99:carrier'])
+ def test_tunnel_independent_loopback(self):
+ copy_unit_to_networkd_unit_path('25-ipip-tunnel-independent-loopback.netdev', 'netdev-link-local-addressing-yes.network')
+ start_networkd()
+
+ wait_online(['ipiptun99:carrier'])
+
+ @expectedFailureIfModuleIsNotAvailable('xfrm_interface')
+ def test_xfrm(self):
+ copy_unit_to_networkd_unit_path('12-dummy.netdev', 'xfrm.network',
+ '25-xfrm.netdev', 'netdev-link-local-addressing-yes.network')
+ start_networkd()
+
+ wait_online(['xfrm99:degraded', 'dummy98:degraded'])
+
+ output = check_output('ip link show dev xfrm99')
+ print(output)
+
+ @expectedFailureIfModuleIsNotAvailable('xfrm_interface')
+ def test_xfrm_independent(self):
+ copy_unit_to_networkd_unit_path('25-xfrm-independent.netdev', 'netdev-link-local-addressing-yes.network')
+ start_networkd()
+
+ wait_online(['xfrm99:degraded'])
+
@expectedFailureIfModuleIsNotAvailable('fou')
def test_fou(self):
# The following redundant check is necessary for CentOS CI.
'bond199',
'dummy98',
'dummy99',
- 'test1']
+ 'gretun97',
+ 'test1'
+ ]
units = [
'11-dummy.netdev',
'25-bond-active-backup-slave.netdev',
'25-fibrule-invert.network',
'25-fibrule-port-range.network',
+ '25-gre-tunnel-remote-any.netdev',
'25-ipv6-address-label-section.network',
'25-neighbor-section.network',
+ '25-neighbor-ip-dummy.network',
+ '25-neighbor-ip.network',
'25-link-local-addressing-no.network',
'25-link-local-addressing-yes.network',
'25-link-section-unmanaged.network',
start_networkd()
wait_online(['dummy98:routable'])
+ print('### ip -6 route show dev dummy98')
output = check_output('ip -6 route show dev dummy98')
print(output)
self.assertRegex(output, '2001:1234:5:8fff:ff:ff:ff:ff proto static')
self.assertRegex(output, '2001:1234:5:8f63::1 proto kernel')
+ print('### ip -6 route show dev dummy98 default')
output = check_output('ip -6 route show dev dummy98 default')
+ print(output)
self.assertRegex(output, 'default via 2001:1234:5:8fff:ff:ff:ff:ff proto static metric 1024 pref medium')
+ print('### ip -4 route show dev dummy98')
output = check_output('ip -4 route show dev dummy98')
print(output)
self.assertRegex(output, '149.10.124.48/28 proto kernel scope link src 149.10.124.58')
self.assertRegex(output, '169.254.0.0/16 proto static scope link metric 2048')
self.assertRegex(output, '192.168.1.1 proto static initcwnd 20')
self.assertRegex(output, '192.168.1.2 proto static initrwnd 30')
+ self.assertRegex(output, 'multicast 149.10.123.4 proto static')
+ print('### ip -4 route show dev dummy98 default')
output = check_output('ip -4 route show dev dummy98 default')
+ print(output)
self.assertRegex(output, 'default via 149.10.125.65 proto static onlink')
self.assertRegex(output, 'default via 149.10.124.64 proto static')
self.assertRegex(output, 'default proto static')
+ print('### ip -4 route show table local dev dummy98')
+ output = check_output('ip -4 route show table local dev dummy98')
+ print(output)
+ self.assertRegex(output, 'local 149.10.123.1 proto static scope host')
+ self.assertRegex(output, 'anycast 149.10.123.2 proto static scope link')
+ self.assertRegex(output, 'broadcast 149.10.123.3 proto static scope link')
+
+ print('### ip route show type blackhole')
output = check_output('ip route show type blackhole')
print(output)
self.assertRegex(output, 'blackhole 202.54.1.2 proto static')
+ print('### ip route show type unreachable')
output = check_output('ip route show type unreachable')
print(output)
self.assertRegex(output, 'unreachable 202.54.1.3 proto static')
+ print('### ip route show type prohibit')
output = check_output('ip route show type prohibit')
print(output)
self.assertRegex(output, 'prohibit 202.54.1.4 proto static')
print(output)
self.assertRegex(output, '2004:da8:1::/64')
- def test_ipv6_neighbor(self):
+ def test_neighbor_section(self):
copy_unit_to_networkd_unit_path('25-neighbor-section.network', '12-dummy.netdev')
start_networkd()
wait_online(['dummy98:degraded'], timeout='40s')
self.assertRegex(output, '192.168.10.1.*00:00:5e:00:02:65.*PERMANENT')
self.assertRegex(output, '2004:da8:1::1.*00:00:5e:00:02:66.*PERMANENT')
+ def test_neighbor_gre(self):
+ copy_unit_to_networkd_unit_path('25-neighbor-ip.network', '25-neighbor-ip-dummy.network',
+ '12-dummy.netdev', '25-gre-tunnel-remote-any.netdev')
+ start_networkd()
+ wait_online(['dummy98:degraded', 'gretun97:routable'], timeout='40s')
+
+ output = check_output('ip neigh list dev gretun97')
+ print(output)
+ self.assertRegex(output, '10.0.0.22 lladdr 10.65.223.239 PERMANENT')
+
def test_link_local_addressing(self):
copy_unit_to_networkd_unit_path('25-link-local-addressing-yes.network', '11-dummy.netdev',
'25-link-local-addressing-no.network', '12-dummy.netdev')
output = check_output('bridge -d link show dummy98')
print(output)
- self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1')
self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'path_cost'), '400')
+ self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1')
+ self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1')
self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood'), '1')
self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood'), '0')
- self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1')
+ # CONFIG_BRIDGE_IGMP_SNOOPING=y
+ if (os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast')):
+ self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1')
if (os.path.exists('/sys/devices/virtual/net/bridge99/lower_dummy98/brport/neigh_suppress')):
self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress'), '1')
self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'learning'), '0')
+ self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'priority'), '23')
+ self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard'), '1')
+ self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'root_block'), '1')
- # CONFIG_BRIDGE_IGMP_SNOOPING=y
- if (os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast')):
- self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1')
+ output = check_output('bridge -d link show test1')
+ print(output)
+ self.assertEqual(read_bridge_port_attr('bridge99', 'test1', 'priority'), '0')
check_output('ip address add 192.168.0.16/24 dev bridge99')
time.sleep(1)
'dhcp-client-keep-configuration-dhcp-on-stop.network',
'dhcp-client-keep-configuration-dhcp.network',
'dhcp-client-listen-port.network',
+ 'dhcp-client-reassign-static-routes-ipv4.network',
+ 'dhcp-client-reassign-static-routes-ipv6.network',
'dhcp-client-route-metric.network',
'dhcp-client-route-table.network',
+ 'dhcp-client-use-dns-ipv4-and-ra.network',
+ 'dhcp-client-use-dns-ipv4.network',
+ 'dhcp-client-use-dns-no.network',
+ 'dhcp-client-use-dns-yes.network',
'dhcp-client-use-routes-no.network',
'dhcp-client-vrf.network',
'dhcp-client-with-ipv4ll-fallback-with-dhcp-server.network',
print(output)
self.assertRegex(output, 'metric 24')
- def test_dhcp_client_use_routes_no(self):
+ def test_dhcp_client_reassign_static_routes_ipv4(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network',
- 'dhcp-client-use-routes-no.network')
+ 'dhcp-client-reassign-static-routes-ipv4.network')
start_networkd()
wait_online(['veth-peer:carrier'])
start_dnsmasq(lease_time='2m')
self.assertRegex(output, r'192.168.6.0/24 proto static')
self.assertRegex(output, r'192.168.7.0/24 proto static')
+ stop_dnsmasq(dnsmasq_pid_file)
+ start_dnsmasq(ipv4_range='192.168.5.210,192.168.5.220', lease_time='2m')
+
# Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120
print('Wait for the dynamic address to be renewed')
time.sleep(125)
self.assertRegex(output, r'192.168.6.0/24 proto static')
self.assertRegex(output, r'192.168.7.0/24 proto static')
+ def test_dhcp_client_reassign_static_routes_ipv6(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network',
+ 'dhcp-client-reassign-static-routes-ipv6.network')
+ start_networkd()
+ wait_online(['veth-peer:carrier'])
+ start_dnsmasq(lease_time='2m')
+ wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ output = check_output('ip address show dev veth99 scope global')
+ print(output)
+ self.assertRegex(output, r'inet6 2600::[0-9a-f]*/128 scope global (?:noprefixroute dynamic|dynamic noprefixroute)')
+
+ output = check_output('ip -6 route show dev veth99')
+ print(output)
+ self.assertRegex(output, r'2600::/64 proto ra metric 1024')
+ self.assertRegex(output, r'2600:0:0:1::/64 proto static metric 1024 pref medium')
+
+ stop_dnsmasq(dnsmasq_pid_file)
+ start_dnsmasq(ipv6_range='2600::30,2600::40', lease_time='2m')
+
+ # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120
+ print('Wait for the dynamic address to be renewed')
+ time.sleep(125)
+
+ wait_online(['veth99:routable'])
+
+ output = check_output('ip -6 route show dev veth99')
+ print(output)
+ self.assertRegex(output, r'2600::/64 proto ra metric 1024')
+ self.assertRegex(output, r'2600:0:0:1::/64 proto static metric 1024 pref medium')
+
def test_dhcp_keep_configuration_dhcp(self):
copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-v4-server-veth-peer.network', 'dhcp-client-keep-configuration-dhcp.network')
start_networkd()
self.assertRegex(output, f'default via 192.168.5.1 proto dhcp src {address2} metric 1024')
self.assertRegex(output, f'192.168.5.1 proto dhcp scope link src {address2} metric 1024')
+ def test_dhcp_client_use_dns_yes(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client-use-dns-yes.network')
+
+ start_networkd()
+ wait_online(['veth-peer:carrier'])
+ start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1 --dhcp-option=option6:dns-server,[2600::1]')
+ wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ # link become 'routable' when at least one protocol provide an valid address.
+ self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 brd 192.168.5.255 scope global dynamic', ipv='-4')
+ self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (?:dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
+
+ time.sleep(3)
+ output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ print(output)
+ self.assertRegex(output, '192.168.5.1')
+ self.assertRegex(output, '2600::1')
+
+ def test_dhcp_client_use_dns_no(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client-use-dns-no.network')
+
+ start_networkd()
+ wait_online(['veth-peer:carrier'])
+ start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1 --dhcp-option=option6:dns-server,[2600::1]')
+ wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ # link become 'routable' when at least one protocol provide an valid address.
+ self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 brd 192.168.5.255 scope global dynamic', ipv='-4')
+ self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (?:dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
+
+ time.sleep(3)
+ output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ print(output)
+ self.assertNotRegex(output, '192.168.5.1')
+ self.assertNotRegex(output, '2600::1')
+
+ def test_dhcp_client_use_dns_ipv4(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client-use-dns-ipv4.network')
+
+ start_networkd()
+ wait_online(['veth-peer:carrier'])
+ start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1 --dhcp-option=option6:dns-server,[2600::1]')
+ wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ # link become 'routable' when at least one protocol provide an valid address.
+ self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 brd 192.168.5.255 scope global dynamic', ipv='-4')
+ self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (?:dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
+
+ time.sleep(3)
+ output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ print(output)
+ self.assertRegex(output, '192.168.5.1')
+ self.assertNotRegex(output, '2600::1')
+
+ def test_dhcp_client_use_dns_ipv4_and_ra(self):
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'dhcp-server-veth-peer.network', 'dhcp-client-use-dns-ipv4-and-ra.network')
+
+ start_networkd()
+ wait_online(['veth-peer:carrier'])
+ start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1 --dhcp-option=option6:dns-server,[2600::1]')
+ wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ # link become 'routable' when at least one protocol provide an valid address.
+ self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 brd 192.168.5.255 scope global dynamic', ipv='-4')
+ self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (?:dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
+
+ time.sleep(3)
+ output = check_output(*resolvectl_cmd, 'dns', 'veth99', env=env)
+ print(output)
+ self.assertRegex(output, '192.168.5.1')
+ self.assertRegex(output, '2600::1')
+
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir')
parser.add_argument('--networkd', help='Path to systemd-networkd', dest='networkd_bin')
+ parser.add_argument('--resolved', help='Path to systemd-resolved', dest='resolved_bin')
parser.add_argument('--wait-online', help='Path to systemd-networkd-wait-online', dest='wait_online_bin')
parser.add_argument('--networkctl', help='Path to networkctl', dest='networkctl_bin')
+ parser.add_argument('--resolvectl', help='Path to resolvectl', dest='resolvectl_bin')
+ parser.add_argument('--timedatectl', help='Path to timedatectl', dest='timedatectl_bin')
parser.add_argument('--valgrind', help='Enable valgrind', dest='use_valgrind', type=bool, nargs='?', const=True, default=use_valgrind)
parser.add_argument('--debug', help='Generate debugging logs', dest='enable_debug', type=bool, nargs='?', const=True, default=enable_debug)
parser.add_argument('--asan-options', help='ASAN options', dest='asan_options')
ns, args = parser.parse_known_args(namespace=unittest)
if ns.build_dir:
- if ns.networkd_bin or ns.wait_online_bin or ns.networkctl_bin:
- print('WARNING: --networkd, --wait-online, or --networkctl options are ignored when --build-dir is specified.')
+ if ns.networkd_bin or ns.resolved_bin or ns.wait_online_bin or ns.networkctl_bin or ns.resolvectl_bin or ns.timedatectl_bin:
+ print('WARNING: --networkd, --resolved, --wait-online, --networkctl, --resolvectl, or --timedatectl options are ignored when --build-dir is specified.')
networkd_bin = os.path.join(ns.build_dir, 'systemd-networkd')
+ resolved_bin = os.path.join(ns.build_dir, 'systemd-resolved')
wait_online_bin = os.path.join(ns.build_dir, 'systemd-networkd-wait-online')
networkctl_bin = os.path.join(ns.build_dir, 'networkctl')
+ resolvectl_bin = os.path.join(ns.build_dir, 'resolvectl')
+ timedatectl_bin = os.path.join(ns.build_dir, 'timedatectl')
else:
if ns.networkd_bin:
networkd_bin = ns.networkd_bin
+ if ns.resolved_bin:
+ resolved_bin = ns.resolved_bin
if ns.wait_online_bin:
wait_online_bin = ns.wait_online_bin
if ns.networkctl_bin:
networkctl_bin = ns.networkctl_bin
+ if ns.resolvectl_bin:
+ resolvectl_bin = ns.resolvectl_bin
+ if ns.timedatectl_bin:
+ timedatectl_bin = ns.timedatectl_bin
use_valgrind = ns.use_valgrind
enable_debug = ns.enable_debug
if use_valgrind:
networkctl_cmd = ['valgrind', '--track-origins=yes', '--leak-check=full', '--show-leak-kinds=all', networkctl_bin]
+ resolvectl_cmd = ['valgrind', '--track-origins=yes', '--leak-check=full', '--show-leak-kinds=all', resolvectl_bin]
+ timedatectl_cmd = ['valgrind', '--track-origins=yes', '--leak-check=full', '--show-leak-kinds=all', timedatectl_bin]
wait_online_cmd = ['valgrind', '--track-origins=yes', '--leak-check=full', '--show-leak-kinds=all', wait_online_bin]
else:
networkctl_cmd = [networkctl_bin]
+ resolvectl_cmd = [resolvectl_bin]
+ timedatectl_cmd = [timedatectl_bin]
wait_online_cmd = [wait_online_bin]
if enable_debug:
export PATH="$HOME/.local/bin/:$PATH"
# We use a subset of https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#available-checks instead of "undefined"
-# because our fuzzers crash with "pointer-overflow","object-size" and "float-cast-overflow":
+# because our fuzzers crash with "pointer-overflow" and "float-cast-overflow":
# https://github.com/systemd/systemd/pull/12771#issuecomment-502139157
# https://github.com/systemd/systemd/pull/12812#issuecomment-502780455
# TODO: figure out what to do about unsigned-integer-overflow: https://github.com/google/oss-fuzz/issues/910
-export SANITIZER="address -fsanitize=alignment,array-bounds,bool,bounds,builtin,enum,float-divide-by-zero,function,integer-divide-by-zero,nonnull-attribute,null,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,unsigned-integer-overflow,vla-bound,vptr -fno-sanitize-recover=alignment,array-bounds,bool,bounds,builtin,enum,float-divide-by-zero,function,integer-divide-by-zero,nonnull-attribute,null,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,vla-bound,vptr"
+export SANITIZER="address -fsanitize=alignment,array-bounds,bool,bounds,builtin,enum,float-divide-by-zero,function,integer-divide-by-zero,nonnull-attribute,null,object-size,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,unsigned-integer-overflow,vla-bound,vptr -fno-sanitize-recover=alignment,array-bounds,bool,bounds,builtin,enum,float-divide-by-zero,function,integer-divide-by-zero,nonnull-attribute,null,object-size,return,returns-nonnull-attribute,shift,signed-integer-overflow,unreachable,vla-bound,vptr"
tools/oss-fuzz.sh
FUZZING_TYPE=${1:-sanity}