# SPDX-License-Identifier: LGPL-2.1+
-#
-# Copyright © 2016 Daniel Rusek
# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
# Simply invoke "mkosi" in the project directory to build an OS image.
# SPDX-License-Identifier: LGPL-2.1+
-#
-# This file is part of systemd.
-#
-# Copyright 2016 Daniel Rusek
# This is a settings file for OS image generation using mkosi (https://github.com/systemd/mkosi).
# Simply invoke "mkosi" in the project directory to build an OS image.
Features:
+* when we fork off generators and such, lower LIMIT_NOFILE soft limit to 1K
+
* add a concept for automatically loading per-unit secrets off disk and
inserting them into the kernel keyring. Maybe SecretsDirectory= similar to
ConfigurationDirectory=.
sensor:modalias:acpi:BMA250*:dmi:*:bvrTREK.G.WI71C.JGBMRBA*:*:svnTrekStor:pnSurfTabwintron7.0ST70416-6:*
ACCEL_MOUNT_MATRIX=0, 1, 0; 1, 0, 0; 0, 0, 1
+sensor:modalias:acpi:KIOX000A*:dmi:*:svnTREKSTOR:pnPrimetabT13B:*
+ ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
+
#########################################
# Umax
#########################################
<refsynopsisdiv>
<para><filename><replaceable>ESP</replaceable>/loader/loader.conf</filename>,
- <filename><replaceable>ESP</replaceable>/loader/loader.conf.d/*.conf</filename>
+ <filename><replaceable>ESP</replaceable>/loader/entries/*.conf</filename>
</para>
</refsynopsisdiv>
<para>
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>
- will read <filename>/loader/loader.conf</filename> and any files with the
+ will read <filename><replaceable>ESP</replaceable>/loader/loader.conf</filename> and any files with the
<literal>.conf</literal> extension under
- <filename>/loader/loader.conf.d/</filename> on the EFI system partition (ESP).
+ <filename><replaceable>ESP</replaceable>/loader/entries/</filename> on the EFI system partition (ESP).
</para>
<para>Each configuration file must consist of an option name, followed by
<refsect1>
<title>Options</title>
- <para>The following configuration options are understood:</para>
+ <para>The following configuration options in <filename>loader.conf</filename> are understood:</para>
<variablelist>
<varlistentry>
#
# Czech translation for systemd.
# Daniel Maixner <xskipy@gmail.com>, 2016.
-# Daniel Rusek <mail@asciiwolf.com>, 2016, 2017.
#
msgid ""
msgstr ""
c += strspn(c, DIGITS);
if (*c == '-') {
/* A connector DRM device, let's ignore all but LVDS and eDP! */
-
- if (!startswith(c, "-LVDS-") &&
- !startswith(c, "-Embedded DisplayPort-"))
+ if (!STARTSWITH_SET(c, "-LVDS-", "-Embedded DisplayPort-"))
return -EOPNOTSUPP;
}
* needs free()! */
if (IN_SET(p[0], 0, '_', '.') ||
- streq(p, "notify_on_release") ||
- streq(p, "release_agent") ||
- streq(p, "tasks") ||
+ STR_IN_SET(p, "notify_on_release", "release_agent", "tasks") ||
startswith(p, "cgroup."))
need_prefix = true;
else {
CGroupMask mask;
int r;
- /* Determines the mask of supported cgroup controllers. Only
- * includes controllers we can make sense of and that are
- * actually accessible. */
+ /* Determines the mask of supported cgroup controllers. Only includes controllers we can make sense of and that
+ * are actually accessible. Only covers real controllers, i.e. not the CGROUP_CONTROLLER_BPF_xyz
+ * pseudo-controllers. */
r = cg_all_unified();
if (r < 0)
return cg_unified_update();
}
-int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) {
+int cg_enable_everywhere(
+ CGroupMask supported,
+ CGroupMask mask,
+ const char *p,
+ CGroupMask *ret_result_mask) {
+
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *fs = NULL;
CGroupController c;
+ CGroupMask ret = 0;
int r;
assert(p);
- if (supported == 0)
+ if (supported == 0) {
+ if (ret_result_mask)
+ *ret_result_mask = 0;
return 0;
+ }
r = cg_all_unified();
if (r < 0)
return r;
- if (r == 0) /* on the legacy hiearchy there's no joining of controllers defined */
+ if (r == 0) {
+ /* On the legacy hiearchy there's no concept of "enabling" controllers in cgroups defined. Let's claim
+ * complete success right away. (If you wonder why we return the full mask here, rather than zero: the
+ * caller tends to use the returned mask later on to compare if all controllers where properly joined,
+ * and if not requeues realization. This use is the primary purpose of the return value, hence let's
+ * minimize surprises here and reduce triggers for re-realization by always saying we fully
+ * succeeded.) */
+ if (ret_result_mask)
+ *ret_result_mask = mask & supported & CGROUP_MASK_V2; /* If you wonder why we mask this with
+ * CGROUP_MASK_V2: The 'supported' mask
+ * might contain pure-V1 or BPF
+ * controllers, and we never want to
+ * claim that we could enable those with
+ * cgroup.subtree_control */
return 0;
+ }
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs);
if (r < 0)
if (!f) {
f = fopen(fs, "we");
- if (!f) {
- log_debug_errno(errno, "Failed to open cgroup.subtree_control file of %s: %m", p);
- break;
- }
+ if (!f)
+ return log_debug_errno(errno, "Failed to open cgroup.subtree_control file of %s: %m", p);
}
r = write_string_stream(f, s, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0) {
- log_debug_errno(r, "Failed to enable controller %s for %s (%s): %m", n, p, fs);
+ log_debug_errno(r, "Failed to %s controller %s for %s (%s): %m",
+ FLAGS_SET(mask, bit) ? "enable" : "disable", n, p, fs);
clearerr(f);
+
+ /* If we can't turn off a controller, leave it on in the reported resulting mask. This
+ * happens for example when we attempt to turn off a controller up in the tree that is
+ * used down in the tree. */
+ if (!FLAGS_SET(mask, bit) && r == -EBUSY) /* You might wonder why we check for EBUSY
+ * only here, and not follow the same logic
+ * for other errors such as EINVAL or
+ * EOPNOTSUPP or anything else. That's
+ * because EBUSY indicates that the
+ * controllers is currently enabled and
+ * cannot be disabled because something down
+ * the hierarchy is still using it. Any other
+ * error most likely means something like "I
+ * never heard of this controller" or
+ * similar. In the former case it's hence
+ * safe to assume the controller is still on
+ * after the failed operation, while in the
+ * latter case it's safer to assume the
+ * controller is unknown and hence certainly
+ * not enabled. */
+ ret |= bit;
+ } else {
+ /* Otherwise, if we managed to turn on a controller, set the bit reflecting that. */
+ if (FLAGS_SET(mask, bit))
+ ret |= bit;
}
}
}
+ /* Let's return the precise set of controllers now enabled for the cgroup. */
+ if (ret_result_mask)
+ *ret_result_mask = ret;
+
return 0;
}
int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t callback, void *userdata);
int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t callback, void *userdata);
int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root);
-int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p);
+int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p, CGroupMask *ret_result_mask);
int cg_mask_supported(CGroupMask *ret);
int cg_mask_from_string(const char *s, CGroupMask *ret);
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
+#include "macro.h"
+
/* The head of the linked list. Use this in the structure that shall
* contain the head of the linked list */
#define LIST_HEAD(t,name) \
/* Initialize the list's head */
#define LIST_HEAD_INIT(head) \
do { \
- (head) = NULL; } \
- while (false)
+ (head) = NULL; \
+ } while (false)
/* Initialize a list item */
#define LIST_INIT(name,item) \
/* Like valid_device_node_path(), but also allows full-subsystem expressions, like DeviceAllow= and DeviceDeny=
* accept it */
- if (startswith(path, "block-") ||
- startswith(path, "char-"))
+ if (STARTSWITH_SET(path, "block-", "char-"))
return true;
return valid_device_node_path(path);
#define PATH_STARTSWITH_SET(p, ...) \
({ \
- char **s; \
- bool _found = false; \
- STRV_FOREACH(s, STRV_MAKE(__VA_ARGS__)) \
- if (path_startswith(p, *s)) { \
- _found = true; \
- break; \
- } \
+ const char *_p = (p); \
+ char *_found = NULL, **_i; \
+ STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \
+ _found = path_startswith(_p, *_i); \
+ if (_found) \
+ break; \
+ } \
_found; \
})
_x && strv_contains(STRV_MAKE(__VA_ARGS__), _x); \
})
+#define STARTSWITH_SET(p, ...) \
+ ({ \
+ const char *_p = (p); \
+ char *_found = NULL, **_i; \
+ STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \
+ _found = startswith(_p, *_i); \
+ if (_found) \
+ break; \
+ } \
+ _found; \
+ })
+
#define FOREACH_STRING(x, ...) \
for (char **_l = ({ \
char **_ll = STRV_MAKE(__VA_ARGS__); \
return -ENOMEM;
}
} else {
- if (startswith(s, "/dev/"))
- p = s + 5;
- else if (startswith(s, "../"))
- p = s + 3;
- else
+ p = PATH_STARTSWITH_SET(s, "/dev/", "../");
+ if (!p)
p = s;
b = strdup(p);
if (r < 0)
return r; /* returns EINVAL if not a symlink */
- e = path_startswith(t, "/usr/share/zoneinfo/");
- if (!e)
- e = path_startswith(t, "../usr/share/zoneinfo/");
+ e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/");
if (!e)
return -EINVAL;
}
/* remove trailing whitespace */
- while (linelen > 0 && strchra(sep, line[linelen-1]))
+ while (linelen > 0 && strchra((CHAR8 *)" \t", line[linelen-1]))
linelen--;
line[linelen] = '\0';
#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC)
-bool manager_owns_root_cgroup(Manager *m) {
+/* Returns the log level to use when cgroup attribute writes fail. When an attribute is missing or we have access
+ * problems we downgrade to LOG_DEBUG. This is supposed to be nice to container managers and kernels which want to mask
+ * out specific attributes from us. */
+#define LOG_LEVEL_CGROUP_WRITE(r) (IN_SET(abs(r), ENOENT, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING)
+
+bool manager_owns_host_root_cgroup(Manager *m) {
assert(m);
/* Returns true if we are managing the root cgroup. Note that it isn't sufficient to just check whether the
* appears to be no nice way to detect whether we are in a CLONE_NEWCGROUP namespace we instead just check if
* we run in any kind of container virtualization. */
+ if (MANAGER_IS_USER(m))
+ return false;
+
if (detect_container() > 0)
return false;
return empty_or_root(m->cgroup_root);
}
-bool unit_has_root_cgroup(Unit *u) {
+bool unit_has_host_root_cgroup(Unit *u) {
assert(u);
/* Returns whether this unit manages the root cgroup. This will return true if this unit is the root slice and
* the manager manages the root cgroup. */
- if (!manager_owns_root_cgroup(u->manager))
+ if (!manager_owns_host_root_cgroup(u->manager))
return false;
return unit_has_name(u, SPECIAL_ROOT_SLICE);
}
+static int set_attribute_and_warn(Unit *u, const char *controller, const char *attribute, const char *value) {
+ int r;
+
+ r = cg_set_attribute(controller, u->cgroup_path, attribute, value);
+ if (r < 0)
+ log_unit_full(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to set '%s' attribute on '%s' to '%.*s': %m",
+ strna(attribute), isempty(u->cgroup_path) ? "/" : u->cgroup_path, (int) strcspn(value, NEWLINE), value);
+
+ return r;
+}
+
static void cgroup_compat_warn(void) {
static bool cgroup_compat_warned = false;
void cgroup_context_init(CGroupContext *c) {
assert(c);
- /* Initialize everything to the kernel defaults, assuming the
- * structure is preinitialized to 0 */
+ /* Initialize everything to the kernel defaults. */
- c->cpu_weight = CGROUP_WEIGHT_INVALID;
- c->startup_cpu_weight = CGROUP_WEIGHT_INVALID;
- c->cpu_quota_per_sec_usec = USEC_INFINITY;
+ *c = (CGroupContext) {
+ .cpu_weight = CGROUP_WEIGHT_INVALID,
+ .startup_cpu_weight = CGROUP_WEIGHT_INVALID,
+ .cpu_quota_per_sec_usec = USEC_INFINITY,
- c->cpu_shares = CGROUP_CPU_SHARES_INVALID;
- c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID;
+ .cpu_shares = CGROUP_CPU_SHARES_INVALID,
+ .startup_cpu_shares = CGROUP_CPU_SHARES_INVALID,
- c->memory_high = CGROUP_LIMIT_MAX;
- c->memory_max = CGROUP_LIMIT_MAX;
- c->memory_swap_max = CGROUP_LIMIT_MAX;
+ .memory_high = CGROUP_LIMIT_MAX,
+ .memory_max = CGROUP_LIMIT_MAX,
+ .memory_swap_max = CGROUP_LIMIT_MAX,
- c->memory_limit = CGROUP_LIMIT_MAX;
+ .memory_limit = CGROUP_LIMIT_MAX,
- c->io_weight = CGROUP_WEIGHT_INVALID;
- c->startup_io_weight = CGROUP_WEIGHT_INVALID;
+ .io_weight = CGROUP_WEIGHT_INVALID,
+ .startup_io_weight = CGROUP_WEIGHT_INVALID,
- c->blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID;
- c->startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID;
+ .blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID,
+ .startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID,
- c->tasks_max = (uint64_t) -1;
+ .tasks_max = CGROUP_LIMIT_MAX,
+ };
}
void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) {
major(st.st_rdev), minor(st.st_rdev),
acc);
+ /* Changing the devices list of a populated cgroup might result in EINVAL, hence ignore EINVAL here. */
+
r = cg_set_attribute("devices", path, "devices.allow", buf);
if (r < 0)
- return log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING,
+ return log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING,
r, "Failed to set devices.allow on %s: %m", path);
return 0;
maj,
acc);
+ /* Changing the devices list of a populated cgroup might result in EINVAL, hence ignore EINVAL
+ * here. */
+
r = cg_set_attribute("devices", path, "devices.allow", buf);
if (r < 0)
- log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING,
+ log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING,
r, "Failed to set devices.allow on %s: %m", path);
}
}
return CGROUP_CPU_SHARES_DEFAULT;
}
-static void cgroup_apply_unified_cpu_config(Unit *u, uint64_t weight, uint64_t quota) {
- char buf[MAX(DECIMAL_STR_MAX(uint64_t) + 1, (DECIMAL_STR_MAX(usec_t) + 1) * 2)];
- int r;
+static void cgroup_apply_unified_cpu_weight(Unit *u, uint64_t weight) {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 2];
xsprintf(buf, "%" PRIu64 "\n", weight);
- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.weight", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set cpu.weight: %m");
+ (void) set_attribute_and_warn(u, "cpu", "cpu.weight", buf);
+}
+
+static void cgroup_apply_unified_cpu_quota(Unit *u, usec_t quota) {
+ char buf[(DECIMAL_STR_MAX(usec_t) + 1) * 2 + 1];
if (quota != USEC_INFINITY)
xsprintf(buf, USEC_FMT " " USEC_FMT "\n",
quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC, CGROUP_CPU_QUOTA_PERIOD_USEC);
else
xsprintf(buf, "max " USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC);
-
- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.max", buf);
-
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set cpu.max: %m");
+ (void) set_attribute_and_warn(u, "cpu", "cpu.max", buf);
}
-static void cgroup_apply_legacy_cpu_config(Unit *u, uint64_t shares, uint64_t quota) {
- char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1];
- int r;
+static void cgroup_apply_legacy_cpu_shares(Unit *u, uint64_t shares) {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 2];
xsprintf(buf, "%" PRIu64 "\n", shares);
- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.shares", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set cpu.shares: %m");
+ (void) set_attribute_and_warn(u, "cpu", "cpu.shares", buf);
+}
+
+static void cgroup_apply_legacy_cpu_quota(Unit *u, usec_t quota) {
+ char buf[DECIMAL_STR_MAX(usec_t) + 2];
xsprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC);
- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_period_us", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set cpu.cfs_period_us: %m");
+ (void) set_attribute_and_warn(u, "cpu", "cpu.cfs_period_us", buf);
if (quota != USEC_INFINITY) {
xsprintf(buf, USEC_FMT "\n", quota * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC);
- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", buf);
+ (void) set_attribute_and_warn(u, "cpu", "cpu.cfs_quota_us", buf);
} else
- r = cg_set_attribute("cpu", u->cgroup_path, "cpu.cfs_quota_us", "-1");
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set cpu.cfs_quota_us: %m");
+ (void) set_attribute_and_warn(u, "cpu", "cpu.cfs_quota_us", "-1\n");
}
static uint64_t cgroup_cpu_shares_to_weight(uint64_t shares) {
return;
xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), io_weight);
- r = cg_set_attribute("io", u->cgroup_path, "io.weight", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set io.weight: %m");
+ (void) set_attribute_and_warn(u, "io", "io.weight", buf);
}
static void cgroup_apply_blkio_device_weight(Unit *u, const char *dev_path, uint64_t blkio_weight) {
return;
xsprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), blkio_weight);
- r = cg_set_attribute("blkio", u->cgroup_path, "blkio.weight_device", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set blkio.weight_device: %m");
+ (void) set_attribute_and_warn(u, "blkio", "blkio.weight_device", buf);
}
static void cgroup_apply_io_device_latency(Unit *u, const char *dev_path, usec_t target) {
else
xsprintf(buf, "%u:%u target=max\n", major(dev), minor(dev));
- r = cg_set_attribute("io", u->cgroup_path, "io.latency", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set io.latency on cgroup %s: %m", u->cgroup_path);
+ (void) set_attribute_and_warn(u, "io", "io.latency", buf);
}
static void cgroup_apply_io_device_limit(Unit *u, const char *dev_path, uint64_t *limits) {
xsprintf(buf, "%u:%u rbps=%s wbps=%s riops=%s wiops=%s\n", major(dev), minor(dev),
limit_bufs[CGROUP_IO_RBPS_MAX], limit_bufs[CGROUP_IO_WBPS_MAX],
limit_bufs[CGROUP_IO_RIOPS_MAX], limit_bufs[CGROUP_IO_WIOPS_MAX]);
- r = cg_set_attribute("io", u->cgroup_path, "io.max", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set io.max: %m");
+ (void) set_attribute_and_warn(u, "io", "io.max", buf);
}
static void cgroup_apply_blkio_device_limit(Unit *u, const char *dev_path, uint64_t rbps, uint64_t wbps) {
return;
sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), rbps);
- r = cg_set_attribute("blkio", u->cgroup_path, "blkio.throttle.read_bps_device", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set blkio.throttle.read_bps_device: %m");
+ (void) set_attribute_and_warn(u, "blkio", "blkio.throttle.read_bps_device", buf);
sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), wbps);
- r = cg_set_attribute("blkio", u->cgroup_path, "blkio.throttle.write_bps_device", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set blkio.throttle.write_bps_device: %m");
+ (void) set_attribute_and_warn(u, "blkio", "blkio.throttle.write_bps_device", buf);
}
static bool cgroup_context_has_unified_memory_config(CGroupContext *c) {
}
static void cgroup_apply_unified_memory_limit(Unit *u, const char *file, uint64_t v) {
- char buf[DECIMAL_STR_MAX(uint64_t) + 1] = "max";
- int r;
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1] = "max\n";
if (v != CGROUP_LIMIT_MAX)
xsprintf(buf, "%" PRIu64 "\n", v);
- r = cg_set_attribute("memory", u->cgroup_path, file, buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set %s: %m", file);
+ (void) set_attribute_and_warn(u, "memory", file, buf);
}
static void cgroup_apply_firewall(Unit *u) {
const char *path;
CGroupContext *c;
- bool is_root;
+ bool is_host_root, is_local_root;
int r;
assert(u);
if (apply_mask == 0)
return;
- /* Some cgroup attributes are not supported on the root cgroup, hence silently ignore */
- is_root = unit_has_root_cgroup(u);
+ /* Some cgroup attributes are not supported on the host root cgroup, hence silently ignore them here. And other
+ * attributes should only be managed for cgroups further down the tree. */
+ is_local_root = unit_has_name(u, SPECIAL_ROOT_SLICE);
+ is_host_root = unit_has_host_root_cgroup(u);
assert_se(c = unit_get_cgroup_context(u));
assert_se(path = u->cgroup_path);
- if (is_root) /* Make sure we don't try to display messages with an empty path. */
+ if (is_local_root) /* Make sure we don't try to display messages with an empty path. */
path = "/";
/* We generally ignore errors caused by read-only mounted
* cgroup trees (assuming we are running in a container then),
* and missing cgroups, i.e. EROFS and ENOENT. */
- if ((apply_mask & CGROUP_MASK_CPU) && !is_root) {
+ if (apply_mask & CGROUP_MASK_CPU) {
bool has_weight, has_shares;
has_weight = cgroup_context_has_cpu_weight(c);
has_shares = cgroup_context_has_cpu_shares(c);
if (cg_all_unified() > 0) {
- uint64_t weight;
- if (has_weight)
- weight = cgroup_context_cpu_weight(c, state);
- else if (has_shares) {
- uint64_t shares = cgroup_context_cpu_shares(c, state);
+ /* In fully unified mode these attributes don't exist on the host cgroup root, and inside of
+ * containers we want to leave control of these to the container manager (and if delegation is
+ * used we couldn't even write to them if we wanted to). */
+ if (!is_local_root) {
+ uint64_t weight;
- weight = cgroup_cpu_shares_to_weight(shares);
+ if (has_weight)
+ weight = cgroup_context_cpu_weight(c, state);
+ else if (has_shares) {
+ uint64_t shares;
- log_cgroup_compat(u, "Applying [Startup]CPUShares %" PRIu64 " as [Startup]CPUWeight %" PRIu64 " on %s",
- shares, weight, path);
- } else
- weight = CGROUP_WEIGHT_DEFAULT;
+ shares = cgroup_context_cpu_shares(c, state);
+ weight = cgroup_cpu_shares_to_weight(shares);
- cgroup_apply_unified_cpu_config(u, weight, c->cpu_quota_per_sec_usec);
- } else {
- uint64_t shares;
+ log_cgroup_compat(u, "Applying [Startup]CPUShares %" PRIu64 " as [Startup]CPUWeight %" PRIu64 " on %s",
+ shares, weight, path);
+ } else
+ weight = CGROUP_WEIGHT_DEFAULT;
- if (has_weight) {
- uint64_t weight = cgroup_context_cpu_weight(c, state);
+ cgroup_apply_unified_cpu_weight(u, weight);
+ cgroup_apply_unified_cpu_quota(u, c->cpu_quota_per_sec_usec);
+ }
- shares = cgroup_cpu_weight_to_shares(weight);
+ } else {
+ /* Setting the weight makes very little sense on the host root cgroup, as there are no other
+ * cgroups at this level. And for containers we want to leave management of this to the
+ * container manager */
+ if (!is_local_root) {
+ uint64_t shares;
+
+ if (has_weight) {
+ uint64_t weight;
+
+ weight = cgroup_context_cpu_weight(c, state);
+ shares = cgroup_cpu_weight_to_shares(weight);
+
+ log_cgroup_compat(u, "Applying [Startup]CPUWeight %" PRIu64 " as [Startup]CPUShares %" PRIu64 " on %s",
+ weight, shares, path);
+ } else if (has_shares)
+ shares = cgroup_context_cpu_shares(c, state);
+ else
+ shares = CGROUP_CPU_SHARES_DEFAULT;
- log_cgroup_compat(u, "Applying [Startup]CPUWeight %" PRIu64 " as [Startup]CPUShares %" PRIu64 " on %s",
- weight, shares, path);
- } else if (has_shares)
- shares = cgroup_context_cpu_shares(c, state);
- else
- shares = CGROUP_CPU_SHARES_DEFAULT;
+ cgroup_apply_legacy_cpu_shares(u, shares);
+ }
- cgroup_apply_legacy_cpu_config(u, shares, c->cpu_quota_per_sec_usec);
+ /* The "cpu" quota attribute is available on the host root, hence manage it there. But in
+ * containers let's leave this to the container manager. */
+ if (is_host_root || !is_local_root)
+ cgroup_apply_legacy_cpu_quota(u, c->cpu_quota_per_sec_usec);
}
}
- if (apply_mask & CGROUP_MASK_IO) {
- bool has_io = cgroup_context_has_io_config(c);
- bool has_blockio = cgroup_context_has_blockio_config(c);
+ /* The 'io' controller attributes are not exported on the host's root cgroup (being a pure cgroupsv2
+ * controller), and in case of containers we want to leave control of these attributes to the container manager
+ * (and we couldn't access that stuff anyway, even if we tried if proper delegation is used). */
+ if ((apply_mask & CGROUP_MASK_IO) && !is_local_root) {
+ char buf[8+DECIMAL_STR_MAX(uint64_t)+1];
+ bool has_io, has_blockio;
+ uint64_t weight;
- if (!is_root) {
- char buf[8+DECIMAL_STR_MAX(uint64_t)+1];
- uint64_t weight;
+ has_io = cgroup_context_has_io_config(c);
+ has_blockio = cgroup_context_has_blockio_config(c);
- if (has_io)
- weight = cgroup_context_io_weight(c, state);
- else if (has_blockio) {
- uint64_t blkio_weight = cgroup_context_blkio_weight(c, state);
+ if (has_io)
+ weight = cgroup_context_io_weight(c, state);
+ else if (has_blockio) {
+ uint64_t blkio_weight;
- weight = cgroup_weight_blkio_to_io(blkio_weight);
+ blkio_weight = cgroup_context_blkio_weight(c, state);
+ weight = cgroup_weight_blkio_to_io(blkio_weight);
- log_cgroup_compat(u, "Applying [Startup]BlockIOWeight %" PRIu64 " as [Startup]IOWeight %" PRIu64,
- blkio_weight, weight);
- } else
- weight = CGROUP_WEIGHT_DEFAULT;
-
- xsprintf(buf, "default %" PRIu64 "\n", weight);
- r = cg_set_attribute("io", path, "io.weight", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set io.weight: %m");
+ log_cgroup_compat(u, "Applying [Startup]BlockIOWeight %" PRIu64 " as [Startup]IOWeight %" PRIu64,
+ blkio_weight, weight);
+ } else
+ weight = CGROUP_WEIGHT_DEFAULT;
- if (has_io) {
- CGroupIODeviceWeight *w;
+ xsprintf(buf, "default %" PRIu64 "\n", weight);
+ (void) set_attribute_and_warn(u, "io", "io.weight", buf);
- LIST_FOREACH(device_weights, w, c->io_device_weights)
- cgroup_apply_io_device_weight(u, w->path, w->weight);
- } else if (has_blockio) {
- CGroupBlockIODeviceWeight *w;
+ if (has_io) {
+ CGroupIODeviceLatency *latency;
+ CGroupIODeviceLimit *limit;
+ CGroupIODeviceWeight *w;
- LIST_FOREACH(device_weights, w, c->blockio_device_weights) {
- weight = cgroup_weight_blkio_to_io(w->weight);
+ LIST_FOREACH(device_weights, w, c->io_device_weights)
+ cgroup_apply_io_device_weight(u, w->path, w->weight);
- log_cgroup_compat(u, "Applying BlockIODeviceWeight %" PRIu64 " as IODeviceWeight %" PRIu64 " for %s",
- w->weight, weight, w->path);
-
- cgroup_apply_io_device_weight(u, w->path, weight);
- }
- }
+ LIST_FOREACH(device_limits, limit, c->io_device_limits)
+ cgroup_apply_io_device_limit(u, limit->path, limit->limits);
- if (has_io) {
- CGroupIODeviceLatency *l;
+ LIST_FOREACH(device_latencies, latency, c->io_device_latencies)
+ cgroup_apply_io_device_latency(u, latency->path, latency->target_usec);
- LIST_FOREACH(device_latencies, l, c->io_device_latencies)
- cgroup_apply_io_device_latency(u, l->path, l->target_usec);
- }
- }
+ } else if (has_blockio) {
+ CGroupBlockIODeviceWeight *w;
+ CGroupBlockIODeviceBandwidth *b;
- if (has_io) {
- CGroupIODeviceLimit *l;
+ LIST_FOREACH(device_weights, w, c->blockio_device_weights) {
+ weight = cgroup_weight_blkio_to_io(w->weight);
- LIST_FOREACH(device_limits, l, c->io_device_limits)
- cgroup_apply_io_device_limit(u, l->path, l->limits);
+ log_cgroup_compat(u, "Applying BlockIODeviceWeight %" PRIu64 " as IODeviceWeight %" PRIu64 " for %s",
+ w->weight, weight, w->path);
- } else if (has_blockio) {
- CGroupBlockIODeviceBandwidth *b;
+ cgroup_apply_io_device_weight(u, w->path, weight);
+ }
LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) {
uint64_t limits[_CGROUP_IO_LIMIT_TYPE_MAX];
}
if (apply_mask & CGROUP_MASK_BLKIO) {
- bool has_io = cgroup_context_has_io_config(c);
- bool has_blockio = cgroup_context_has_blockio_config(c);
+ bool has_io, has_blockio;
+
+ has_io = cgroup_context_has_io_config(c);
+ has_blockio = cgroup_context_has_blockio_config(c);
- if (!is_root) {
+ /* Applying a 'weight' never makes sense for the host root cgroup, and for containers this should be
+ * left to our container manager, too. */
+ if (!is_local_root) {
char buf[DECIMAL_STR_MAX(uint64_t)+1];
uint64_t weight;
if (has_io) {
- uint64_t io_weight = cgroup_context_io_weight(c, state);
+ uint64_t io_weight;
+ io_weight = cgroup_context_io_weight(c, state);
weight = cgroup_weight_io_to_blkio(cgroup_context_io_weight(c, state));
log_cgroup_compat(u, "Applying [Startup]IOWeight %" PRIu64 " as [Startup]BlockIOWeight %" PRIu64,
weight = CGROUP_BLKIO_WEIGHT_DEFAULT;
xsprintf(buf, "%" PRIu64 "\n", weight);
- r = cg_set_attribute("blkio", path, "blkio.weight", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set blkio.weight: %m");
+ (void) set_attribute_and_warn(u, "blkio", "blkio.weight", buf);
if (has_io) {
CGroupIODeviceWeight *w;
}
}
- if (has_io) {
- CGroupIODeviceLimit *l;
+ /* The bandwith limits are something that make sense to be applied to the host's root but not container
+ * roots, as there we want the container manager to handle it */
+ if (is_host_root || !is_local_root) {
+ if (has_io) {
+ CGroupIODeviceLimit *l;
- LIST_FOREACH(device_limits, l, c->io_device_limits) {
- log_cgroup_compat(u, "Applying IO{Read|Write}Bandwidth %" PRIu64 " %" PRIu64 " as BlockIO{Read|Write}BandwidthMax for %s",
- l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX], l->path);
+ LIST_FOREACH(device_limits, l, c->io_device_limits) {
+ log_cgroup_compat(u, "Applying IO{Read|Write}Bandwidth %" PRIu64 " %" PRIu64 " as BlockIO{Read|Write}BandwidthMax for %s",
+ l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX], l->path);
- cgroup_apply_blkio_device_limit(u, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX]);
- }
- } else if (has_blockio) {
- CGroupBlockIODeviceBandwidth *b;
+ cgroup_apply_blkio_device_limit(u, l->path, l->limits[CGROUP_IO_RBPS_MAX], l->limits[CGROUP_IO_WBPS_MAX]);
+ }
+ } else if (has_blockio) {
+ CGroupBlockIODeviceBandwidth *b;
- LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths)
- cgroup_apply_blkio_device_limit(u, b->path, b->rbps, b->wbps);
+ LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths)
+ cgroup_apply_blkio_device_limit(u, b->path, b->rbps, b->wbps);
+ }
}
}
- if ((apply_mask & CGROUP_MASK_MEMORY) && !is_root) {
- if (cg_all_unified() > 0) {
- uint64_t max, swap_max = CGROUP_LIMIT_MAX;
+ if (apply_mask & CGROUP_MASK_MEMORY) {
- if (cgroup_context_has_unified_memory_config(c)) {
- max = c->memory_max;
- swap_max = c->memory_swap_max;
- } else {
- max = c->memory_limit;
+ if (cg_all_unified() > 0) {
+ /* In unified mode 'memory' attributes do not exist on the root cgroup. And if we run in a
+ * container we want to leave control to the container manager (and if proper delegation is
+ * used we couldn't even write to this if we wanted to. */
+ if (!is_local_root) {
+ uint64_t max, swap_max = CGROUP_LIMIT_MAX;
+
+ if (cgroup_context_has_unified_memory_config(c)) {
+ max = c->memory_max;
+ swap_max = c->memory_swap_max;
+ } else {
+ max = c->memory_limit;
+
+ if (max != CGROUP_LIMIT_MAX)
+ log_cgroup_compat(u, "Applying MemoryLimit=%" PRIu64 " as MemoryMax=", max);
+ }
- if (max != CGROUP_LIMIT_MAX)
- log_cgroup_compat(u, "Applying MemoryLimit %" PRIu64 " as MemoryMax", max);
+ cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min);
+ cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low);
+ cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
+ cgroup_apply_unified_memory_limit(u, "memory.max", max);
+ cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
}
-
- cgroup_apply_unified_memory_limit(u, "memory.min", c->memory_min);
- cgroup_apply_unified_memory_limit(u, "memory.low", c->memory_low);
- cgroup_apply_unified_memory_limit(u, "memory.high", c->memory_high);
- cgroup_apply_unified_memory_limit(u, "memory.max", max);
- cgroup_apply_unified_memory_limit(u, "memory.swap.max", swap_max);
} else {
- char buf[DECIMAL_STR_MAX(uint64_t) + 1];
- uint64_t val;
- if (cgroup_context_has_unified_memory_config(c)) {
- val = c->memory_max;
- log_cgroup_compat(u, "Applying MemoryMax %" PRIi64 " as MemoryLimit", val);
- } else
- val = c->memory_limit;
+ /* In legacy mode 'memory' exists on the host root, but in container mode we want to leave it
+ * to the container manager around us */
+ if (is_host_root || !is_local_root) {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1];
+ uint64_t val;
- if (val == CGROUP_LIMIT_MAX)
- strncpy(buf, "-1\n", sizeof(buf));
- else
- xsprintf(buf, "%" PRIu64 "\n", val);
+ if (cgroup_context_has_unified_memory_config(c)) {
+ val = c->memory_max;
+ log_cgroup_compat(u, "Applying MemoryMax=%" PRIi64 " as MemoryLimit=", val);
+ } else
+ val = c->memory_limit;
- r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf);
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set memory.limit_in_bytes: %m");
+ if (val == CGROUP_LIMIT_MAX)
+ strncpy(buf, "-1\n", sizeof(buf));
+ else
+ xsprintf(buf, "%" PRIu64 "\n", val);
+
+ (void) set_attribute_and_warn(u, "memory", "memory.limit_in_bytes", buf);
+ }
}
}
- if ((apply_mask & (CGROUP_MASK_DEVICES | CGROUP_MASK_BPF_DEVICES)) && !is_root) {
+ /* On cgroupsv2 we can apply BPF everywhre. On cgroupsv1 we apply it everywhere except for the root of
+ * containers, where we leave this to the manager */
+ if ((apply_mask & (CGROUP_MASK_DEVICES | CGROUP_MASK_BPF_DEVICES)) &&
+ (is_host_root || cg_all_unified() > 0 || !is_local_root)) {
_cleanup_(bpf_program_unrefp) BPFProgram *prog = NULL;
CGroupDeviceAllow *a;
if (r < 0)
log_unit_warning_errno(u, r, "Failed to initialize device control bpf program: %m");
} else {
- /* Changing the devices list of a populated cgroup
- * might result in EINVAL, hence ignore EINVAL
+ /* Changing the devices list of a populated cgroup might result in EINVAL, hence ignore EINVAL
* here. */
if (c->device_allow || c->device_policy != CGROUP_AUTO)
else
r = cg_set_attribute("devices", path, "devices.allow", "a");
if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to reset devices.list: %m");
+ log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r,
+ "Failed to reset devices.allow/devices.deny: %m");
}
if (c->device_policy == CGROUP_CLOSED ||
if (apply_mask & CGROUP_MASK_PIDS) {
- if (is_root) {
+ if (is_host_root) {
/* So, the "pids" controller does not expose anything on the root cgroup, in order not to
* replicate knobs exposed elsewhere needlessly. We abstract this away here however, and when
* the knobs of the root cgroup are modified propagate this to the relevant sysctls. There's a
r = 0;
if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
+ log_unit_full(u, LOG_LEVEL_CGROUP_WRITE(r), r,
"Failed to write to tasks limit sysctls: %m");
+ }
- } else {
+ /* The attribute itself is not available on the host root cgroup, and in the container case we want to
+ * leave it for the container manager. */
+ if (!is_local_root) {
if (c->tasks_max != CGROUP_LIMIT_MAX) {
char buf[DECIMAL_STR_MAX(uint64_t) + 2];
sprintf(buf, "%" PRIu64 "\n", c->tasks_max);
- r = cg_set_attribute("pids", path, "pids.max", buf);
+ (void) set_attribute_and_warn(u, "pids", "pids.max", buf);
} else
- r = cg_set_attribute("pids", path, "pids.max", "max");
- if (r < 0)
- log_unit_full(u, IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set pids.max: %m");
+ (void) set_attribute_and_warn(u, "pids", "pids.max", "max\n");
}
}
cgroup_apply_firewall(u);
}
-CGroupMask cgroup_context_get_mask(CGroupContext *c) {
+static bool unit_get_needs_bpf_firewall(Unit *u) {
+ CGroupContext *c;
+ Unit *p;
+ assert(u);
+
+ c = unit_get_cgroup_context(u);
+ if (!c)
+ return false;
+
+ if (c->ip_accounting ||
+ c->ip_address_allow ||
+ c->ip_address_deny)
+ return true;
+
+ /* If any parent slice has an IP access list defined, it applies too */
+ for (p = UNIT_DEREF(u->slice); p; p = UNIT_DEREF(p->slice)) {
+ c = unit_get_cgroup_context(p);
+ if (!c)
+ return false;
+
+ if (c->ip_address_allow ||
+ c->ip_address_deny)
+ return true;
+ }
+
+ return false;
+}
+
+static CGroupMask cgroup_context_get_mask(CGroupContext *c) {
CGroupMask mask = 0;
/* Figure out which controllers we need, based on the cgroup context object */
return CGROUP_MASK_EXTEND_JOINED(mask);
}
-CGroupMask unit_get_bpf_mask(Unit *u) {
+static CGroupMask unit_get_bpf_mask(Unit *u) {
CGroupMask mask = 0;
/* Figure out which controllers we need, based on the cgroup context, possibly taking into account children
CGroupMask unit_get_own_mask(Unit *u) {
CGroupContext *c;
- /* Returns the mask of controllers the unit needs for itself */
+ /* Returns the mask of controllers the unit needs for itself. If a unit is not properly loaded, return an empty
+ * mask, as we shouldn't reflect it in the cgroup hierarchy then. */
+
+ if (u->load_state != UNIT_LOADED)
+ return 0;
c = unit_get_cgroup_context(u);
if (!c)
/* Returns the mask of controllers all of the unit's children require, merged */
if (u->cgroup_members_mask_valid)
- return u->cgroup_members_mask;
+ return u->cgroup_members_mask; /* Use cached value if possible */
u->cgroup_members_mask = 0;
return mask;
}
-bool unit_get_needs_bpf_firewall(Unit *u) {
- CGroupContext *c;
- Unit *p;
+void unit_invalidate_cgroup_members_masks(Unit *u) {
assert(u);
- c = unit_get_cgroup_context(u);
- if (!c)
- return false;
-
- if (c->ip_accounting ||
- c->ip_address_allow ||
- c->ip_address_deny)
- return true;
+ /* Recurse invalidate the member masks cache all the way up the tree */
+ u->cgroup_members_mask_valid = false;
- /* If any parent slice has an IP access list defined, it applies too */
- for (p = UNIT_DEREF(u->slice); p; p = UNIT_DEREF(p->slice)) {
- c = unit_get_cgroup_context(p);
- if (!c)
- return false;
-
- if (c->ip_address_allow ||
- c->ip_address_deny)
- return true;
- }
-
- return false;
-}
-
-/* Recurse from a unit up through its containing slices, propagating
- * mask bits upward. A unit is also member of itself. */
-void unit_update_cgroup_members_masks(Unit *u) {
- CGroupMask m;
- bool more;
-
- assert(u);
-
- /* Calculate subtree mask */
- m = unit_get_subtree_mask(u);
-
- /* See if anything changed from the previous invocation. If
- * not, we're done. */
- if (u->cgroup_subtree_mask_valid && m == u->cgroup_subtree_mask)
- return;
-
- more =
- u->cgroup_subtree_mask_valid &&
- ((m & ~u->cgroup_subtree_mask) != 0) &&
- ((~m & u->cgroup_subtree_mask) == 0);
-
- u->cgroup_subtree_mask = m;
- u->cgroup_subtree_mask_valid = true;
-
- if (UNIT_ISSET(u->slice)) {
- Unit *s = UNIT_DEREF(u->slice);
-
- if (more)
- /* There's more set now than before. We
- * propagate the new mask to the parent's mask
- * (not caring if it actually was valid or
- * not). */
-
- s->cgroup_members_mask |= m;
-
- else
- /* There's less set now than before (or we
- * don't know), we need to recalculate
- * everything, so let's invalidate the
- * parent's members mask */
-
- s->cgroup_members_mask_valid = false;
-
- /* And now make sure that this change also hits our
- * grandparents */
- unit_update_cgroup_members_masks(s);
- }
+ if (UNIT_ISSET(u->slice))
+ unit_invalidate_cgroup_members_masks(UNIT_DEREF(u->slice));
}
const char *unit_get_realized_cgroup_path(Unit *u, CGroupMask mask) {
CGroupMask target_mask,
CGroupMask enable_mask) {
- CGroupContext *c;
- int r;
bool created;
+ int r;
assert(u);
- c = unit_get_cgroup_context(u);
- if (!c)
+ if (!UNIT_HAS_CGROUP_CONTEXT(u))
return 0;
/* Figure out our cgroup path */
(void) unit_watch_cgroup(u);
/* Preserve enabled controllers in delegated units, adjust others. */
- if (created || !unit_cgroup_delegate(u)) {
+ if (created || !u->cgroup_realized || !unit_cgroup_delegate(u)) {
+ CGroupMask result_mask = 0;
/* Enable all controllers we need */
- r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path);
+ r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path, &result_mask);
if (r < 0)
- log_unit_warning_errno(u, r, "Failed to enable controllers on cgroup %s, ignoring: %m",
- u->cgroup_path);
+ log_unit_warning_errno(u, r, "Failed to enable/disable controllers on cgroup %s, ignoring: %m", u->cgroup_path);
+
+ /* If we just turned off a controller, this might release the controller for our parent too, let's
+ * enqueue the parent for re-realization in that case again. */
+ if (UNIT_ISSET(u->slice)) {
+ CGroupMask turned_off;
+
+ turned_off = (u->cgroup_realized ? u->cgroup_enabled_mask & ~result_mask : 0);
+ if (turned_off != 0) {
+ Unit *parent;
+
+ /* Force the parent to propagate the enable mask to the kernel again, by invalidating
+ * the controller we just turned off. */
+
+ for (parent = UNIT_DEREF(u->slice); parent; parent = UNIT_DEREF(parent->slice))
+ unit_invalidate_cgroup(parent, turned_off);
+ }
+ }
+
+ /* Remember what's actually enabled now */
+ u->cgroup_enabled_mask = result_mask;
}
/* Keep track that this is now realized */
u->cgroup_realized = true;
u->cgroup_realized_mask = target_mask;
- u->cgroup_enabled_mask = enable_mask;
if (u->type != UNIT_SLICE && !unit_cgroup_delegate(u)) {
assert(u);
+ /* Returns true if this unit is fully realized. We check four things:
+ *
+ * 1. Whether the cgroup was created at all
+ * 2. Whether the cgroup was created in all the hierarchies we need it to be created in (in case of cgroupsv1)
+ * 3. Whether the cgroup has all the right controllers enabled (in case of cgroupsv2)
+ * 4. Whether the invalidation mask is currently zero
+ *
+ * If you wonder why we mask the target realization and enable mask with CGROUP_MASK_V1/CGROUP_MASK_V2: note
+ * that there are three sets of bitmasks: CGROUP_MASK_V1 (for real cgroupv1 controllers), CGROUP_MASK_V2 (for
+ * real cgroupv2 controllers) and CGROUP_MASK_BPF (for BPF-based pseudo-controllers). Now, cgroup_realized_mask
+ * is only matters for cgroupsv1 controllers, and cgroup_enabled_mask only used for cgroupsv2, and if they
+ * differ in the others, we don't really care. (After all, the cgroup_enabled_mask tracks with controllers are
+ * enabled through cgroup.subtree_control, and since the BPF pseudo-controllers don't show up there, they
+ * simply don't matter. */
+
return u->cgroup_realized &&
- u->cgroup_realized_mask == target_mask &&
- u->cgroup_enabled_mask == enable_mask &&
+ ((u->cgroup_realized_mask ^ target_mask) & CGROUP_MASK_V1) == 0 &&
+ ((u->cgroup_enabled_mask ^ enable_mask) & CGROUP_MASK_V2) == 0 &&
u->cgroup_invalidated_mask == 0;
}
-static void unit_add_to_cgroup_realize_queue(Unit *u) {
+void unit_add_to_cgroup_realize_queue(Unit *u) {
assert(u);
if (u->in_cgroup_realize_queue)
void unit_release_cgroup(Unit *u) {
assert(u);
- /* Forgets all cgroup details for this cgroup */
+ /* Forgets all cgroup details for this cgroup — but does *not* destroy the cgroup. This is hence OK to call
+ * when we close down everything for reexecution, where we really want to leave the cgroup in place. */
if (u->cgroup_path) {
(void) hashmap_remove(u->manager->cgroup_unit, u->cgroup_path);
(void) sd_event_source_set_description(m->cgroup_inotify_event_source, "cgroup-inotify");
- } else if (MANAGER_IS_SYSTEM(m) && !MANAGER_IS_TEST_RUN(m)) {
+ } else if (MANAGER_IS_SYSTEM(m) && manager_owns_host_root_cgroup(m) && !MANAGER_IS_TEST_RUN(m)) {
/* On the legacy hierarchy we only get notifications via cgroup agents. (Which isn't really reliable,
* since it does not generate events when control groups with children run empty. */
return -ENODATA;
/* The root cgroup doesn't expose this information, let's get it from /proc instead */
- if (unit_has_root_cgroup(u))
+ if (unit_has_host_root_cgroup(u))
return procfs_memory_get_current(ret);
if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0)
return -ENODATA;
/* The root cgroup doesn't expose this information, let's get it from /proc instead */
- if (unit_has_root_cgroup(u))
+ if (unit_has_host_root_cgroup(u))
return procfs_tasks_get_current(ret);
if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0)
return -ENODATA;
/* The root cgroup doesn't expose this information, let's get it from /proc instead */
- if (unit_has_root_cgroup(u))
+ if (unit_has_host_root_cgroup(u))
return procfs_cpu_get_usage(ret);
r = cg_all_unified();
if (m & (CGROUP_MASK_CPU | CGROUP_MASK_CPUACCT))
m |= CGROUP_MASK_CPU | CGROUP_MASK_CPUACCT;
- if ((u->cgroup_realized_mask & m) == 0) /* NOP? */
+ if (FLAGS_SET(u->cgroup_invalidated_mask, m)) /* NOP? */
return;
- u->cgroup_realized_mask &= ~m;
+ u->cgroup_invalidated_mask |= m;
unit_add_to_cgroup_realize_queue(u);
}
void cgroup_context_done(CGroupContext *c);
void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix);
-CGroupMask cgroup_context_get_mask(CGroupContext *c);
-
void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a);
void cgroup_context_free_io_device_weight(CGroupContext *c, CGroupIODeviceWeight *w);
void cgroup_context_free_io_device_limit(CGroupContext *c, CGroupIODeviceLimit *l);
CGroupMask unit_get_members_mask(Unit *u);
CGroupMask unit_get_siblings_mask(Unit *u);
CGroupMask unit_get_subtree_mask(Unit *u);
-
CGroupMask unit_get_target_mask(Unit *u);
CGroupMask unit_get_enable_mask(Unit *u);
-bool unit_get_needs_bpf_firewall(Unit *u);
-CGroupMask unit_get_bpf_mask(Unit *u);
+void unit_invalidate_cgroup_members_masks(Unit *u);
-void unit_update_cgroup_members_masks(Unit *u);
+void unit_add_to_cgroup_realize_queue(Unit *u);
const char *unit_get_realized_cgroup_path(Unit *u, CGroupMask mask);
char *unit_default_cgroup_path(Unit *u);
cc ? cc->name : false; \
})
-bool manager_owns_root_cgroup(Manager *m);
-bool unit_has_root_cgroup(Unit *u);
+bool manager_owns_host_root_cgroup(Manager *m);
+bool unit_has_host_root_cgroup(Unit *u);
int manager_notify_cgroup_empty(Manager *m, const char *group);
int bus_mount_commit_properties(Unit *u) {
assert(u);
- unit_update_cgroup_members_masks(u);
+ unit_invalidate_cgroup_members_masks(u);
unit_realize_cgroup(u);
return 0;
int bus_scope_commit_properties(Unit *u) {
assert(u);
- unit_update_cgroup_members_masks(u);
+ unit_invalidate_cgroup_members_masks(u);
unit_realize_cgroup(u);
return 0;
int bus_service_commit_properties(Unit *u) {
assert(u);
- unit_update_cgroup_members_masks(u);
+ unit_invalidate_cgroup_members_masks(u);
unit_realize_cgroup(u);
return 0;
int bus_slice_commit_properties(Unit *u) {
assert(u);
- unit_update_cgroup_members_masks(u);
+ unit_invalidate_cgroup_members_masks(u);
unit_realize_cgroup(u);
return 0;
int bus_socket_commit_properties(Unit *u) {
assert(u);
- unit_update_cgroup_members_masks(u);
+ unit_invalidate_cgroup_members_masks(u);
unit_realize_cgroup(u);
return 0;
int bus_swap_commit_properties(Unit *u) {
assert(u);
- unit_update_cgroup_members_masks(u);
+ unit_invalidate_cgroup_members_masks(u);
unit_realize_cgroup(u);
return 0;
return 0;
}
- if (!startswith(resolved, "block-") && !startswith(resolved, "char-")) {
+ if (!STARTSWITH_SET(resolved, "block-", "char-")) {
r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
if (r < 0)
assert(m);
r = slice_make_perpetual(m, SPECIAL_ROOT_SLICE, &u);
- if (r >= 0 && manager_owns_root_cgroup(m)) {
+ if (r >= 0 && manager_owns_host_root_cgroup(m)) {
Slice *s = SLICE(u);
/* If we are managing the root cgroup then this means our root slice covers the whole system, which
#include "process-util.h"
#include "signal-util.h"
#include "string-util.h"
+#include "strv.h"
#include "umount.h"
#include "util.h"
#include "virt.h"
if (!u)
return;
+ if (UNIT_ISSET(u->slice)) {
+ /* A unit is being dropped from the tree, make sure our parent slice recalculates the member mask */
+ unit_invalidate_cgroup_members_masks(UNIT_DEREF(u->slice));
+
+ /* And make sure the parent is realized again, updating cgroup memberships */
+ unit_add_to_cgroup_realize_queue(UNIT_DEREF(u->slice));
+ }
+
u->transient_file = safe_fclose(u->transient_file);
if (!MANAGER_IS_RELOADING(u->manager))
(void) cg_mask_to_string(u->cgroup_realized_mask, &s);
fprintf(f, "%s\tCGroup realized mask: %s\n", prefix, strnull(s));
}
+
if (u->cgroup_enabled_mask != 0) {
_cleanup_free_ char *s = NULL;
(void) cg_mask_to_string(u->cgroup_enabled_mask, &s);
fprintf(f, "%s\tCGroup enabled mask: %s\n", prefix, strnull(s));
}
+
m = unit_get_own_mask(u);
if (m != 0) {
_cleanup_free_ char *s = NULL;
(void) cg_mask_to_string(m, &s);
fprintf(f, "%s\tCGroup own mask: %s\n", prefix, strnull(s));
}
+
m = unit_get_members_mask(u);
if (m != 0) {
_cleanup_free_ char *s = NULL;
fprintf(f, "%s\tCGroup members mask: %s\n", prefix, strnull(s));
}
+ m = unit_get_delegate_mask(u);
+ if (m != 0) {
+ _cleanup_free_ char *s = NULL;
+ (void) cg_mask_to_string(m, &s);
+ fprintf(f, "%s\tCGroup delegate mask: %s\n", prefix, strnull(s));
+ }
+
SET_FOREACH(t, u->names, i)
fprintf(f, "%s\tName: %s\n", prefix, t);
if (u->job_running_timeout != USEC_INFINITY && u->job_running_timeout > u->job_timeout)
log_unit_warning(u, "JobRunningTimeoutSec= is greater than JobTimeoutSec=, it has no effect.");
- unit_update_cgroup_members_masks(u);
+ /* We finished loading, let's ensure our parents recalculate the members mask */
+ unit_invalidate_cgroup_members_masks(u);
}
assert((u->load_state != UNIT_MERGED) == !u->merged_into);
/* Counterparts in the cgroup filesystem */
char *cgroup_path;
- CGroupMask cgroup_realized_mask;
- CGroupMask cgroup_enabled_mask;
- CGroupMask cgroup_invalidated_mask;
- CGroupMask cgroup_subtree_mask;
- CGroupMask cgroup_members_mask;
+ CGroupMask cgroup_realized_mask; /* In which hierarchies does this unit's cgroup exist? (only relevant on cgroupsv1) */
+ CGroupMask cgroup_enabled_mask; /* Which controllers are enabled (or more correctly: enabled for the children) for this unit's cgroup? (only relevant on cgroupsv2) */
+ CGroupMask cgroup_invalidated_mask; /* A mask specifiying controllers which shall be considered invalidated, and require re-realization */
+ CGroupMask cgroup_members_mask; /* A cache for the controllers required by all children of this cgroup (only relevant for slice units) */
int cgroup_inotify_wd;
/* Device Controller BPF program */
bool cgroup_realized:1;
bool cgroup_members_mask_valid:1;
- bool cgroup_subtree_mask_valid:1;
/* Reset cgroup accounting next time we fork something off */
bool reset_accounting:1;
continue;
}
- uuid = startswith(device, "UUID=");
+ uuid = STARTSWITH_SET(device, "UUID=", "luks-");
if (!uuid)
uuid = path_startswith(device, "/dev/disk/by-uuid/");
- if (!uuid)
- uuid = startswith(name, "luks-");
if (uuid)
d = hashmap_get(arg_disks, uuid);
printf("%s... ", name);
fflush(stdout);
for (int j = 0; j < MIN_NUMBER_OF_RUNS; j++)
- (void) LLVMFuzzerTestOneInput((uint8_t*)buf, size);
+ if (LLVMFuzzerTestOneInput((uint8_t*)buf, size) == EXIT_TEST_SKIP)
+ return EXIT_TEST_SKIP;
printf("ok\n");
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <sched.h>
+#include <sys/mount.h>
+#include <unistd.h>
+
+#include "fd-util.h"
+#include "fs-util.h"
+#include "fuzz.h"
+#include "log.h"
+#include "mkdir.h"
+#include "missing.h"
+#include "rm-rf.h"
+#include "string-util.h"
+#include "tests.h"
+#include "udev.h"
+
+static const struct fakefs {
+ const char *target;
+ bool ignore_mount_error;
+} fakefss[] = {
+ { "/sys", false },
+ { "/dev", false },
+ { "/run", false },
+ { "/etc", false },
+ { UDEVLIBEXECDIR "/rules.d", true },
+};
+
+static int setup_mount_namespace(void) {
+ static thread_local bool is_namespaced = false;
+
+ if (is_namespaced)
+ return 1;
+
+ if (unshare(CLONE_NEWNS) < 0)
+ return log_error_errno(errno, "Failed to call unshare(): %m");
+
+ if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0)
+ return log_error_errno(errno, "Failed to mount / as private: %m");
+
+ is_namespaced = true;
+
+ return 1;
+}
+
+static int setup_fake_filesystems(const char *runtime_dir) {
+ for (unsigned i = 0; i < ELEMENTSOF(fakefss); i++)
+ if (mount(runtime_dir, fakefss[i].target, NULL, MS_BIND, NULL) < 0) {
+ log_full_errno(fakefss[i].ignore_mount_error ? LOG_DEBUG : LOG_ERR, errno, "Failed to mount %s: %m", fakefss[i].target);
+ if (!fakefss[i].ignore_mount_error)
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int cleanup_fake_filesystems(const char *runtime_dir) {
+ for (unsigned i = 0; i < ELEMENTSOF(fakefss); i++)
+ if (umount(fakefss[i].target) < 0) {
+ log_full_errno(fakefss[i].ignore_mount_error ? LOG_DEBUG : LOG_ERR, errno, "Failed to umount %s: %m", fakefss[i].target);
+ if (!fakefss[i].ignore_mount_error)
+ return -errno;
+ }
+ return 0;
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(udev_rules_freep) struct udev_rules *rules = NULL;
+ _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
+ FILE *f = NULL;
+
+ if (!getenv("SYSTEMD_LOG_LEVEL")) {
+ log_set_max_level_realm(LOG_REALM_UDEV, LOG_CRIT);
+ log_set_max_level_realm(LOG_REALM_SYSTEMD, LOG_CRIT);
+ }
+
+ if (setup_mount_namespace() < 0)
+ return EXIT_TEST_SKIP;
+
+ assert_se(runtime_dir = setup_fake_runtime_dir());
+
+ assert_se(setup_fake_filesystems(runtime_dir) >= 0);
+ assert_se(mkdir_p("/etc/udev/rules.d", 0755) >= 0);
+ f = fopen("/etc/udev/rules.d/fuzz.rules", "we");
+ assert_se(f);
+ if (size != 0)
+ assert_se(fwrite(data, size, 1, f) == 1);
+ assert_se(fclose(f) == 0);
+ rules = udev_rules_new(RESOLVE_NAME_EARLY);
+
+ assert_se(cleanup_fake_filesystems(runtime_dir) >= 0);
+
+ return 0;
+}
libshared],
[]],
+ [['src/fuzz/fuzz-udev-rules.c'],
+ [libudev_core,
+ libudev_static,
+ libsystemd_network,
+ libshared],
+ [threads,
+ libacl]],
+
[['src/fuzz/fuzz-compress.c'],
[libshared],
[]],
int curl_glue_new(CurlGlue **glue, sd_event *event) {
_cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
+ _cleanup_(curl_multi_cleanupp) CURL *c = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
int r;
- g = new0(CurlGlue, 1);
- if (!g)
- return -ENOMEM;
-
if (event)
- g->event = sd_event_ref(event);
+ e = sd_event_ref(event);
else {
- r = sd_event_default(&g->event);
+ r = sd_event_default(&e);
if (r < 0)
return r;
}
- g->curl = curl_multi_init();
- if (!g->curl)
+ c = curl_multi_init();
+ if (!c)
+ return -ENOMEM;
+
+ g = new(CurlGlue, 1);
+ if (!g)
return -ENOMEM;
+ *g = (CurlGlue) {
+ .event = TAKE_PTR(e),
+ .curl = TAKE_PTR(c),
+ };
+
if (curl_multi_setopt(g->curl, CURLMOPT_SOCKETDATA, g) != CURLM_OK)
return -EINVAL;
}
int curl_glue_make(CURL **ret, const char *url, void *userdata) {
+ _cleanup_(curl_easy_cleanupp) CURL *c = NULL;
const char *useragent;
- CURL *c;
- int r;
assert(ret);
assert(url);
/* curl_easy_setopt(c, CURLOPT_VERBOSE, 1L); */
- if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK) {
- r = -EIO;
- goto fail;
- }
+ if (curl_easy_setopt(c, CURLOPT_URL, url) != CURLE_OK)
+ return -EIO;
- if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK) {
- r = -EIO;
- goto fail;
- }
+ if (curl_easy_setopt(c, CURLOPT_PRIVATE, userdata) != CURLE_OK)
+ return -EIO;
useragent = strjoina(program_invocation_short_name, "/" PACKAGE_VERSION);
- if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK) {
- r = -EIO;
- goto fail;
- }
+ if (curl_easy_setopt(c, CURLOPT_USERAGENT, useragent) != CURLE_OK)
+ return -EIO;
- if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) {
- r = -EIO;
- goto fail;
- }
+ if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK)
+ return -EIO;
*ret = c;
return 0;
-
-fail:
- curl_easy_cleanup(c);
- return r;
}
int curl_glue_add(CurlGlue *g, CURL *c) {
int curl_parse_http_time(const char *t, usec_t *ret);
DEFINE_TRIVIAL_CLEANUP_FUNC(CURL*, curl_easy_cleanup);
+DEFINE_TRIVIAL_CLEANUP_FUNC(CURL*, curl_multi_cleanup);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct curl_slist*, curl_slist_free_all);
assert(ret);
- e = new0(RawExport, 1);
+ e = new(RawExport, 1);
if (!e)
return -ENOMEM;
- e->output_fd = e->input_fd = -1;
- e->on_finished = on_finished;
- e->userdata = userdata;
+ *e = (RawExport) {
+ .output_fd = -1,
+ .input_fd = -1,
+ .on_finished = on_finished,
+ .userdata = userdata,
+ .last_percent = (unsigned) -1,
+ };
RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
- e->last_percent = (unsigned) -1;
if (event)
e->event = sd_event_ref(event);
assert(ret);
- e = new0(TarExport, 1);
+ e = new(TarExport, 1);
if (!e)
return -ENOMEM;
- e->output_fd = e->tar_fd = -1;
- e->on_finished = on_finished;
- e->userdata = userdata;
- e->quota_referenced = (uint64_t) -1;
+ *e = (TarExport) {
+ .output_fd = -1,
+ .tar_fd = -1,
+ .on_finished = on_finished,
+ .userdata = userdata,
+ .quota_referenced = (uint64_t) -1,
+ .last_percent = (unsigned) -1,
+ };
RATELIMIT_INIT(e->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
- e->last_percent = (unsigned) -1;
if (event)
e->event = sd_event_ref(event);
void *userdata) {
_cleanup_(raw_import_unrefp) RawImport *i = NULL;
+ _cleanup_free_ char *root = NULL;
+ bool grow;
int r;
assert(ret);
- i = new0(RawImport, 1);
- if (!i)
+ root = strdup(image_root ?: "/var/lib/machines");
+ if (!root)
return -ENOMEM;
- i->input_fd = i->output_fd = -1;
- i->on_finished = on_finished;
- i->userdata = userdata;
-
- RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
- i->last_percent = (unsigned) -1;
+ grow = path_startswith(root, "/var/lib/machines");
- i->image_root = strdup(image_root ?: "/var/lib/machines");
- if (!i->image_root)
+ i = new(RawImport, 1);
+ if (!i)
return -ENOMEM;
- i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
+ *i = (RawImport) {
+ .input_fd = -1,
+ .output_fd = -1,
+ .on_finished = on_finished,
+ .userdata = userdata,
+ .last_percent = (unsigned) -1,
+ .image_root = TAKE_PTR(root),
+ .grow_machine_directory = grow,
+ };
+
+ RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
if (event)
i->event = sd_event_ref(event);
void *userdata) {
_cleanup_(tar_import_unrefp) TarImport *i = NULL;
+ _cleanup_free_ char *root = NULL;
+ bool grow;
int r;
assert(ret);
- i = new0(TarImport, 1);
- if (!i)
+ root = strdup(image_root ?: "/var/lib/machines");
+ if (!root)
return -ENOMEM;
- i->input_fd = i->tar_fd = -1;
- i->on_finished = on_finished;
- i->userdata = userdata;
-
- RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
- i->last_percent = (unsigned) -1;
+ grow = path_startswith(root, "/var/lib/machines");
- i->image_root = strdup(image_root ?: "/var/lib/machines");
- if (!i->image_root)
+ i = new(TarImport, 1);
+ if (!i)
return -ENOMEM;
- i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
+ *i = (TarImport) {
+ .input_fd = -1,
+ .tar_fd = -1,
+ .on_finished = on_finished,
+ .userdata = userdata,
+ .last_percent = (unsigned) -1,
+ .image_root = TAKE_PTR(root),
+ .grow_machine_directory = grow,
+ };
+
+ RATELIMIT_INIT(i->progress_rate_limit, 100 * USEC_PER_MSEC, 1);
if (event)
i->event = sd_event_ref(event);
if (r < 0)
return r;
- t = new0(Transfer, 1);
+ t = new(Transfer, 1);
if (!t)
return -ENOMEM;
- t->type = _TRANSFER_TYPE_INVALID;
- t->log_fd = -1;
- t->stdin_fd = -1;
- t->stdout_fd = -1;
- t->verify = _IMPORT_VERIFY_INVALID;
+ *t = (Transfer) {
+ .type = _TRANSFER_TYPE_INVALID,
+ .log_fd = -1,
+ .stdin_fd = -1,
+ .stdout_fd = -1,
+ .verify = _IMPORT_VERIFY_INVALID,
+ };
id = m->current_transfer_id + 1;
int pull_job_new(PullJob **ret, const char *url, CurlGlue *glue, void *userdata) {
_cleanup_(pull_job_unrefp) PullJob *j = NULL;
+ _cleanup_free_ char *u = NULL;
assert(url);
assert(glue);
assert(ret);
- j = new0(PullJob, 1);
- if (!j)
+ u = strdup(url);
+ if (u)
return -ENOMEM;
- j->state = PULL_JOB_INIT;
- j->disk_fd = -1;
- j->userdata = userdata;
- j->glue = glue;
- j->content_length = (uint64_t) -1;
- j->start_usec = now(CLOCK_MONOTONIC);
- j->compressed_max = j->uncompressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU; /* 64GB safety limit */
- j->style = VERIFICATION_STYLE_UNSET;
-
- j->url = strdup(url);
- if (!j->url)
+ j = new(PullJob, 1);
+ if (!j)
return -ENOMEM;
+ *j = (PullJob) {
+ .state = PULL_JOB_INIT,
+ .disk_fd = -1,
+ .userdata = userdata,
+ .glue = glue,
+ .content_length = (uint64_t) -1,
+ .start_usec = now(CLOCK_MONOTONIC),
+ .compressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU, /* 64GB safety limit */
+ .uncompressed_max = 64LLU * 1024LLU * 1024LLU * 1024LLU, /* 64GB safety limit */
+ .style = VERIFICATION_STYLE_UNSET,
+ .url = TAKE_PTR(u),
+ };
+
*ret = TAKE_PTR(j);
return 0;
RawPullFinished on_finished,
void *userdata) {
+ _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(raw_pull_unrefp) RawPull *i = NULL;
+ _cleanup_free_ char *root = NULL;
+ bool grow;
int r;
assert(ret);
- i = new0(RawPull, 1);
- if (!i)
- return -ENOMEM;
-
- i->on_finished = on_finished;
- i->userdata = userdata;
-
- i->image_root = strdup(image_root ?: "/var/lib/machines");
- if (!i->image_root)
+ root = strdup(image_root ?: "/var/lib/machines");
+ if (!root)
return -ENOMEM;
- i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
+ grow = path_startswith(root, "/var/lib/machines");
if (event)
- i->event = sd_event_ref(event);
+ e = sd_event_ref(event);
else {
- r = sd_event_default(&i->event);
+ r = sd_event_default(&e);
if (r < 0)
return r;
}
- r = curl_glue_new(&i->glue, i->event);
+ r = curl_glue_new(&g, e);
if (r < 0)
return r;
+ i = new(RawPull, 1);
+ if (!i)
+ return -ENOMEM;
+
+ *i = (RawPull) {
+ .on_finished = on_finished,
+ .userdata = userdata,
+ .image_root = TAKE_PTR(root),
+ .grow_machine_directory = grow,
+ .event = TAKE_PTR(e),
+ .glue = TAKE_PTR(g),
+ };
+
i->glue->on_finished = pull_job_curl_on_finished;
i->glue->userdata = i;
TarPullFinished on_finished,
void *userdata) {
+ _cleanup_(curl_glue_unrefp) CurlGlue *g = NULL;
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
_cleanup_(tar_pull_unrefp) TarPull *i = NULL;
+ _cleanup_free_ char *root = NULL;
+ bool grow;
int r;
assert(ret);
- i = new0(TarPull, 1);
- if (!i)
- return -ENOMEM;
-
- i->on_finished = on_finished;
- i->userdata = userdata;
-
- i->image_root = strdup(image_root ?: "/var/lib/machines");
- if (!i->image_root)
+ root = strdup(image_root ?: "/var/lib/machines");
+ if (!root)
return -ENOMEM;
- i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
+ grow = path_startswith(root, "/var/lib/machines");
if (event)
- i->event = sd_event_ref(event);
+ e = sd_event_ref(event);
else {
- r = sd_event_default(&i->event);
+ r = sd_event_default(&e);
if (r < 0)
return r;
}
- r = curl_glue_new(&i->glue, i->event);
+ r = curl_glue_new(&g, e);
if (r < 0)
return r;
+ i = new(TarPull, 1);
+ if (!i)
+ return -ENOMEM;
+
+ *i = (TarPull) {
+ .on_finished = on_finished,
+ .userdata = userdata,
+ .image_root = TAKE_PTR(root),
+ .grow_machine_directory = grow,
+ .event = TAKE_PTR(e),
+ .glue = TAKE_PTR(g),
+ };
+
i->glue->on_finished = pull_job_curl_on_finished;
i->glue->userdata = i;
if (arg_url) {
const char *url;
- char *hostname, *p;
+ char *hostname;
if (!strstr(arg_url, "/entries")) {
if (endswith(arg_url, "/"))
url = strjoina(arg_url, "entries");
else
url = strjoina(arg_url, "/entries");
- }
- else
+ } else
url = strdupa(arg_url);
log_info("Spawning curl %s...", url);
if (fd < 0)
return fd;
- hostname =
- startswith(arg_url, "https://") ?:
- startswith(arg_url, "http://") ?:
- arg_url;
+ hostname = STARTSWITH_SET(arg_url, "https://", "http://");
+ if (!hostname)
+ hostname = arg_url;
- hostname = strdupa(hostname);
- if ((p = strchr(hostname, '/')))
- *p = '\0';
- if ((p = strchr(hostname, ':')))
- *p = '\0';
+ hostname = strndupa(hostname, strcspn(hostname, "/:"));
r = journal_remote_add_source(s, fd, hostname, false);
if (r < 0)
#include "sigbus.h"
#include "signal-util.h"
#include "string-util.h"
+#include "strv.h"
#include "util.h"
#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
assert(u);
assert(url);
- memzero(u, sizeof(Uploader));
- u->input = -1;
+ *u = (Uploader) {
+ .input = -1
+ };
- if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) {
+ host = STARTSWITH_SET(url, "http://", "https://");
+ if (!host) {
host = url;
proto = "https://";
}
#include "selinux-util.h"
#include "socket-util.h"
#include "string-util.h"
+#include "strv.h"
#include "unaligned.h"
static bool allow_object_pid(const struct ucred *ucred) {
return;
}
- e = path_startswith(k, "/dev/shm/");
- if (!e)
- e = path_startswith(k, "/tmp/");
- if (!e)
- e = path_startswith(k, "/var/tmp/");
+ e = PATH_STARTSWITH_SET(k, "/dev/shm/", "/tmp/", "/var/tmp/");
if (!e) {
log_error("Received file outside of allowed directories. Refusing.");
return;
tilded = strjoina(full, "~");
atted = strjoina(prefix, "@");
- return streq(filename, full) ||
- streq(filename, tilded) ||
+ return STR_IN_SET(filename, full, tilded) ||
startswith(filename, atted);
}
return r;
FOREACH_DEVICE(e, d) {
+ const char *status, *enabled, *dash, *nn, *subsys;
sd_device *p;
- const char *status, *enabled, *dash, *nn, *i, *subsys;
- bool external = false;
if (sd_device_get_parent(d, &p) < 0)
continue;
continue;
dash++;
- FOREACH_STRING(i, "VGA-", "DVI-I-", "DVI-D-", "DVI-A-"
- "Composite-", "SVIDEO-", "Component-",
- "DIN-", "DP-", "HDMI-A-", "HDMI-B-", "TV-") {
-
- if (startswith(dash, i)) {
- external = true;
- break;
- }
- }
- if (!external)
+ if (!STARTSWITH_SET(dash,
+ "VGA-", "DVI-I-", "DVI-D-", "DVI-A-"
+ "Composite-", "SVIDEO-", "Component-",
+ "DIN-", "DP-", "HDMI-A-", "HDMI-B-", "TV-"))
continue;
/* Ignore ports that are not enabled */
}
/* Try to enable as many controllers as possible for the new payload. */
- (void) cg_enable_everywhere(supported, supported, cgroup);
+ (void) cg_enable_everywhere(supported, supported, cgroup, NULL);
return 0;
}
}
static const char *timezone_from_path(const char *path) {
- const char *z;
-
- z = path_startswith(path, "../usr/share/zoneinfo/");
- if (z)
- return z;
-
- z = path_startswith(path, "/usr/share/zoneinfo/");
- if (z)
- return z;
-
- return NULL;
+ return PATH_STARTSWITH_SET(
+ path,
+ "../usr/share/zoneinfo/",
+ "/usr/share/zoneinfo/");
}
static int setup_timezone(const char *dest) {
return log_oom();
with_timer = with_timer ||
- !!startswith(optarg, "OnActiveSec=") ||
- !!startswith(optarg, "OnBootSec=") ||
- !!startswith(optarg, "OnStartupSec=") ||
- !!startswith(optarg, "OnUnitActiveSec=") ||
- !!startswith(optarg, "OnUnitInactiveSec=") ||
- !!startswith(optarg, "OnCalendar=");
+ STARTSWITH_SET(optarg,
+ "OnActiveSec=",
+ "OnBootSec=",
+ "OnStartupSec=",
+ "OnUnitActiveSec=",
+ "OnUnitInactiveSec=",
+ "OnCalendar=");
break;
case ARG_PATH_PROPERTY:
STRV_FOREACH(entry, split) {
char *p;
- p = startswith(*entry, "default:");
+ p = STARTSWITH_SET(*entry, "default:", "d:");
if (!p)
- p = startswith(*entry, "d:");
+ p = *entry;
- if (p)
- r = strv_push(&d, p);
- else
- r = strv_push(&a, *entry);
+ r = strv_push(&d, p);
if (r < 0)
return r;
}
"BindsTo=%3$s\n"
"After=initrd-root-device.target local-fs-pre.target %3$s\n"
"Before=shutdown.target\n"
+ "Conflicts=shutdown.target\n"
"\n"
"[Service]\n"
"Type=oneshot\n"
#include <stdbool.h>
#include "string-util.h"
+#include "strv.h"
#include "utf8.h"
#include "web-util.h"
if (!endswith(etag, "\""))
return false;
- if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
+ if (!STARTSWITH_SET(etag, "\"", "W/\""))
return false;
return true;
if (isempty(url))
return false;
- p = startswith(url, "http://");
- if (!p)
- p = startswith(url, "https://");
+ p = STARTSWITH_SET(url, "http://", "https://");
if (!p)
return false;
if (http_url_is_valid(url))
return true;
- p = startswith(url, "file:/");
- if (!p)
- p = startswith(url, "info:");
- if (!p)
- p = startswith(url, "man:");
-
+ p = STARTSWITH_SET(url, "file:/", "info:", "man:");
if (isempty(p))
return false;
assert_se(!empty_or_root("//yy//"));
}
+static void test_path_startswith_set(void) {
+
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/bar", "/zzz"), ""));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/", "/zzz"), "bar"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo", "/zzz"), "bar"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/", "/zzz"), "foo/bar"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "", "/zzz"), NULL));
+
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/bar", "/zzz"), NULL));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/", "/zzz"), "bar2"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo", "/zzz"), "bar2"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/", "/zzz"), "foo/bar2"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "", "/zzz"), NULL));
+
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/bar", "/zzz"), NULL));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/", "/zzz"), NULL));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo", "/zzz"), NULL));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/", "/zzz"), "foo2/bar"));
+ assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "", "/zzz"), NULL));
+}
+
int main(int argc, char **argv) {
test_setup_logging(LOG_DEBUG);
test_hidden_or_backup_file();
test_skip_dev_prefix();
test_empty_or_root();
+ test_path_startswith_set();
test_systemd_installation_has_version(argv[1]); /* NULL is OK */
assert_se(!STRPTR_IN_SET(NULL, NULL));
}
+static void test_startswith_set(void) {
+ assert_se(!STARTSWITH_SET("foo", "bar", "baz", "waldo"));
+ assert_se(!STARTSWITH_SET("foo", "bar"));
+
+ assert_se(STARTSWITH_SET("abc", "a", "ab", "abc"));
+ assert_se(STARTSWITH_SET("abc", "ax", "ab", "abc"));
+ assert_se(STARTSWITH_SET("abc", "ax", "abx", "abc"));
+ assert_se(!STARTSWITH_SET("abc", "ax", "abx", "abcx"));
+
+ assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "foo", "zzz"), "bar"));
+ assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "", "zzz"), "foobar"));
+ assert_se(streq_ptr(STARTSWITH_SET("", "hhh", "kkk", "zzz", ""), ""));
+}
+
static const char* const input_table_multiple[] = {
"one",
"two",
test_specifier_printf();
test_str_in_set();
test_strptr_in_set();
+ test_startswith_set();
test_strv_foreach();
test_strv_foreach_backwards();
test_strv_foreach_pair();
/* If autonegotiation is disabled, the speed and duplex represent the fixed link
* mode and are writable if the driver supports multiple link modes. If it is
- * enabled then they are read-only. If the link is up they represent the negotiated
+ * enabled then they are read-only. If the link is up they represent the negotiated
* link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
* enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
*/
if (r < 0) {
if (config->port != _NET_DEV_PORT_INVALID)
- log_warning_errno(r, "Could not set port (%s) of %s: %m", port_to_string(config->port), old_name);
+ log_warning_errno(r, "Could not set port (%s) of %s: %m", port_to_string(config->port), old_name);
if (!eqzero(config->advertise))
log_warning_errno(r, "Could not set advertise mode: %m"); /* TODO: include modes in the log message. */
keycode++;
release[release_count] = scancode;
- if (release_count < ELEMENTSOF(release)-1)
+ if (release_count < ELEMENTSOF(release)-1)
release_count++;
if (keycode[0] == '\0')
int r;
uctrl = new0(struct udev_ctrl, 1);
- if (uctrl == NULL)
+ if (!uctrl)
return NULL;
uctrl->n_ref = 1;
if (fd < 0) {
uctrl->sock = socket(AF_LOCAL, SOCK_SEQPACKET|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
if (uctrl->sock < 0) {
- log_error_errno(errno, "error getting socket: %m");
+ log_error_errno(errno, "Failed to create socket: %m");
udev_ctrl_unref(uctrl);
return NULL;
}
*/
r = setsockopt_int(uctrl->sock, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
- log_warning_errno(r, "could not set SO_PASSCRED: %m");
+ log_warning_errno(r, "Failed to set SO_PASSCRED: %m");
uctrl->saddr.un = (struct sockaddr_un) {
.sun_family = AF_UNIX,
}
if (err < 0)
- return log_error_errno(errno, "bind failed: %m");
+ return log_error_errno(errno, "Failed to bind socket: %m");
err = listen(uctrl->sock, 0);
if (err < 0)
- return log_error_errno(errno, "listen failed: %m");
+ return log_error_errno(errno, "Failed to listen: %m");
uctrl->bound = true;
uctrl->cleanup_socket = true;
DEFINE_TRIVIAL_UNREF_FUNC(struct udev_ctrl, udev_ctrl, udev_ctrl_free);
int udev_ctrl_cleanup(struct udev_ctrl *uctrl) {
- if (uctrl == NULL)
+ if (!uctrl)
return 0;
if (uctrl->cleanup_socket)
sockaddr_un_unlink(&uctrl->saddr.un);
}
int udev_ctrl_get_fd(struct udev_ctrl *uctrl) {
- if (uctrl == NULL)
+ if (!uctrl)
return -EINVAL;
return uctrl->sock;
}
int r;
conn = new(struct udev_ctrl_connection, 1);
- if (conn == NULL)
+ if (!conn)
return NULL;
conn->n_ref = 1;
conn->uctrl = uctrl;
conn->sock = accept4(uctrl->sock, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK);
if (conn->sock < 0) {
if (errno != EINTR)
- log_error_errno(errno, "unable to receive ctrl connection: %m");
+ log_error_errno(errno, "Failed to receive ctrl connection: %m");
goto err;
}
/* check peer credential of connection */
r = getpeercred(conn->sock, &ucred);
if (r < 0) {
- log_error_errno(r, "unable to receive credentials of ctrl connection: %m");
+ log_error_errno(r, "Failed to receive credentials of ctrl connection: %m");
goto err;
}
if (ucred.uid > 0) {
- log_error("sender uid="UID_FMT", message ignored", ucred.uid);
+ log_error("Sender uid="UID_FMT", message ignored", ucred.uid);
goto err;
}
/* enable receiving of the sender credentials in the messages */
r = setsockopt_int(conn->sock, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
- log_warning_errno(r, "could not set SO_PASSCRED: %m");
+ log_warning_errno(r, "Failed to set SO_PASSCRED: %m");
udev_ctrl_ref(uctrl);
return conn;
ctrl_msg_wire.magic = UDEV_CTRL_MAGIC;
ctrl_msg_wire.type = type;
- if (buf != NULL)
+ if (buf)
strscpy(ctrl_msg_wire.buf, sizeof(ctrl_msg_wire.buf), buf);
else
ctrl_msg_wire.intval = intval;
pfd[0].fd = uctrl->sock;
pfd[0].events = POLLIN;
r = poll(pfd, 1, timeout * MSEC_PER_SEC);
- if (r < 0) {
+ if (r < 0) {
if (errno == EINTR)
continue;
err = -errno;
struct ucred *cred;
uctrl_msg = new0(struct udev_ctrl_msg, 1);
- if (uctrl_msg == NULL)
+ if (!uctrl_msg)
return NULL;
uctrl_msg->n_ref = 1;
uctrl_msg->conn = conn;
pfd[0].events = POLLIN;
r = poll(pfd, 1, 10000);
- if (r < 0) {
+ if (r < 0) {
if (errno == EINTR)
continue;
goto err;
} else if (r == 0) {
- log_error("timeout waiting for ctrl message");
+ log_error("Timeout waiting for ctrl message");
goto err;
} else {
if (!(pfd[0].revents & POLLIN)) {
- log_error_errno(errno, "ctrl connection error: %m");
+ log_error("Invalid ctrl connection: %m");
goto err;
}
}
iov.iov_len = sizeof(struct udev_ctrl_msg_wire);
size = recvmsg(conn->sock, &smsg, 0);
- if (size < 0) {
- log_error_errno(errno, "unable to receive ctrl message: %m");
+ if (size < 0) {
+ log_error_errno(errno, "Failed to receive ctrl message: %m");
goto err;
}
cmsg = CMSG_FIRSTHDR(&smsg);
- if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
- log_error("no sender credentials received, message ignored");
+ if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) {
+ log_error("No sender credentials received, ignoring message");
goto err;
}
cred = (struct ucred *) CMSG_DATA(cmsg);
if (cred->uid != 0) {
- log_error("sender uid="UID_FMT", message ignored", cred->uid);
+ log_error("Sender uid="UID_FMT", ignoring message", cred->uid);
goto err;
}
if (uctrl_msg->ctrl_msg_wire.magic != UDEV_CTRL_MAGIC) {
- log_error("message magic 0x%08x doesn't match, ignore it", uctrl_msg->ctrl_msg_wire.magic);
+ log_error("Message magic 0x%08x doesn't match, ignoring", uctrl_msg->ctrl_msg_wire.magic);
goto err;
}
#include "glob-util.h"
#include "libudev-util.h"
#include "mkdir.h"
+#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "stat-util.h"
union {
unsigned attr_off;
unsigned rule_goto;
- mode_t mode;
+ mode_t mode;
uid_t uid;
gid_t gid;
int devlink_prio;
[OP_REMOVE] = "remove",
[OP_ASSIGN] = "assign",
[OP_ASSIGN_FINAL] = "assign-final",
-} ;
+ };
return operation_strs[type];
}
return 0;
}
-static void log_unknown_owner(int error, const char *entity, const char *owner) {
+static void log_unknown_owner(sd_device *dev, int error, const char *entity, const char *owner) {
if (IN_SET(abs(error), ENOENT, ESRCH))
- log_error("Specified %s '%s' unknown", entity, owner);
+ log_device_error(dev, "Specified %s '%s' unknown", entity, owner);
else
- log_error_errno(error, "Failed to resolve %s '%s': %m", entity, owner);
+ log_device_error_errno(dev, error, "Failed to resolve %s '%s': %m", entity, owner);
}
static uid_t add_uid(struct udev_rules *rules, const char *owner) {
}
r = get_user_creds(&owner, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
if (r < 0)
- log_unknown_owner(r, "user", owner);
+ log_unknown_owner(NULL, r, "user", owner);
/* grow buffer if needed */
if (rules->uids_cur+1 >= rules->uids_max) {
}
r = get_group_creds(&group, &gid, USER_CREDS_ALLOW_MISSING);
if (r < 0)
- log_unknown_owner(r, "group", group);
+ log_unknown_owner(NULL, r, "group", group);
/* grow buffer if needed */
if (rules->gids_cur+1 >= rules->gids_max) {
event->program_result = mfree(event->program_result);
udev_event_apply_format(event, rules_str(rules, cur->key.value_off), program, sizeof(program), false);
- log_debug("PROGRAM '%s' %s:%u",
- program,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "PROGRAM '%s' %s:%u",
+ program,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
if (udev_event_spawn(event, timeout_usec, true, program, result, sizeof(result)) < 0) {
if (cur->key.op != OP_NOMATCH)
if (IN_SET(esc, ESCAPE_UNSET, ESCAPE_REPLACE)) {
count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT);
if (count > 0)
- log_debug("Replaced %i character(s)" , count);
+ log_device_debug(dev, "Replaced %i character(s) from result of '%s'" , count, program);
}
event->program_result = strdup(result);
if (cur->key.op == OP_NOMATCH)
char import[UTIL_PATH_SIZE];
udev_event_apply_format(event, rules_str(rules, cur->key.value_off), import, sizeof(import), false);
- log_debug("IMPORT '%s' %s:%u",
- import,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "IMPORT '%s' %s:%u",
+ import,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
if (import_program_into_properties(event, timeout_usec, import) != 0)
if (cur->key.op != OP_NOMATCH)
if (udev_builtin_run_once(cur->key.builtin_cmd)) {
/* check if we ran already */
if (event->builtin_run & (1 << cur->key.builtin_cmd)) {
- log_debug("IMPORT builtin skip '%s' %s:%u",
- udev_builtin_name(cur->key.builtin_cmd),
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "IMPORT builtin skip '%s' %s:%u",
+ udev_builtin_name(cur->key.builtin_cmd),
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
/* return the result from earlier run */
if (event->builtin_ret & (1 << cur->key.builtin_cmd))
if (cur->key.op != OP_NOMATCH)
}
udev_event_apply_format(event, rules_str(rules, cur->key.value_off), command, sizeof(command), false);
- log_debug("IMPORT builtin '%s' %s:%u",
- udev_builtin_name(cur->key.builtin_cmd),
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "IMPORT builtin '%s' %s:%u",
+ udev_builtin_name(cur->key.builtin_cmd),
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
r = udev_builtin_run(dev, cur->key.builtin_cmd, command, false);
if (r < 0) {
/* remember failure */
- log_debug_errno(r, "IMPORT builtin '%s' fails: %m",
- udev_builtin_name(cur->key.builtin_cmd));
+ log_device_debug_errno(dev, r, "IMPORT builtin '%s' fails: %m",
+ udev_builtin_name(cur->key.builtin_cmd));
event->builtin_ret |= (1 << cur->key.builtin_cmd);
if (cur->key.op != OP_NOMATCH)
goto nomatch;
key = rules_str(rules, cur->key.value_off);
r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &value);
if (r < 0)
- log_debug_errno(r, "Failed to read %s from /proc/cmdline, ignoring: %m", key);
+ log_device_debug_errno(dev, r, "Failed to read %s from /proc/cmdline, ignoring: %m", key);
else if (r > 0) {
imported = true;
event->owner_set = true;
r = get_user_creds(&ow, &event->uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
if (r < 0) {
- log_unknown_owner(r, "user", owner);
+ log_unknown_owner(dev, r, "user", owner);
event->uid = 0;
}
- log_debug("OWNER %u %s:%u",
- event->uid,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "OWNER %u %s:%u",
+ event->uid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
case TK_A_GROUP: {
event->group_set = true;
r = get_group_creds(&gr, &event->gid, USER_CREDS_ALLOW_MISSING);
if (r < 0) {
- log_unknown_owner(r, "group", group);
+ log_unknown_owner(dev, r, "group", group);
event->gid = 0;
}
- log_debug("GROUP %u %s:%u",
- event->gid,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "GROUP %u %s:%u",
+ event->gid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
case TK_A_MODE: {
- char mode_str[UTIL_NAME_SIZE], *endptr;
+ char mode_str[UTIL_NAME_SIZE];
mode_t mode;
if (event->mode_final)
break;
udev_event_apply_format(event, rules_str(rules, cur->key.value_off), mode_str, sizeof(mode_str), false);
- mode = strtol(mode_str, &endptr, 8);
- if (endptr[0] != '\0') {
- log_error("ignoring invalid mode '%s'", mode_str);
+ r = parse_mode(mode_str, &mode);
+ if (r < 0) {
+ log_device_error_errno(dev, r, "Failed to parse mode '%s': %m", mode_str);
break;
}
if (cur->key.op == OP_ASSIGN_FINAL)
event->mode_final = true;
event->mode_set = true;
event->mode = mode;
- log_debug("MODE %#o %s:%u",
- event->mode,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "MODE %#o %s:%u",
+ event->mode,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
case TK_A_OWNER_ID:
event->owner_final = true;
event->owner_set = true;
event->uid = cur->key.uid;
- log_debug("OWNER %u %s:%u",
- event->uid,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "OWNER %u %s:%u",
+ event->uid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
case TK_A_GROUP_ID:
if (event->group_final)
event->group_final = true;
event->group_set = true;
event->gid = cur->key.gid;
- log_debug("GROUP %u %s:%u",
- event->gid,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "GROUP %u %s:%u",
+ event->gid,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
case TK_A_MODE_ID:
if (event->mode_final)
event->mode_final = true;
event->mode_set = true;
event->mode = cur->key.mode;
- log_debug("MODE %#o %s:%u",
- event->mode,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "MODE %#o %s:%u",
+ event->mode,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
case TK_A_SECLABEL: {
_cleanup_free_ char *name = NULL, *label = NULL;
name = label = NULL;
- log_debug("SECLABEL{%s}='%s' %s:%u",
- name, label,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "SECLABEL{%s}='%s' %s:%u",
+ name, label,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
case TK_A_ENV: {
(*p >= '0' && *p <= '9') ||
IN_SET(*p, '-', '_'))
continue;
- log_error("Ignoring invalid tag name '%s'", tag);
+ log_device_error(dev, "Ignoring invalid tag name '%s'", tag);
break;
}
if (cur->key.op == OP_REMOVE)
if (IN_SET(esc, ESCAPE_UNSET, ESCAPE_REPLACE)) {
count = util_replace_chars(name_str, "/");
if (count > 0)
- log_debug("Replaced %i character(s)", count);
+ log_device_debug(dev, "Replaced %i character(s) from result of NAME=\"%s\"", count, name);
}
if (sd_device_get_devnum(dev, NULL) >= 0 &&
(sd_device_get_devname(dev, &val) < 0 ||
!streq(name_str, val + STRLEN("/dev/")))) {
- log_error("NAME=\"%s\" ignored, kernel device nodes cannot be renamed; please fix it in %s:%u\n",
- name,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_error(dev, "Kernel device nodes cannot be renamed, ignoring NAME=\"%s\"; please fix it in %s:%u\n",
+ name,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
if (free_and_strdup(&event->name, name_str) < 0)
return log_oom();
- log_debug("NAME '%s' %s:%u",
- event->name,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "NAME '%s' %s:%u",
+ event->name,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
case TK_A_DEVLINK: {
else if (esc == ESCAPE_REPLACE)
count = util_replace_chars(temp, "/");
if (count > 0)
- log_debug("Replaced %i character(s)" , count);
+ log_device_debug(dev, "Replaced %i character(s) from result of LINK" , count);
pos = temp;
while (isspace(pos[0]))
pos++;
next = strchr(pos, ' ');
while (next) {
next[0] = '\0';
- log_debug("LINK '%s' %s:%u", pos,
- rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ log_device_debug(dev, "LINK '%s' %s:%u", pos,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
strscpyl(filename, sizeof(filename), "/dev/", pos, NULL);
device_add_devlink(dev, filename);
while (isspace(next[1]))
next = strchr(pos, ' ');
}
if (pos[0] != '\0') {
- log_debug("LINK '%s' %s:%u", pos,
- rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ log_device_debug(dev, "LINK '%s' %s:%u", pos,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
strscpyl(filename, sizeof(filename), "/dev/", pos, NULL);
device_add_devlink(dev, filename);
}
attr_subst_subdir(attr, sizeof(attr));
udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value), false);
- log_debug("ATTR '%s' writing '%s' %s:%u", attr, value,
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "ATTR '%s' writing '%s' %s:%u", attr, value,
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
f = fopen(attr, "we");
if (!f)
- log_error_errno(errno, "Failed to open ATTR{%s} for writing: %m", attr);
+ log_device_error_errno(dev, errno, "Failed to open ATTR{%s} for writing: %m", attr);
else if (fprintf(f, "%s", value) <= 0)
- log_error_errno(errno, "Failed to write ATTR{%s}: %m", attr);
+ log_device_error_errno(dev, errno, "Failed to write ATTR{%s}: %m", attr);
break;
}
case TK_A_SYSCTL: {
udev_event_apply_format(event, rules_str(rules, cur->key.attr_off), filename, sizeof(filename), false);
sysctl_normalize(filename);
udev_event_apply_format(event, rules_str(rules, cur->key.value_off), value, sizeof(value), false);
- log_debug("SYSCTL '%s' writing '%s' %s:%u", filename, value,
- rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
+ log_device_debug(dev, "SYSCTL '%s' writing '%s' %s:%u", filename, value,
+ rules_str(rules, rule->rule.filename_off), rule->rule.filename_line);
r = sysctl_write(filename, value);
if (r < 0)
- log_error_errno(r, "Failed to write SYSCTL{%s}='%s': %m", filename, value);
+ log_device_error_errno(dev, r, "Failed to write SYSCTL{%s}='%s': %m", filename, value);
break;
}
case TK_A_RUN_BUILTIN:
cmd = NULL;
- log_debug("RUN '%s' %s:%u",
- rules_str(rules, cur->key.value_off),
- rules_str(rules, rule->rule.filename_off),
- rule->rule.filename_line);
+ log_device_debug(dev, "RUN '%s' %s:%u",
+ rules_str(rules, cur->key.value_off),
+ rules_str(rules, rule->rule.filename_off),
+ rule->rule.filename_line);
break;
}
case TK_A_GOTO:
case TK_M_PARENTS_MAX:
case TK_M_MAX:
case TK_UNSET:
- log_error("Wrong type %u", cur->type);
+ log_device_error(dev, "Wrong type %u", cur->type);
goto nomatch;
}
#include "signal-util.h"
#include "socket-util.h"
#include "string-util.h"
+#include "strv.h"
#include "strxcpyx.h"
#include "syslog-util.h"
#include "udev-builtin.h"
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get sysname: %m");
- if (startswith(val, "dm-") ||
- startswith(val, "md") ||
- startswith(val, "drbd"))
+ if (STARTSWITH_SET(val, "dm-", "md", "drbd"))
return 0;
r = sd_device_get_devtype(dev, &val);
-w /sys/fs/cgroup/system.slice/test0.service/cgroup.subtree_control
systemd-run --wait --unit=test1.service -p "DynamicUser=1" -p "Delegate=memory pids" \
- grep memory /sys/fs/cgroup/system.slice/test1.service/cgroup.controllers
+ grep -q memory /sys/fs/cgroup/system.slice/test1.service/cgroup.controllers
systemd-run --wait --unit=test2.service -p "DynamicUser=1" -p "Delegate=memory pids" \
- grep pids /sys/fs/cgroup/system.slice/test2.service/cgroup.controllers
+ grep -q pids /sys/fs/cgroup/system.slice/test2.service/cgroup.controllers
+
+ # "io" is not among the controllers enabled by default for all units, verify that
+ grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
+
+ # Run a service with "io" enabled, and verify it works
+ systemd-run --wait --unit=test3.service -p "IOAccounting=yes" -p "Slice=system-foo-bar-baz.slice" \
+ grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test3.service/cgroup.controllers
+
+ # We want to check if "io" is removed again from the controllers
+ # list. However, PID 1 (rightfully) does this asynchronously. In order
+ # to force synchronization on this, let's start a short-lived service
+ # which requires PID 1 to refresh the cgroup tree, so that we can
+ # verify that this all works.
+ systemd-run --wait --unit=test4.service true
+
+ # And now check again, "io" should have vanished
+ grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers
else
echo "Skipping TEST-19-DELEGATE, as the kernel doesn't actually support cgroupsv2" >&2
fi
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+# run a command on remove events
+ACTION=="remove", ENV{REMOVE_CMD}!="", RUN+="$env{REMOVE_CMD}"
+ACTION=="remove", GOTO="default_end"
+
+SUBSYSTEM=="virtio-ports", KERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name}"
+
+# select "system RTC" or just use the first one
+SUBSYSTEM=="rtc", ATTR{hctosys}=="1", SYMLINK+="rtc"
+SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100"
+
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
+ENV{MODALIAS}!="", IMPORT{builtin}="hwdb --subsystem=$env{SUBSYSTEM}"
+
+ACTION!="add", GOTO="default_end"
+
+SUBSYSTEM=="tty", KERNEL=="ptmx", GROUP="tty", MODE="0666"
+SUBSYSTEM=="tty", KERNEL=="tty", GROUP="tty", MODE="0666"
+SUBSYSTEM=="tty", KERNEL=="tty[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="tty", KERNEL=="sclp_line[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="tty", KERNEL=="ttysclp[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="tty", KERNEL=="3270/tty[0-9]*", GROUP="tty", MODE="0620"
+SUBSYSTEM=="vc", KERNEL=="vcs*|vcsa*", GROUP="tty"
+KERNEL=="tty[A-Z]*[0-9]|ttymxc[0-9]*|pppox[0-9]*|ircomm[0-9]*|noz[0-9]*|rfcomm[0-9]*", GROUP="dialout"
+
+SUBSYSTEM=="mem", KERNEL=="mem|kmem|port", GROUP="kmem", MODE="0640"
+
+SUBSYSTEM=="input", GROUP="input"
+SUBSYSTEM=="input", KERNEL=="js[0-9]*", MODE="0664"
+
+SUBSYSTEM=="video4linux", GROUP="video"
+SUBSYSTEM=="graphics", GROUP="video"
+SUBSYSTEM=="drm", KERNEL!="renderD*", GROUP="video"
+SUBSYSTEM=="dvb", GROUP="video"
+SUBSYSTEM=="media", GROUP="video"
+SUBSYSTEM=="cec", GROUP="video"
+
+SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="render", MODE="0666"
+SUBSYSTEM=="kfd", GROUP="render", MODE="0666"
+
+SUBSYSTEM=="sound", GROUP="audio", \
+ OPTIONS+="static_node=snd/seq", OPTIONS+="static_node=snd/timer"
+
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", MODE="0664"
+
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x00010*", GROUP="video"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00b09d:0x00010*", GROUP="video"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x010001*", GROUP="video"
+SUBSYSTEM=="firewire", ATTR{units}=="*0x00a02d:0x014001*", GROUP="video"
+
+KERNEL=="parport[0-9]*", GROUP="lp"
+SUBSYSTEM=="printer", KERNEL=="lp*", GROUP="lp"
+SUBSYSTEM=="ppdev", GROUP="lp"
+KERNEL=="lp[0-9]*", GROUP="lp"
+KERNEL=="irlpt[0-9]*", GROUP="lp"
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", GROUP="lp"
+
+SUBSYSTEM=="block", GROUP="disk"
+SUBSYSTEM=="block", KERNEL=="sr[0-9]*", GROUP="cdrom"
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="4|5", GROUP="cdrom"
+KERNEL=="sch[0-9]*", GROUP="cdrom"
+KERNEL=="pktcdvd[0-9]*", GROUP="cdrom"
+KERNEL=="pktcdvd", GROUP="cdrom"
+
+SUBSYSTEM=="scsi_generic|scsi_tape", SUBSYSTEMS=="scsi", ATTRS{type}=="1|8", GROUP="tape"
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="0", GROUP="disk"
+KERNEL=="qft[0-9]*|nqft[0-9]*|zqft[0-9]*|nzqft[0-9]*|rawqft[0-9]*|nrawqft[0-9]*", GROUP="disk"
+KERNEL=="loop-control", GROUP="disk", OPTIONS+="static_node=loop-control"
+KERNEL=="btrfs-control", GROUP="disk"
+KERNEL=="rawctl", GROUP="disk"
+SUBSYSTEM=="raw", KERNEL=="raw[0-9]*", GROUP="disk"
+SUBSYSTEM=="aoe", GROUP="disk", MODE="0220"
+SUBSYSTEM=="aoe", KERNEL=="err", MODE="0440"
+
+KERNEL=="rfkill", MODE="0664"
+KERNEL=="tun", MODE="0666", OPTIONS+="static_node=net/tun"
+
+KERNEL=="fuse", MODE="0666", OPTIONS+="static_node=fuse"
+
+# The static_node is required on s390x and ppc (they are using MODULE_ALIAS)
+KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"
+
+SUBSYSTEM=="ptp", ATTR{clock_name}=="KVM virtual PTP", SYMLINK += "ptp_kvm"
+
+LABEL="default_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+# enable in-kernel media-presence polling
+ACTION=="add", SUBSYSTEM=="module", KERNEL=="block", ATTR{parameters/events_dfl_poll_msecs}=="0", \
+ ATTR{parameters/events_dfl_poll_msecs}="2000"
+
+# forward scsi device event to corresponding block device
+ACTION=="change", SUBSYSTEM=="scsi", ENV{DEVTYPE}=="scsi_device", TEST=="block", ATTR{block/*/uevent}="change"
+
+# watch metadata changes, caused by tools closing the device node which was opened for writing
+ACTION!="remove", SUBSYSTEM=="block", KERNEL=="loop*|nvme*|sd*|vd*|xvd*|pmem*|mmcblk*", OPTIONS+="watch"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="cdrom_end"
+SUBSYSTEM!="block", GOTO="cdrom_end"
+KERNEL!="sr[0-9]*|vdisk*|xvd*", GOTO="cdrom_end"
+ENV{DEVTYPE}!="disk", GOTO="cdrom_end"
+
+# unconditionally tag device as CDROM
+KERNEL=="sr[0-9]*", ENV{ID_CDROM}="1"
+
+# stop automatically any mount units bound to the device if the media eject
+# button is pressed.
+ENV{ID_CDROM}=="1", ENV{SYSTEMD_MOUNT_DEVICE_BOUND}="1"
+
+# media eject button pressed
+ENV{DISK_EJECT_REQUEST}=="?*", RUN+="cdrom_id --eject-media $devnode", GOTO="cdrom_end"
+
+# import device and media properties and lock tray to
+# enable the receiving of media eject button events
+IMPORT{program}="cdrom_id --lock-media $devnode"
+
+# ejecting a CD does not remove the device node, so mark the systemd device
+# unit as inactive while there is no medium; this automatically cleans up of
+# stale mounts after ejecting
+ENV{DISK_MEDIA_CHANGE}=="?*", ENV{ID_CDROM_MEDIA}!="?*", ENV{SYSTEMD_READY}="0"
+
+KERNEL=="sr0", SYMLINK+="cdrom", OPTIONS+="link_priority=-100"
+
+LABEL="cdrom_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION!="remove", SUBSYSTEM=="drm", SUBSYSTEMS=="pci|usb|platform", IMPORT{builtin}="path_id"
+
+# by-path
+ENV{ID_PATH}=="?*", KERNEL=="card*", SYMLINK+="dri/by-path/$env{ID_PATH}-card"
+ENV{ID_PATH}=="?*", KERNEL=="controlD*", SYMLINK+="dri/by-path/$env{ID_PATH}-control"
+ENV{ID_PATH}=="?*", KERNEL=="renderD*", SYMLINK+="dri/by-path/$env{ID_PATH}-render"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="evdev_end"
+KERNEL!="event*", GOTO="evdev_end"
+
+# skip later rules when we find something for this input device
+IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=evdev:", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+# AT keyboard matching by the machine's DMI data
+DRIVERS=="atkbd", \
+ IMPORT{builtin}="hwdb 'evdev:atkbd:$attr{[dmi/id]modalias}'", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+# device matching the input device name + properties + the machine's DMI data
+KERNELS=="input*", IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:phys:$attr{phys}:ev:$attr{capabilities/ev}:$attr{[dmi/id]modalias}'", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+# device matching the input device name and the machine's DMI data
+KERNELS=="input*", IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'", \
+ RUN{builtin}+="keyboard", GOTO="evdev_end"
+
+LABEL="evdev_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="id_input_end"
+
+SUBSYSTEM=="input", ENV{ID_INPUT}=="", IMPORT{builtin}="input_id"
+SUBSYSTEM=="input", IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=id-input:modalias:"
+
+LABEL="id_input_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="persistent_alsa_end"
+SUBSYSTEM!="sound", GOTO="persistent_alsa_end"
+KERNEL!="controlC[0-9]*", GOTO="persistent_alsa_end"
+
+SUBSYSTEMS=="usb", ENV{ID_MODEL}=="", IMPORT{builtin}="usb_id"
+ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", SYMLINK+="snd/by-id/$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}"
+ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", SYMLINK+="snd/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
+
+IMPORT{builtin}="path_id"
+ENV{ID_PATH}=="?*", SYMLINK+="snd/by-path/$env{ID_PATH}"
+
+LABEL="persistent_alsa_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="persistent_input_end"
+SUBSYSTEM!="input", GOTO="persistent_input_end"
+SUBSYSTEMS=="bluetooth", ENV{ID_BUS}="bluetooth", GOTO="persistent_input_end"
+# Bluetooth devices don't always have the bluetooth subsystem
+ATTRS{id/bustype}=="0005", ENV{ID_BUS}="bluetooth", GOTO="persistent_input_end"
+SUBSYSTEMS=="rmi4", ENV{ID_BUS}="rmi"
+SUBSYSTEMS=="serio", ENV{ID_BUS}="i8042"
+
+SUBSYSTEMS=="usb", ENV{ID_BUS}=="", IMPORT{builtin}="usb_id"
+
+# determine class name for persistent symlinks
+ENV{ID_INPUT_KEYBOARD}=="?*", ENV{.INPUT_CLASS}="kbd"
+ENV{ID_INPUT_MOUSE}=="?*", ENV{.INPUT_CLASS}="mouse"
+ENV{ID_INPUT_TOUCHPAD}=="?*", ENV{.INPUT_CLASS}="mouse"
+ENV{ID_INPUT_TABLET}=="?*", ENV{.INPUT_CLASS}="mouse"
+ENV{ID_INPUT_JOYSTICK}=="?*", ENV{.INPUT_CLASS}="joystick"
+DRIVERS=="pcspkr", ENV{.INPUT_CLASS}="spkr"
+ATTRS{name}=="*dvb*|*DVB*|* IR *", ENV{.INPUT_CLASS}="ir"
+
+# fill empty serial number
+ENV{.INPUT_CLASS}=="?*", ENV{ID_SERIAL}=="", ENV{ID_SERIAL}="noserial"
+
+# by-id links
+KERNEL=="mouse*|js*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="|00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-$env{.INPUT_CLASS}"
+KERNEL=="mouse*|js*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="?*", ATTRS{bInterfaceNumber}!="00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$attr{bInterfaceNumber}-$env{.INPUT_CLASS}"
+KERNEL=="event*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="|00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-$env{.INPUT_CLASS}"
+KERNEL=="event*", ENV{ID_BUS}=="?*", ENV{.INPUT_CLASS}=="?*", ATTRS{bInterfaceNumber}=="?*", ATTRS{bInterfaceNumber}!="00", SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$attr{bInterfaceNumber}-event-$env{.INPUT_CLASS}"
+# allow empty class for USB devices, by appending the interface number
+SUBSYSTEMS=="usb", ENV{ID_BUS}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", ATTRS{bInterfaceNumber}=="?*", \
+ SYMLINK+="input/by-id/$env{ID_BUS}-$env{ID_SERIAL}-event-if$attr{bInterfaceNumber}"
+
+# by-path
+SUBSYSTEMS=="pci|usb|platform|acpi", IMPORT{builtin}="path_id"
+ENV{ID_PATH}=="?*", KERNEL=="mouse*|js*", ENV{.INPUT_CLASS}=="?*", SYMLINK+="input/by-path/$env{ID_PATH}-$env{.INPUT_CLASS}"
+ENV{ID_PATH}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="?*", SYMLINK+="input/by-path/$env{ID_PATH}-event-$env{.INPUT_CLASS}"
+# allow empty class for platform and usb devices; platform supports only a single interface that way
+SUBSYSTEMS=="usb|platform", ENV{ID_PATH}=="?*", KERNEL=="event*", ENV{.INPUT_CLASS}=="", \
+ SYMLINK+="input/by-path/$env{ID_PATH}-event"
+
+LABEL="persistent_input_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/tape/{by-id,by-path}
+
+ACTION=="remove", GOTO="persistent_storage_tape_end"
+ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_tape_end"
+
+# type 8 devices are "Medium Changers"
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{program}="scsi_id --sg-version=3 --export --whitelisted -d $devnode", \
+ SYMLINK+="tape/by-id/scsi-$env{ID_SERIAL}"
+
+# iSCSI devices from the same host have all the same ID_SERIAL,
+# but additionally a property named ID_SCSI_SERIAL.
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", ENV{ID_SCSI_SERIAL}=="?*", \
+ SYMLINK+="tape/by-id/scsi-$env{ID_SCSI_SERIAL}"
+
+SUBSYSTEM=="scsi_generic", SUBSYSTEMS=="scsi", ATTRS{type}=="8", IMPORT{builtin}="path_id", \
+ SYMLINK+="tape/by-path/$env{ID_PATH}-changer"
+
+SUBSYSTEM!="scsi_tape", GOTO="persistent_storage_tape_end"
+
+KERNEL=="st*[0-9]|nst*[0-9]", ATTRS{ieee1394_id}=="?*", ENV{ID_SERIAL}="$attr{ieee1394_id}", ENV{ID_BUS}="ieee1394"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", KERNELS=="[0-9]*:*[0-9]", ENV{.BSG_DEV}="$root/bsg/$id"
+KERNEL=="st*[0-9]|nst*[0-9]", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --whitelisted --export --device=$env{.BSG_DEV}", ENV{ID_BUS}="scsi"
+KERNEL=="st*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
+KERNEL=="st*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}"
+KERNEL=="nst*[0-9]", ENV{ID_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SERIAL}-nst"
+KERNEL=="nst*[0-9]", ENV{ID_SCSI_SERIAL}=="?*", SYMLINK+="tape/by-id/$env{ID_BUS}-$env{ID_SCSI_SERIAL}-nst"
+
+# by-path (parent device path)
+KERNEL=="st*[0-9]|nst*[0-9]", IMPORT{builtin}="path_id"
+KERNEL=="st*[0-9]", ENV{ID_PATH}=="?*", SYMLINK+="tape/by-path/$env{ID_PATH}"
+KERNEL=="nst*[0-9]", ENV{ID_PATH}=="?*", SYMLINK+="tape/by-path/$env{ID_PATH}-nst"
+
+LABEL="persistent_storage_tape_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+# persistent storage links: /dev/disk/{by-id,by-uuid,by-label,by-path}
+# scheme based on "Linux persistent device names", 2004, Hannes Reinecke <hare@suse.de>
+
+ACTION=="remove", GOTO="persistent_storage_end"
+ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}=="1", GOTO="persistent_storage_end"
+
+SUBSYSTEM!="block", GOTO="persistent_storage_end"
+KERNEL!="loop*|mmcblk*[0-9]|msblk*[0-9]|mspblk*[0-9]|nvme*|sd*|sr*|vd*|xvd*|bcache*|cciss*|dasd*|ubd*|scm*|pmem*|nbd*|zd*", GOTO="persistent_storage_end"
+
+# ignore partitions that span the entire disk
+TEST=="whole_disk", GOTO="persistent_storage_end"
+
+# for partitions import parent information
+ENV{DEVTYPE}=="partition", IMPORT{parent}="ID_*"
+
+# NVMe
+KERNEL=="nvme*[0-9]n*[0-9]", ATTR{wwid}=="?*", SYMLINK+="disk/by-id/nvme-$attr{wwid}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{wwid}=="?*", SYMLINK+="disk/by-id/nvme-$attr{wwid}-part%n"
+
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{wwid}=="?*", ENV{ID_WWN}="$attr{wwid}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
+KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+ ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
+
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{model}=="?*", ENV{ID_MODEL}="$attr{model}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{firmware_rev}=="?*", ENV{ID_REVISION}="$attr{firmware_rev}"
+KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
+ ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
+
+# virtio-blk
+KERNEL=="vd*[!0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}"
+KERNEL=="vd*[0-9]", ATTRS{serial}=="?*", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/virtio-$env{ID_SERIAL}-part%n"
+
+# ATA
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", IMPORT{program}="ata_id --export $devnode"
+
+# ATAPI devices (SPC-3 or later)
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="scsi", ATTRS{type}=="5", ATTRS{scsi_level}=="[6-9]*", IMPORT{program}="ata_id --export $devnode"
+
+# Run ata_id on non-removable USB Mass Storage (SATA/PATA disks in enclosures)
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", ATTR{removable}=="0", SUBSYSTEMS=="usb", IMPORT{program}="ata_id --export $devnode"
+
+# Fall back usb_id for USB devices
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
+
+# SCSI devices
+KERNEL=="sd*[!0-9]|sr*", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="scsi"
+KERNEL=="cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}!="?*", IMPORT{program}="scsi_id --export --whitelisted -d $devnode", ENV{ID_BUS}="cciss"
+KERNEL=="sd*|sr*|cciss*", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"
+KERNEL=="sd*|cciss*", ENV{DEVTYPE}=="partition", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}-part%n"
+
+# PMEM devices
+KERNEL=="pmem*", ENV{DEVTYPE}=="disk", ATTRS{uuid}=="?*", SYMLINK+="disk/by-id/pmem-$attr{uuid}"
+
+# FireWire
+KERNEL=="sd*[!0-9]|sr*", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}"
+KERNEL=="sd*[0-9]", ATTRS{ieee1394_id}=="?*", SYMLINK+="disk/by-id/ieee1394-$attr{ieee1394_id}-part%n"
+
+# MMC
+KERNEL=="mmcblk[0-9]", SUBSYSTEMS=="mmc", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
+ ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}"
+KERNEL=="mmcblk[0-9]p[0-9]*", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/mmc-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
+
+# UBI-MTD
+SUBSYSTEM=="ubi", KERNEL=="ubi*_*", ATTRS{mtd_num}=="*", SYMLINK+="ubi_mtd%s{mtd_num}_%s{name}"
+
+# Memstick
+KERNEL=="msblk[0-9]|mspblk[0-9]", SUBSYSTEMS=="memstick", ATTRS{name}=="?*", ATTRS{serial}=="?*", \
+ ENV{ID_NAME}="$attr{name}", ENV{ID_SERIAL}="$attr{serial}", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}"
+KERNEL=="msblk[0-9]p[0-9]|mspblk[0-9]p[0-9]", ENV{ID_NAME}=="?*", ENV{ID_SERIAL}=="?*", SYMLINK+="disk/by-id/memstick-$env{ID_NAME}_$env{ID_SERIAL}-part%n"
+
+# by-path
+ENV{DEVTYPE}=="disk", DEVPATH!="*/virtual/*", IMPORT{builtin}="path_id"
+KERNEL=="mmcblk[0-9]boot[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-boot%n"
+KERNEL!="mmcblk[0-9]boot[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"
+ENV{DEVTYPE}=="partition", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part%n"
+
+# legacy virtio-pci by-path links (deprecated)
+KERNEL=="vd*[!0-9]", ENV{ID_PATH}=="pci-*", SYMLINK+="disk/by-path/virtio-$env{ID_PATH}"
+KERNEL=="vd*[0-9]", ENV{ID_PATH}=="pci-*", SYMLINK+="disk/by-path/virtio-$env{ID_PATH}-part%n"
+
+# probe filesystem metadata of optical drives which have a media inserted
+KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="?*", \
+ IMPORT{builtin}="blkid --offset=$env{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}"
+# single-session CDs do not have ID_CDROM_MEDIA_SESSION_LAST_OFFSET
+KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DATA}=="?*", ENV{ID_CDROM_MEDIA_SESSION_LAST_OFFSET}=="", \
+ IMPORT{builtin}="blkid --noraid"
+
+# probe filesystem metadata of disks
+KERNEL!="sr*", IMPORT{builtin}="blkid"
+
+# by-label/by-uuid links (filesystem metadata)
+ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}"
+ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_LABEL_ENC}=="?*", SYMLINK+="disk/by-label/$env{ID_FS_LABEL_ENC}"
+
+# by-id (World Wide Name)
+ENV{DEVTYPE}=="disk", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}"
+ENV{DEVTYPE}=="partition", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-id/wwn-$env{ID_WWN_WITH_EXTENSION}-part%n"
+
+# by-partlabel/by-partuuid links (partition metadata)
+ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}"
+ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}"
+
+LABEL="persistent_storage_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="persistent_v4l_end"
+SUBSYSTEM!="video4linux", GOTO="persistent_v4l_end"
+ENV{MAJOR}=="", GOTO="persistent_v4l_end"
+
+IMPORT{program}="v4l_id $devnode"
+
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
+KERNEL=="video*", ENV{ID_SERIAL}=="?*", SYMLINK+="v4l/by-id/$env{ID_BUS}-$env{ID_SERIAL}-video-index$attr{index}"
+
+# check for valid "index" number
+TEST!="index", GOTO="persistent_v4l_end"
+ATTR{index}!="?*", GOTO="persistent_v4l_end"
+
+IMPORT{builtin}="path_id"
+ENV{ID_PATH}=="?*", KERNEL=="video*|vbi*", SYMLINK+="v4l/by-path/$env{ID_PATH}-video-index$attr{index}"
+ENV{ID_PATH}=="?*", KERNEL=="audio*", SYMLINK+="v4l/by-path/$env{ID_PATH}-audio-index$attr{index}"
+
+LABEL="persistent_v4l_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="sensor_end"
+
+# device matching the sensor's name and the machine's DMI data for IIO devices
+SUBSYSTEM=="iio", KERNEL=="iio*", SUBSYSTEMS=="usb|i2c", \
+ IMPORT{builtin}="hwdb 'sensor:modalias:$attr{modalias}:$attr{[dmi/id]modalias}'", \
+ GOTO="sensor_end"
+
+SUBSYSTEM=="input", ENV{ID_INPUT_ACCELEROMETER}=="1", SUBSYSTEMS=="acpi", \
+ IMPORT{builtin}="hwdb 'sensor:modalias:acpi:$attr{hid}:$attr{[dmi/id]modalias}'", \
+ GOTO="sensor_end"
+
+SUBSYSTEM=="input", ENV{ID_INPUT_ACCELEROMETER}=="1", SUBSYSTEMS=="platform", \
+ IMPORT{builtin}="hwdb 'sensor:modalias:platform:$id:$attr{[dmi/id]modalias}'", \
+ GOTO="sensor_end"
+
+LABEL="sensor_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="serial_end"
+SUBSYSTEM!="tty", GOTO="serial_end"
+
+SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
+SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
+
+# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
+KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"
+
+SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"
+
+IMPORT{builtin}="path_id"
+ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="serial/by-path/$env{ID_PATH}"
+ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
+
+IMPORT{builtin}="usb_id"
+ENV{ID_SERIAL}=="", GOTO="serial_end"
+SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACE_NUM}="$attr{bInterfaceNumber}"
+ENV{ID_USB_INTERFACE_NUM}=="", GOTO="serial_end"
+ENV{.ID_PORT}=="", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}"
+ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}"
+
+LABEL="serial_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM!="block", GOTO="btrfs_end"
+ACTION=="remove", GOTO="btrfs_end"
+ENV{ID_FS_TYPE}!="btrfs", GOTO="btrfs_end"
+ENV{SYSTEMD_READY}=="0", GOTO="btrfs_end"
+
+# let the kernel know about this btrfs filesystem, and check if it is complete
+IMPORT{builtin}="btrfs ready $devnode"
+
+# mark the device as not ready to be used by the system
+ENV{ID_BTRFS_READY}=="0", ENV{SYSTEMD_READY}="0"
+
+# reconsider pending devices in case when multidevice volume awaits
+ENV{ID_BTRFS_READY}=="1", RUN+="/usr/bin/udevadm trigger -s block -p ID_BTRFS_READY=0"
+
+LABEL="btrfs_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="joystick_end"
+ENV{ID_INPUT_JOYSTICK}=="", GOTO="joystick_end"
+KERNEL!="event*", GOTO="joystick_end"
+
+# joystick:<bustype>:v<vid>p<pid>:name:<name>:*
+KERNELS=="input*", ENV{ID_BUS}!="", \
+ IMPORT{builtin}="hwdb 'joystick:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
+ GOTO="joystick_end"
+
+LABEL="joystick_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="mouse_end"
+KERNEL!="event*", GOTO="mouse_end"
+ENV{ID_INPUT_MOUSE}=="", GOTO="mouse_end"
+
+# mouse:<subsystem>:v<vid>p<pid>:name:<name>:*
+KERNELS=="input*", ENV{ID_BUS}=="usb", \
+ IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
+ GOTO="mouse_end"
+KERNELS=="input*", ENV{ID_BUS}=="bluetooth", \
+ IMPORT{builtin}="hwdb 'mouse:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
+ GOTO="mouse_end"
+DRIVERS=="psmouse", SUBSYSTEMS=="serio", \
+ IMPORT{builtin}="hwdb 'mouse:ps2::name:$attr{device/name}:'", \
+ GOTO="mouse_end"
+
+LABEL="mouse_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="touchpad_end"
+ENV{ID_INPUT}=="", GOTO="touchpad_end"
+ENV{ID_INPUT_TOUCHPAD}=="", GOTO="touchpad_end"
+KERNEL!="event*", GOTO="touchpad_end"
+
+# touchpad:<subsystem>:v<vid>p<pid>:name:<name>:*
+KERNELS=="input*", ENV{ID_BUS}!="", \
+ IMPORT{builtin}="hwdb 'touchpad:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
+ GOTO="touchpad_end"
+
+LABEL="touchpad_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION=="remove", GOTO="net_end"
+SUBSYSTEM!="net", GOTO="net_end"
+
+IMPORT{builtin}="net_id"
+
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
+SUBSYSTEMS=="usb", GOTO="net_end"
+
+SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
+SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
+
+LABEL="net_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add", GOTO="mtd_probe_end"
+
+KERNEL=="mtd*ro", IMPORT{program}="mtd_probe $devnode"
+
+LABEL="mtd_probe_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM!="sound", GOTO="sound_end"
+
+ACTION=="add|change", KERNEL=="controlC*", ATTR{../uevent}="change"
+ACTION!="change", GOTO="sound_end"
+
+# Ok, we probably need a little explanation here for what the two lines above
+# are good for.
+#
+# The story goes like this: when ALSA registers a new sound card it emits a
+# series of 'add' events to userspace, for the main card device and for all the
+# child device nodes that belong to it. udev relays those to applications,
+# however only maintains the order between father and child, but not between
+# the siblings. The control device node creation can be used as synchronization
+# point. All other devices that belong to a card are created in the kernel
+# before it. However unfortunately due to the fact that siblings are forwarded
+# out of order by udev this fact is lost to applications.
+#
+# OTOH before an application can open a device it needs to make sure that all
+# its device nodes are completely created and set up.
+#
+# As a workaround for this issue we have added the udev rule above which will
+# generate a 'change' event on the main card device from the 'add' event of the
+# card's control device. Due to the ordering semantics of udev this event will
+# only be relayed after all child devices have finished processing properly.
+# When an application needs to listen for appearing devices it can hence look
+# for 'change' events only, and ignore the actual 'add' events.
+#
+# When the application is initialized at the same time as a device is plugged
+# in it may need to figure out if the 'change' event has already been triggered
+# or not for a card. To find that out we store the flag environment variable
+# SOUND_INITIALIZED on the device which simply tells us if the card 'change'
+# event has already been processed.
+
+KERNEL!="card*", GOTO="sound_end"
+
+ENV{SOUND_INITIALIZED}="1"
+
+IMPORT{builtin}="hwdb"
+SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id"
+SUBSYSTEMS=="usb", GOTO="skip_pci"
+
+SUBSYSTEMS=="firewire", ATTRS{guid}=="?*", \
+ ENV{ID_BUS}="firewire", ENV{ID_SERIAL}="$attr{guid}", ENV{ID_SERIAL_SHORT}="$attr{guid}", \
+ ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{model}", \
+ ENV{ID_VENDOR}="$attr{vendor_name}", ENV{ID_MODEL}="$attr{model_name}"
+SUBSYSTEMS=="firewire", GOTO="skip_pci"
+
+SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
+SUBSYSTEMS=="pci", GOTO="skip_pci"
+
+# If we reach here, the device nor any of its parents are USB/PCI/firewire bus devices.
+# If we now find a parent that is a platform device, assume that we're working with
+# an internal sound card.
+SUBSYSTEMS=="platform", ENV{SOUND_FORM_FACTOR}="internal", GOTO="sound_end"
+
+LABEL="skip_pci"
+
+# Define ID_ID if ID_BUS and ID_SERIAL are set. This will work for both
+# USB and firewire.
+ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="?*", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}-$env{ID_USB_INTERFACE_NUM}"
+ENV{ID_SERIAL}=="?*", ENV{ID_USB_INTERFACE_NUM}=="", ENV{ID_ID}="$env{ID_BUS}-$env{ID_SERIAL}"
+
+IMPORT{builtin}="path_id"
+
+# The values used here for $SOUND_FORM_FACTOR and $SOUND_CLASS should be kept
+# in sync with those defined for PulseAudio's src/pulse/proplist.h
+# PA_PROP_DEVICE_FORM_FACTOR, PA_PROP_DEVICE_CLASS properties.
+
+# If the first PCM device of this card has the pcm class 'modem', then the card is a modem
+ATTR{pcmC%nD0p/pcm_class}=="modem", ENV{SOUND_CLASS}="modem", GOTO="sound_end"
+
+# Identify cards on the internal PCI bus as internal
+SUBSYSTEMS=="pci", DEVPATH=="*/0000:00:??.?/sound/*", ENV{SOUND_FORM_FACTOR}="internal", GOTO="sound_end"
+
+# Devices that also support Image/Video interfaces are most likely webcams
+SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACES}=="*:0e????:*", ENV{SOUND_FORM_FACTOR}="webcam", GOTO="sound_end"
+
+# Matching on the model strings is a bit ugly, I admit
+ENV{ID_MODEL}=="*[Ss]peaker*", ENV{SOUND_FORM_FACTOR}="speaker", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Ss]peaker*", ENV{SOUND_FORM_FACTOR}="speaker", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Hh]eadphone*", ENV{SOUND_FORM_FACTOR}="headphone", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]eadphone*", ENV{SOUND_FORM_FACTOR}="headphone", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Hh]eadset*", ENV{SOUND_FORM_FACTOR}="headset", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]eadset*", ENV{SOUND_FORM_FACTOR}="headset", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Hh]andset*", ENV{SOUND_FORM_FACTOR}="handset", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Hh]andset*", ENV{SOUND_FORM_FACTOR}="handset", GOTO="sound_end"
+
+ENV{ID_MODEL}=="*[Mm]icrophone*", ENV{SOUND_FORM_FACTOR}="microphone", GOTO="sound_end"
+ENV{ID_MODEL_FROM_DATABASE}=="*[Mm]icrophone*", ENV{SOUND_FORM_FACTOR}="microphone", GOTO="sound_end"
+
+LABEL="sound_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+ACTION!="add", GOTO="drivers_end"
+
+ENV{MODALIAS}=="?*", RUN{builtin}+="kmod load $env{MODALIAS}"
+SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="SD", RUN{builtin}+="kmod load tifm_sd"
+SUBSYSTEM=="tifm", ENV{TIFM_CARD_TYPE}=="MS", RUN{builtin}+="kmod load tifm_ms"
+SUBSYSTEM=="memstick", RUN{builtin}+="kmod load ms_block mspro_block"
+SUBSYSTEM=="i2o", RUN{builtin}+="kmod load i2o_block"
+SUBSYSTEM=="module", KERNEL=="parport_pc", RUN{builtin}+="kmod load ppdev"
+KERNEL=="mtd*ro", ENV{MTD_FTL}=="smartmedia", RUN{builtin}+="kmod load sm_ftl"
+
+LABEL="drivers_end"
--- /dev/null
+# do not edit this file, it will be overwritten on update
+
+SUBSYSTEM!="net", GOTO="net_setup_link_end"
+
+IMPORT{builtin}="path_id"
+
+ACTION!="add", GOTO="net_setup_link_end"
+
+IMPORT{builtin}="net_setup_link"
+
+NAME=="", ENV{ID_NET_NAME}!="", NAME="$env{ID_NET_NAME}"
+
+LABEL="net_setup_link_end"
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1+
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+ACTION=="remove", GOTO="systemd_end"
+
+SUBSYSTEM=="tty", KERNEL=="tty[a-zA-Z]*|hvc*|xvc*|hvsi*|ttysclp*|sclp_line*|3270/tty[0-9]*", TAG+="systemd"
+KERNEL=="vport*", TAG+="systemd"
+
+SUBSYSTEM=="ubi", TAG+="systemd"
+
+SUBSYSTEM=="block", TAG+="systemd"
+SUBSYSTEM=="block", ACTION=="add", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", ENV{SYSTEMD_READY}="0"
+
+# Ignore encrypted devices with no identified superblock on it, since
+# we are probably still calling mke2fs or mkswap on it.
+SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0"
+
+# add symlink to GPT root disk
+SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="gpt-auto-root"
+SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="gpt-auto-root-luks"
+SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", SYMLINK+="gpt-auto-root"
+
+# Ignore raid devices that are not yet assembled and started
+SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", TEST!="md/array_state", ENV{SYSTEMD_READY}="0"
+SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", ATTR{md/array_state}=="|clear|inactive", ENV{SYSTEMD_READY}="0"
+
+# Ignore loop devices that don't have any file attached
+SUBSYSTEM=="block", KERNEL=="loop[0-9]*", ENV{DEVTYPE}=="disk", TEST!="loop/backing_file", ENV{SYSTEMD_READY}="0"
+
+# Ignore nbd devices until the PID file exists (which signals a connected device)
+SUBSYSTEM=="block", KERNEL=="nbd*", ENV{DEVTYPE}=="disk", TEST!="pid", ENV{SYSTEMD_READY}="0"
+
+# We need a hardware independent way to identify network devices. We
+# use the /sys/subsystem/ path for this. Kernel "bus" and "class" names
+# should be treated as one namespace, like udev handles it. This is mostly
+# just an identification string for systemd, so whether the path actually is
+# accessible or not does not matter as long as it is unique and in the
+# filesystem namespace.
+#
+# http://cgit.freedesktop.org/systemd/systemd/tree/src/libudev/libudev-enumerate.c#n955
+
+SUBSYSTEM=="net", KERNEL!="lo", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/net/devices/$name"
+SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_ALIAS}+="/sys/subsystem/bluetooth/devices/%k"
+
+SUBSYSTEM=="bluetooth", TAG+="systemd", ENV{SYSTEMD_WANTS}+="bluetooth.target"
+ENV{ID_SMARTCARD_READER}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="smartcard.target"
+SUBSYSTEM=="sound", KERNEL=="card*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sound.target"
+
+SUBSYSTEM=="printer", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target"
+SUBSYSTEM=="usb", KERNEL=="lp*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target"
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0701??:*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="printer.target"
+
+# Apply sysctl variables to network devices (and only to those) as they appear.
+ACTION=="add", SUBSYSTEM=="net", KERNEL!="lo", RUN+="/usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/$name --prefix=/net/ipv4/neigh/$name --prefix=/net/ipv6/conf/$name --prefix=/net/ipv6/neigh/$name"
+
+# Pull in backlight save/restore for all backlight devices and
+# keyboard backlights
+SUBSYSTEM=="backlight", TAG+="systemd", IMPORT{builtin}="path_id", ENV{SYSTEMD_WANTS}+="systemd-backlight@backlight:$name.service"
+SUBSYSTEM=="leds", KERNEL=="*kbd_backlight", TAG+="systemd", IMPORT{builtin}="path_id", ENV{SYSTEMD_WANTS}+="systemd-backlight@leds:$name.service"
+
+# Pull in rfkill save/restore for all rfkill devices
+SUBSYSTEM=="rfkill", ENV{SYSTEMD_RFKILL}="1"
+SUBSYSTEM=="rfkill", IMPORT{builtin}="path_id"
+SUBSYSTEM=="misc", KERNEL=="rfkill", TAG+="systemd", ENV{SYSTEMD_WANTS}+="systemd-rfkill.socket"
+
+# Asynchronously mount file systems implemented by these modules as soon as they are loaded.
+SUBSYSTEM=="module", KERNEL=="fuse", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-fs-fuse-connections.mount"
+SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sys-kernel-config.mount"
+
+LABEL="systemd_end"