Merge pull request #7284 from poettering/cgroup-delegate-mask

add a concept of delegating cgroups per unit while enabling specific controllers
This commit is contained in:
Lennart Poettering 2017-11-13 12:14:23 +01:00 committed by GitHub
commit f38326f21a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 549 additions and 209 deletions

6
TODO
View File

@ -66,10 +66,16 @@ Features:
* In journalctl add a way how "-o verbose" and suchlike can be tweaked to show
only a specific set of properties
* beef up pam_systemd to take unit file settings such as cgroups properties as
parameters
* export UID ranges nspawns's --private-user and DynamicUser= uses in
the systemd.pc pkg-config file, the same way we already expose the system
user boundary there
* a new "systemd-analyze security" tool outputting a checklist of security
features a service does and does not implement
* Whenever we check a UID against the system UID range, also check for the
dynamic UID range

View File

@ -711,13 +711,30 @@
<term><varname>Delegate=</varname></term>
<listitem>
<para>Turns on delegation of further resource control
partitioning to processes of the unit. For unprivileged
services (i.e. those using the <varname>User=</varname>
setting), this allows processes to create a subhierarchy
beneath its control group path. For privileged services and
scopes, this ensures the processes will have all control
group controllers enabled.</para>
<para>Turns on delegation of further resource control partitioning to processes of the unit. Units where this
is enabled may create and manage their own private subhierarchy of control groups below the control group of
the unit itself. For unprivileged services (i.e. those using the <varname>User=</varname> setting) the unit's
control group will be made accessible to the relevant user. When enabled the service manager will refrain
from manipulating control groups or moving processes below the unit's control group, so that a clear concept
of ownership is established: the control group tree above the unit's control group (i.e. towards the root
control group) is owned and managed by the service manager of the host, while the control group tree below
the unit's control group is owned and managed by the unit itself. Takes either a boolean argument or a list
of control group controller names. If true, delegation is turned on, and all supported controllers are
enabled for the unit, making them available to the unit's processes for management. If false, delegation is
turned off entirely (and no additional controllers are enabled). If set to a list of controllers, delegation
is turned on, and the specified controllers are enabled for the unit. Note that assigning the empty string
will enable delegation, but not enable any additional controllers. Defaults to false.</para>
<para>Note that controller delegation to less privileged code is only safe on the unified control group
hierarchy. Accordingly, access to the specified controllers will not be granted to unprivileged services on
the legacy hierarchy, even when requested.</para>
<para>The following controller names may be specified: <option>cpu</option>, <option>cpuacct</option>,
<option>io</option>, <option>blkio</option>, <option>memory</option>, <option>devices</option>,
<option>pids</option>. Not all of these controllers are available on all kernels however, and some are
specific to the unified hierarchy while others are specific to the legacy hierarchy. Also note that the
kernel might support further controllers, which aren't covered here yet as delegation is either not supported
at all for them or not defined cleanly.</para>
</listitem>
</varlistentry>

View File

@ -2244,10 +2244,10 @@ int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root)
}
int cg_mask_to_string(CGroupMask mask, char **ret) {
const char *controllers[_CGROUP_CONTROLLER_MAX + 1];
_cleanup_free_ char *s = NULL;
size_t n = 0, allocated = 0;
bool space = false;
CGroupController c;
int i = 0;
char *s;
assert(ret);
@ -2257,19 +2257,32 @@ int cg_mask_to_string(CGroupMask mask, char **ret) {
}
for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) {
const char *k;
size_t l;
if (!(mask & CGROUP_CONTROLLER_TO_MASK(c)))
continue;
controllers[i++] = cgroup_controller_to_string(c);
controllers[i] = NULL;
k = cgroup_controller_to_string(c);
l = strlen(k);
if (!GREEDY_REALLOC(s, allocated, n + space + l + 1))
return -ENOMEM;
if (space)
s[n] = ' ';
memcpy(s + n + space, k, l);
n += space + l;
space = true;
}
s = strv_join((char **)controllers, NULL);
if (!s)
return -ENOMEM;
assert(s);
s[n] = 0;
*ret = s;
s = NULL;
return 0;
}

View File

@ -34,11 +34,11 @@
/* An enum of well known cgroup controllers */
typedef enum CGroupController {
CGROUP_CONTROLLER_CPU,
CGROUP_CONTROLLER_CPUACCT,
CGROUP_CONTROLLER_IO,
CGROUP_CONTROLLER_BLKIO,
CGROUP_CONTROLLER_CPUACCT, /* v1 only */
CGROUP_CONTROLLER_IO, /* v2 only */
CGROUP_CONTROLLER_BLKIO, /* v1 only */
CGROUP_CONTROLLER_MEMORY,
CGROUP_CONTROLLER_DEVICES,
CGROUP_CONTROLLER_DEVICES, /* v1 only */
CGROUP_CONTROLLER_PIDS,
_CGROUP_CONTROLLER_MAX,
_CGROUP_CONTROLLER_INVALID = -1,

View File

@ -1527,9 +1527,7 @@ int mkdtemp_malloc(const char *template, char **ret) {
return 0;
}
static inline void funlockfilep(FILE **f) {
funlockfile(*f);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, funlockfile);
int read_line(FILE *f, size_t limit, char **ret) {
_cleanup_free_ char *buffer = NULL;

View File

@ -278,6 +278,9 @@ char *strjoin_real(const char *x, ...) {
char *strstrip(char *s) {
char *e;
if (!s)
return NULL;
/* Drops trailing whitespace. Modifies the string in
* place. Returns pointer to first non-space character */
@ -295,7 +298,13 @@ char *strstrip(char *s) {
char *delete_chars(char *s, const char *bad) {
char *f, *t;
/* Drops all whitespace, regardless where in the string */
/* Drops all specified bad characters, regardless where in the string */
if (!s)
return NULL;
if (!bad)
bad = WHITESPACE;
for (f = s, t = s; *f; f++) {
if (strchr(bad, *f))
@ -309,6 +318,26 @@ char *delete_chars(char *s, const char *bad) {
return s;
}
char *delete_trailing_chars(char *s, const char *bad) {
char *p, *c = s;
/* Drops all specified bad characters, at the end of the string */
if (!s)
return NULL;
if (!bad)
bad = WHITESPACE;
for (p = s; *p; p++)
if (!strchr(bad, *p))
c = p + 1;
*c = 0;
return s;
}
char *truncate_nl(char *s) {
assert(s);

View File

@ -133,8 +133,20 @@ char *strjoin_real(const char *x, ...) _sentinel_;
char *strstrip(char *s);
char *delete_chars(char *s, const char *bad);
char *delete_trailing_chars(char *s, const char *bad);
char *truncate_nl(char *s);
static inline char *skip_leading_chars(const char *s, const char *bad) {
if (!s)
return NULL;
if (!bad)
bad = WHITESPACE;
return (char*) s + strspn(s, bad);
}
char ascii_tolower(char x);
char *ascii_strlower(char *s);
char *ascii_strlower_n(char *s, size_t n);

View File

@ -382,19 +382,14 @@ int unit_name_path_escape(const char *f, char **ret) {
if (STR_IN_SET(p, "/", ""))
s = strdup("-");
else {
char *e;
if (!path_is_safe(p))
return -EINVAL;
/* Truncate trailing slashes */
e = endswith(p, "/");
if (e)
*e = 0;
delete_trailing_chars(p, "/");
/* Truncate leading slashes */
if (p[0] == '/')
p++;
p = skip_leading_chars(p, "/");
s = unit_name_escape(p);
}

View File

@ -209,6 +209,16 @@ void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) {
prefix, cgroup_device_policy_to_string(c->device_policy),
prefix, yes_no(c->delegate));
if (c->delegate) {
_cleanup_free_ char *t = NULL;
(void) cg_mask_to_string(c->delegate_controllers, &t);
fprintf(f, "%sDelegateController=%s\n",
prefix,
strempty(t));
}
LIST_FOREACH(device_allow, a, c->device_allow)
fprintf(f,
"%sDeviceAllow=%s %s%s%s\n",
@ -1062,37 +1072,47 @@ CGroupMask unit_get_own_mask(Unit *u) {
if (!c)
return 0;
/* If delegation is turned on, then turn on all cgroups,
* unless we are on the legacy hierarchy and the process we
* fork into it is known to drop privileges, and hence
* shouldn't get access to the controllers.
*
* Note that on the unified hierarchy it is safe to delegate
* controllers to unprivileged services. */
return cgroup_context_get_mask(c);
}
if (c->delegate) {
CGroupMask unit_get_delegate_mask(Unit *u) {
CGroupContext *c;
/* If delegation is turned on, then turn on selected controllers, unless we are on the legacy hierarchy and the
* process we fork into is known to drop privileges, and hence shouldn't get access to the controllers.
*
* Note that on the unified hierarchy it is safe to delegate controllers to unprivileged services. */
if (u->type == UNIT_SLICE)
return 0;
c = unit_get_cgroup_context(u);
if (!c)
return 0;
if (!c->delegate)
return 0;
if (cg_all_unified() <= 0) {
ExecContext *e;
e = unit_get_exec_context(u);
if (!e ||
exec_context_maintains_privileges(e) ||
cg_all_unified() > 0)
return _CGROUP_MASK_ALL;
if (e && !exec_context_maintains_privileges(e))
return 0;
}
return cgroup_context_get_mask(c);
return c->delegate_controllers;
}
CGroupMask unit_get_members_mask(Unit *u) {
assert(u);
/* Returns the mask of controllers all of the unit's children
* require, merged */
/* Returns the mask of controllers all of the unit's children require, merged */
if (u->cgroup_members_mask_valid)
return u->cgroup_members_mask;
u->cgroup_members_mask = 0;
u->cgroup_members_mask = unit_get_delegate_mask(u);
if (u->type == UNIT_SLICE) {
void *v;
@ -1107,9 +1127,7 @@ CGroupMask unit_get_members_mask(Unit *u) {
if (UNIT_DEREF(member->slice) != u)
continue;
u->cgroup_members_mask |=
unit_get_own_mask(member) |
unit_get_members_mask(member);
u->cgroup_members_mask |= unit_get_subtree_mask(member); /* note that this calls ourselves again, for the children */
}
}
@ -1127,7 +1145,7 @@ CGroupMask unit_get_siblings_mask(Unit *u) {
if (UNIT_ISSET(u->slice))
return unit_get_members_mask(UNIT_DEREF(u->slice));
return unit_get_own_mask(u) | unit_get_members_mask(u);
return unit_get_subtree_mask(u);
}
CGroupMask unit_get_subtree_mask(Unit *u) {
@ -1946,11 +1964,9 @@ int manager_setup_cgroup(Manager *m) {
if (e)
*e = 0;
/* And make sure to store away the root value without trailing
* slash, even for the root dir, so that we can easily prepend
* it everywhere. */
while ((e = endswith(m->cgroup_root, "/")))
*e = 0;
/* And make sure to store away the root value without trailing slash, even for the root dir, so that we can
* easily prepend it everywhere. */
delete_trailing_chars(m->cgroup_root, "/");
/* 2. Show data */
r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path);

View File

@ -126,6 +126,7 @@ struct CGroupContext {
uint64_t tasks_max;
bool delegate;
CGroupMask delegate_controllers;
};
/* Used when querying IP accounting data */
@ -153,8 +154,9 @@ void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODe
void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b);
CGroupMask unit_get_own_mask(Unit *u);
CGroupMask unit_get_siblings_mask(Unit *u);
CGroupMask unit_get_delegate_mask(Unit *u);
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);

View File

@ -32,6 +32,42 @@
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy);
static int property_get_delegate_controllers(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *error) {
CGroupContext *c = userdata;
CGroupController cc;
int r;
assert(bus);
assert(reply);
assert(c);
if (!c->delegate)
return sd_bus_message_append(reply, "as", 0);
r = sd_bus_message_open_container(reply, 'a', "s");
if (r < 0)
return r;
for (cc = 0; cc < _CGROUP_CONTROLLER_MAX; cc++) {
if ((c->delegate_controllers & CGROUP_CONTROLLER_TO_MASK(cc)) == 0)
continue;
r = sd_bus_message_append(reply, "s", cgroup_controller_to_string(cc));
if (r < 0)
return r;
}
return sd_bus_message_close_container(reply);
}
static int property_get_io_device_weight(
sd_bus *bus,
const char *path,
@ -255,6 +291,7 @@ static int property_get_ip_address_access(
const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0),
SD_BUS_PROPERTY("DelegateControllers", "as", property_get_delegate_controllers, 0, 0),
SD_BUS_PROPERTY("CPUAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, cpu_accounting), 0),
SD_BUS_PROPERTY("CPUWeight", "t", NULL, offsetof(CGroupContext, cpu_weight), 0),
SD_BUS_PROPERTY("StartupCPUWeight", "t", NULL, offsetof(CGroupContext, startup_cpu_weight), 0),
@ -315,9 +352,54 @@ static int bus_cgroup_set_transient_property(
if (mode != UNIT_CHECK) {
c->delegate = b;
c->delegate_controllers = b ? _CGROUP_MASK_ALL : 0;
unit_write_drop_in_private(u, mode, name, b ? "Delegate=yes" : "Delegate=no");
}
return 1;
} else if (streq(name, "DelegateControllers")) {
CGroupMask mask = 0;
r = sd_bus_message_enter_container(message, 'a', "s");
if (r < 0)
return r;
for (;;) {
CGroupController cc;
const char *t;
r = sd_bus_message_read(message, "s", &t);
if (r < 0)
return r;
if (r == 0)
break;
cc = cgroup_controller_from_string(t);
if (cc < 0)
return sd_bus_error_set_errnof(error, EINVAL, "Unknown cgroup contoller '%s'", t);
mask |= CGROUP_CONTROLLER_TO_MASK(cc);
}
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (mode != UNIT_CHECK) {
_cleanup_free_ char *t = NULL;
r = cg_mask_to_string(mask, &t);
if (r < 0)
return r;
c->delegate = true;
c->delegate_controllers |= mask;
unit_write_drop_in_private_format(u, mode, name, "Delegate=%s", t);
}
return 1;
}

View File

@ -117,8 +117,8 @@ static int process_deps(Unit *u, UnitDependency dependency, const char *dir_suff
r = unit_add_dependency_by_name(u, dependency, entry, *p, true, UNIT_DEPENDENCY_FILE);
if (r < 0)
log_unit_error_errno(u, r, "cannot add %s dependency on %s, ignoring: %m",
unit_dependency_to_string(dependency), entry);
log_unit_warning_errno(u, r, "Cannot add %s dependency on %s, ignoring: %m",
unit_dependency_to_string(dependency), entry);
}
return 0;
@ -154,12 +154,11 @@ int unit_load_dropin(Unit *u) {
return log_oom();
}
STRV_FOREACH(f, u->dropin_paths) {
config_parse(u->id, *f, NULL,
UNIT_VTABLE(u)->sections,
config_item_perf_lookup, load_fragment_gperf_lookup,
false, false, false, u);
}
STRV_FOREACH(f, u->dropin_paths)
(void) config_parse(u->id, *f, NULL,
UNIT_VTABLE(u)->sections,
config_item_perf_lookup, load_fragment_gperf_lookup,
0, u);
u->dropin_mtime = now(CLOCK_REALTIME);

View File

@ -174,7 +174,7 @@ $1.BlockIOReadBandwidth, config_parse_blockio_bandwidth, 0,
$1.BlockIOWriteBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context)
$1.TasksAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.tasks_accounting)
$1.TasksMax, config_parse_tasks_max, 0, offsetof($1, cgroup_context.tasks_max)
$1.Delegate, config_parse_bool, 0, offsetof($1, cgroup_context.delegate)
$1.Delegate, config_parse_delegate, 0, offsetof($1, cgroup_context)
$1.IPAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.ip_accounting)
$1.IPAddressAllow, config_parse_ip_address_access, 0, offsetof($1, cgroup_context.ip_address_allow)
$1.IPAddressDeny, config_parse_ip_address_access, 0, offsetof($1, cgroup_context.ip_address_deny)

View File

@ -3202,6 +3202,67 @@ int config_parse_tasks_max(
return 0;
}
int config_parse_delegate(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
CGroupContext *c = data;
int r;
/* We either accept a boolean value, which may be used to turn on delegation for all controllers, or turn it
* off for all. Or it takes a list of controller names, in which case we add the specified controllers to the
* mask to delegate. */
r = parse_boolean(rvalue);
if (r < 0) {
const char *p = rvalue;
CGroupMask mask = 0;
for (;;) {
_cleanup_free_ char *word = NULL;
CGroupController cc;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
return r;
}
cc = cgroup_controller_from_string(word);
if (cc < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Invalid controller name '%s', ignoring", rvalue);
continue;
}
mask |= CGROUP_CONTROLLER_TO_MASK(cc);
}
c->delegate = true;
c->delegate_controllers |= mask;
} else if (r > 0) {
c->delegate = true;
c->delegate_controllers = _CGROUP_MASK_ALL;
} else {
c->delegate = false;
c->delegate_controllers = 0;
}
return 0;
}
int config_parse_device_allow(
const char *unit,
const char *filename,
@ -4450,7 +4511,7 @@ static int load_from_path(Unit *u, const char *path) {
r = config_parse(u->id, filename, f,
UNIT_VTABLE(u)->sections,
config_item_perf_lookup, load_fragment_gperf_lookup,
false, true, false, u);
CONFIG_PARSE_ALLOW_INCLUDE, u);
if (r < 0)
return r;
}

View File

@ -85,6 +85,7 @@ int config_parse_cpu_weight(const char *unit, const char *filename, unsigned lin
int config_parse_cpu_shares(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_memory_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_tasks_max(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_delegate(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_device_policy(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_device_allow(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
int config_parse_io_weight(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);

View File

@ -768,7 +768,7 @@ static int parse_config_file(void) {
CONF_PATHS_NULSTR("systemd/system.conf.d") :
CONF_PATHS_NULSTR("systemd/user.conf.d");
config_parse_many_nulstr(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL);
(void) config_parse_many_nulstr(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, CONFIG_PARSE_WARN, NULL);
/* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY
* like everywhere else. */

View File

@ -1152,10 +1152,6 @@ int setup_namespace(
}
}
/* Try to set up the new root directory before mounting anything there */
if (root)
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
if (root_image) {
/* A root image is specified, mount it to the right place */
r = dissected_image_mount(dissected_image, root, dissect_image_flags);
@ -1192,6 +1188,10 @@ int setup_namespace(
}
}
/* Try to set up the new root directory before mounting anything else there. */
if (root_image || root_directory)
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
if (n_mounts > 0) {
_cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
char **blacklist;

View File

@ -1057,8 +1057,9 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
timespan[FORMAT_TIMESPAN_MAX];
Unit *following;
_cleanup_set_free_ Set *following_set = NULL;
int r;
const char *n;
CGroupMask m;
int r;
assert(u);
assert(u->type >= 0);
@ -1105,11 +1106,23 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
if (u->cgroup_realized_mask != 0) {
_cleanup_free_ char *s = NULL;
(void) cg_mask_to_string(u->cgroup_realized_mask, &s);
fprintf(f, "%s\tCGroup mask: %s\n", prefix, strnull(s));
fprintf(f, "%s\tCGroup realized mask: %s\n", prefix, strnull(s));
}
if (u->cgroup_members_mask != 0) {
if (u->cgroup_enabled_mask != 0) {
_cleanup_free_ char *s = NULL;
(void) cg_mask_to_string(u->cgroup_members_mask, &s);
(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;
(void) cg_mask_to_string(m, &s);
fprintf(f, "%s\tCGroup members mask: %s\n", prefix, strnull(s));
}

View File

@ -147,7 +147,7 @@ static int parse_config(void) {
CONF_PATHS_NULSTR("systemd/coredump.conf.d"),
"Coredump\0",
config_item_table_lookup, items,
false, NULL);
CONFIG_PARSE_WARN, NULL);
}
static inline uint64_t storage_size_max(void) {

View File

@ -1254,7 +1254,7 @@ static int parse_config(void) {
return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf",
CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"),
"Remote\0", config_item_table_lookup, items,
false, NULL);
CONFIG_PARSE_WARN, NULL);
}
static void help(void) {

View File

@ -543,7 +543,7 @@ static int parse_config(void) {
return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-upload.conf",
CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"),
"Upload\0", config_item_table_lookup, items,
false, NULL);
CONFIG_PARSE_WARN, NULL);
}
static void help(void) {

View File

@ -1398,7 +1398,7 @@ static int server_parse_config_file(Server *s) {
CONF_PATHS_NULSTR("systemd/journald.conf.d"),
"Journal\0",
config_item_perf_lookup, journald_gperf_lookup,
false, s);
CONFIG_PARSE_WARN, s);
}
static int server_dispatch_sync(sd_event_source *es, usec_t t, void *userdata) {

View File

@ -138,7 +138,6 @@ int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_expor
#define UDEV_ALLOWED_CHARS_INPUT "/ $%?,"
int util_log_priority(const char *priority);
size_t util_path_encode(const char *src, char *dest, size_t size);
void util_remove_trailing_chars(char *path, char c);
int util_replace_whitespace(const char *str, char *to, size_t len);
int util_replace_chars(char *str, const char *white);
unsigned int util_string_hash32(const char *key);

View File

@ -150,17 +150,6 @@ size_t util_path_encode(const char *src, char *dest, size_t size)
return j;
}
void util_remove_trailing_chars(char *path, char c)
{
size_t len;
if (path == NULL)
return;
len = strlen(path);
while (len > 0 && path[len-1] == c)
path[--len] = '\0';
}
/*
* Copy from 'str' to 'to', while removing all leading and trailing whitespace,
* and replacing each run of consecutive whitespace with a single underscore.

View File

@ -1072,7 +1072,7 @@ static int manager_parse_config_file(Manager *m) {
CONF_PATHS_NULSTR("systemd/logind.conf.d"),
"Login\0",
config_item_perf_lookup, logind_gperf_lookup,
false, m);
CONFIG_PARSE_WARN, m);
}
static int manager_dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {

View File

@ -630,7 +630,7 @@ static int netdev_load_one(Manager *manager, const char *filename) {
r = config_parse_many(filename, network_dirs, dropin_dirname,
"Match\0NetDev\0",
config_item_perf_lookup, network_netdev_gperf_lookup,
true, netdev_raw);
CONFIG_PARSE_WARN, netdev_raw);
if (r < 0)
return r;
@ -671,7 +671,7 @@ static int netdev_load_one(Manager *manager, const char *filename) {
r = config_parse(NULL, filename, file,
NETDEV_VTABLE(netdev)->sections,
config_item_perf_lookup, network_netdev_gperf_lookup,
false, false, false, netdev);
CONFIG_PARSE_WARN, netdev);
if (r < 0)
return r;

View File

@ -35,7 +35,7 @@ int manager_parse_config_file(Manager *m) {
CONF_PATHS_NULSTR("systemd/networkd.conf.d"),
"DHCP\0",
config_item_perf_lookup, networkd_gperf_lookup,
false, m);
CONFIG_PARSE_WARN, m);
}
static const char* const duid_type_table[_DUID_TYPE_MAX] = {

View File

@ -280,7 +280,7 @@ static int network_load_one(Manager *manager, const char *filename) {
"IPv6PrefixDelegation\0"
"IPv6Prefix\0",
config_item_perf_lookup, network_network_gperf_lookup,
false, network);
CONFIG_PARSE_WARN, network);
if (r < 0)
return r;

View File

@ -59,9 +59,7 @@ int settings_load(FILE *f, const char *path, Settings **ret) {
"Network\0"
"Files\0",
config_item_perf_lookup, nspawn_gperf_lookup,
false,
false,
true,
CONFIG_PARSE_WARN,
s);
if (r < 0)
return r;

View File

@ -236,7 +236,7 @@ int manager_parse_config_file(Manager *m) {
CONF_PATHS_NULSTR("systemd/resolved.conf.d"),
"Resolve\0",
config_item_perf_lookup, resolved_gperf_lookup,
false, m);
CONFIG_PARSE_WARN, m);
if (r < 0)
return r;

View File

@ -183,6 +183,51 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "sv", field, "t", bytes);
goto finish;
} else if (streq(field, "Delegate")) {
r = parse_boolean(eq);
if (r < 0) {
const char *p = eq;
r = sd_bus_message_append(m, "s", "DelegateControllers");
if (r < 0)
goto finish;
r = sd_bus_message_open_container(m, 'v', "as");
if (r < 0)
goto finish;
r = sd_bus_message_open_container(m, 'a', "s");
if (r < 0)
goto finish;
for (;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Invalid syntax: %s", eq);
r = sd_bus_message_append(m, "s", word);
if (r < 0)
goto finish;
}
r = sd_bus_message_close_container(m);
if (r < 0)
goto finish;
r = sd_bus_message_close_container(m);
} else
r = sd_bus_message_append(m, "sv", "Delegate", "b", r);
goto finish;
} else if (streq(field, "TasksMax")) {
uint64_t t;
@ -238,7 +283,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
"TasksAccounting", "IPAccounting", "SendSIGHUP", "SendSIGKILL", "WakeSystem",
"DefaultDependencies", "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "TTYVTDisallocate",
"RemainAfterExit", "PrivateTmp", "PrivateDevices", "PrivateNetwork", "PrivateUsers",
"NoNewPrivileges", "SyslogLevelPrefix", "Delegate", "RemainAfterElapse",
"NoNewPrivileges", "SyslogLevelPrefix", "RemainAfterElapse",
"MemoryDenyWriteExecute", "RestrictRealtime", "DynamicUser", "RemoveIPC",
"ProtectKernelTunables", "ProtectKernelModules", "ProtectControlGroups", "MountAPIVFS",
"CPUSchedulingResetOnFork", "LockPersonality")) {

View File

@ -121,17 +121,18 @@ int config_item_perf_lookup(
}
/* Run the user supplied parser for an assignment */
static int next_assignment(const char *unit,
const char *filename,
unsigned line,
ConfigItemLookup lookup,
const void *table,
const char *section,
unsigned section_line,
const char *lvalue,
const char *rvalue,
bool relaxed,
void *userdata) {
static int next_assignment(
const char *unit,
const char *filename,
unsigned line,
ConfigItemLookup lookup,
const void *table,
const char *section,
unsigned section_line,
const char *lvalue,
const char *rvalue,
ConfigParseFlags flags,
void *userdata) {
ConfigParserCallback func = NULL;
int ltype = 0;
@ -157,26 +158,26 @@ static int next_assignment(const char *unit,
}
/* Warn about unknown non-extension fields. */
if (!relaxed && !startswith(lvalue, "X-"))
if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(lvalue, "X-"))
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown lvalue '%s' in section '%s'", lvalue, section);
return 0;
}
/* Parse a variable assignment line */
static int parse_line(const char* unit,
const char *filename,
unsigned line,
const char *sections,
ConfigItemLookup lookup,
const void *table,
bool relaxed,
bool allow_include,
char **section,
unsigned *section_line,
bool *section_ignored,
char *l,
void *userdata) {
static int parse_line(
const char* unit,
const char *filename,
unsigned line,
const char *sections,
ConfigItemLookup lookup,
const void *table,
ConfigParseFlags flags,
char **section,
unsigned *section_line,
bool *section_ignored,
char *l,
void *userdata) {
char *e;
@ -186,7 +187,6 @@ static int parse_line(const char* unit,
assert(l);
l = strstrip(l);
if (!*l)
return 0;
@ -205,7 +205,7 @@ static int parse_line(const char* unit,
*
* Support for them should be eventually removed. */
if (!allow_include) {
if (!(flags & CONFIG_PARSE_ALLOW_INCLUDE)) {
log_syntax(unit, LOG_ERR, filename, line, 0, ".include not allowed here. Ignoring.");
return 0;
}
@ -214,7 +214,7 @@ static int parse_line(const char* unit,
if (!fn)
return -ENOMEM;
return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, false, userdata);
return config_parse(unit, fn, NULL, sections, lookup, table, flags, userdata);
}
if (*l == '[') {
@ -235,7 +235,7 @@ static int parse_line(const char* unit,
if (sections && !nulstr_contains(sections, n)) {
if (!relaxed && !startswith(n, "X-"))
if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(n, "X-"))
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n);
free(n);
@ -254,7 +254,7 @@ static int parse_line(const char* unit,
if (sections && !*section) {
if (!relaxed && !*section_ignored)
if (!(flags & CONFIG_PARSE_RELAXED) && !*section_ignored)
log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring.");
return 0;
@ -278,7 +278,7 @@ static int parse_line(const char* unit,
*section_line,
strstrip(l),
strstrip(e),
relaxed,
flags,
userdata);
}
@ -289,15 +289,13 @@ int config_parse(const char *unit,
const char *sections,
ConfigItemLookup lookup,
const void *table,
bool relaxed,
bool allow_include,
bool warn,
ConfigParseFlags flags,
void *userdata) {
_cleanup_free_ char *section = NULL, *continuation = NULL;
_cleanup_fclose_ FILE *ours = NULL;
unsigned line = 0, section_line = 0;
bool section_ignored = false, allow_bom = true;
bool section_ignored = false;
int r;
assert(filename);
@ -308,7 +306,7 @@ int config_parse(const char *unit,
if (!f) {
/* Only log on request, except for ENOENT,
* since we return 0 to the caller. */
if (warn || errno == ENOENT)
if ((flags & CONFIG_PARSE_WARN) || errno == ENOENT)
log_full(errno == ENOENT ? LOG_DEBUG : LOG_ERR,
"Failed to open configuration file '%s': %m", filename);
return errno == ENOENT ? 0 : -errno;
@ -319,52 +317,50 @@ int config_parse(const char *unit,
for (;;) {
_cleanup_free_ char *buf = NULL;
char *l, *p, *c = NULL, *e;
bool escaped = false;
char *l, *p, *e;
r = read_line(f, LONG_LINE_MAX, &buf);
if (r == 0)
break;
if (r == -ENOBUFS) {
if (warn)
if (flags & CONFIG_PARSE_WARN)
log_error_errno(r, "%s:%u: Line too long", filename, line);
return r;
}
if (r < 0) {
if (warn)
if (CONFIG_PARSE_WARN)
log_error_errno(r, "%s:%u: Error while reading configuration file: %m", filename, line);
return r;
}
l = buf;
if (allow_bom) {
if (!(flags & CONFIG_PARSE_REFUSE_BOM)) {
char *q;
q = startswith(buf, UTF8_BYTE_ORDER_MARK);
if (q) {
l = q;
allow_bom = false;
flags |= CONFIG_PARSE_REFUSE_BOM;
}
}
if (continuation) {
if (strlen(continuation) + strlen(l) > LONG_LINE_MAX) {
if (warn)
if (flags & CONFIG_PARSE_WARN)
log_error("%s:%u: Continuation line too long", filename, line);
return -ENOBUFS;
}
c = strappend(continuation, l);
if (!c) {
if (warn)
if (!strextend(&continuation, l, NULL)) {
if (flags & CONFIG_PARSE_WARN)
log_oom();
return -ENOMEM;
}
continuation = mfree(continuation);
p = c;
p = continuation;
} else
p = l;
@ -378,12 +374,10 @@ int config_parse(const char *unit,
if (escaped) {
*(e-1) = ' ';
if (c)
continuation = c;
else {
if (!continuation) {
continuation = strdup(l);
if (!continuation) {
if (warn)
if (flags & CONFIG_PARSE_WARN)
log_oom();
return -ENOMEM;
}
@ -398,20 +392,20 @@ int config_parse(const char *unit,
sections,
lookup,
table,
relaxed,
allow_include,
flags,
&section,
&section_line,
&section_ignored,
p,
userdata);
free(c);
if (r < 0) {
if (warn)
if (flags & CONFIG_PARSE_WARN)
log_warning_errno(r, "%s:%u: Failed to parse file: %m", filename, line);
return r;
}
continuation = mfree(continuation);
}
return 0;
@ -423,20 +417,20 @@ static int config_parse_many_files(
const char *sections,
ConfigItemLookup lookup,
const void *table,
bool relaxed,
ConfigParseFlags flags,
void *userdata) {
char **fn;
int r;
if (conf_file) {
r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata);
r = config_parse(NULL, conf_file, NULL, sections, lookup, table, flags, userdata);
if (r < 0)
return r;
}
STRV_FOREACH(fn, files) {
r = config_parse(NULL, *fn, NULL, sections, lookup, table, relaxed, false, true, userdata);
r = config_parse(NULL, *fn, NULL, sections, lookup, table, flags, userdata);
if (r < 0)
return r;
}
@ -451,7 +445,7 @@ int config_parse_many_nulstr(
const char *sections,
ConfigItemLookup lookup,
const void *table,
bool relaxed,
ConfigParseFlags flags,
void *userdata) {
_cleanup_strv_free_ char **files = NULL;
@ -461,8 +455,7 @@ int config_parse_many_nulstr(
if (r < 0)
return r;
return config_parse_many_files(conf_file, files,
sections, lookup, table, relaxed, userdata);
return config_parse_many_files(conf_file, files, sections, lookup, table, flags, userdata);
}
/* Parse each config file in the directories specified as strv. */
@ -473,7 +466,7 @@ int config_parse_many(
const char *sections,
ConfigItemLookup lookup,
const void *table,
bool relaxed,
ConfigParseFlags flags,
void *userdata) {
_cleanup_strv_free_ char **dropin_dirs = NULL;
@ -490,8 +483,7 @@ int config_parse_many(
if (r < 0)
return r;
return config_parse_many_files(conf_file, files,
sections, lookup, table, relaxed, userdata);
return config_parse_many_files(conf_file, files, sections, lookup, table, flags, userdata);
}
#define DEFINE_PARSER(type, vartype, conv_func) \
@ -567,16 +559,17 @@ int config_parse_iec_size(const char* unit,
return 0;
}
int config_parse_si_size(const char* unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
int config_parse_si_size(
const char* unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
size_t *sz = data;
uint64_t v;
@ -597,16 +590,17 @@ int config_parse_si_size(const char* unit,
return 0;
}
int config_parse_iec_uint64(const char* unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
int config_parse_iec_uint64(
const char* unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
uint64_t *bytes = data;
int r;

View File

@ -29,8 +29,14 @@
#include "log.h"
#include "macro.h"
/* An abstract parser for simple, line based, shallow configuration
* files consisting of variable assignments only. */
/* An abstract parser for simple, line based, shallow configuration files consisting of variable assignments only. */
typedef enum ConfigParseFlags {
CONFIG_PARSE_RELAXED = 1U << 0,
CONFIG_PARSE_ALLOW_INCLUDE = 1U << 1,
CONFIG_PARSE_WARN = 1U << 2,
CONFIG_PARSE_REFUSE_BOM = 1U << 3,
} ConfigParseFlags;
/* Prototype for a parser for a specific configuration setting */
typedef int (*ConfigParserCallback)(const char *unit,
@ -91,9 +97,7 @@ int config_parse(
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
bool relaxed,
bool allow_include,
bool warn,
ConfigParseFlags flags,
void *userdata);
int config_parse_many_nulstr(
@ -102,7 +106,7 @@ int config_parse_many_nulstr(
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
bool relaxed,
ConfigParseFlags flags,
void *userdata);
int config_parse_many(
@ -112,7 +116,7 @@ int config_parse_many(
const char *sections, /* nulstr */
ConfigItemLookup lookup,
const void *table,
bool relaxed,
ConfigParseFlags flags,
void *userdata);
/* Generic parsers */

View File

@ -1308,7 +1308,7 @@ static int unit_file_load(
r = config_parse(info->name, path, f,
NULL,
config_item_table_lookup, items,
true, true, false, info);
CONFIG_PARSE_RELAXED|CONFIG_PARSE_ALLOW_INCLUDE, info);
if (r < 0)
return log_debug_errno(r, "Failed to parse %s: %m", info->name);

View File

@ -58,10 +58,10 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
{}
};
config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
"Sleep\0", config_item_table_lookup, items,
false, NULL);
(void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
"Sleep\0", config_item_table_lookup, items,
CONFIG_PARSE_WARN, NULL);
if (streq(verb, "suspend")) {
/* empty by default */

View File

@ -22,6 +22,7 @@
#include "macro.h"
#include "manager.h"
#include "rm-rf.h"
#include "string-util.h"
#include "test-helper.h"
#include "tests.h"
#include "unit.h"
@ -117,10 +118,38 @@ static int test_cgroup_mask(void) {
return 0;
}
static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) {
_cleanup_free_ char *b = NULL;
assert_se(cg_mask_to_string(mask, &b) >= 0);
assert_se(streq_ptr(b, t));
}
static void test_cg_mask_to_string(void) {
test_cg_mask_to_string_one(0, NULL);
test_cg_mask_to_string_one(_CGROUP_MASK_ALL, "cpu cpuacct io blkio memory devices pids");
test_cg_mask_to_string_one(CGROUP_MASK_CPU, "cpu");
test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT, "cpuacct");
test_cg_mask_to_string_one(CGROUP_MASK_IO, "io");
test_cg_mask_to_string_one(CGROUP_MASK_BLKIO, "blkio");
test_cg_mask_to_string_one(CGROUP_MASK_MEMORY, "memory");
test_cg_mask_to_string_one(CGROUP_MASK_DEVICES, "devices");
test_cg_mask_to_string_one(CGROUP_MASK_PIDS, "pids");
test_cg_mask_to_string_one(CGROUP_MASK_CPU|CGROUP_MASK_CPUACCT, "cpu cpuacct");
test_cg_mask_to_string_one(CGROUP_MASK_CPU|CGROUP_MASK_PIDS, "cpu pids");
test_cg_mask_to_string_one(CGROUP_MASK_CPUACCT|CGROUP_MASK_PIDS, "cpuacct pids");
test_cg_mask_to_string_one(CGROUP_MASK_DEVICES|CGROUP_MASK_PIDS, "devices pids");
test_cg_mask_to_string_one(CGROUP_MASK_IO|CGROUP_MASK_BLKIO, "io blkio");
}
int main(int argc, char* argv[]) {
int rc = 0;
log_parse_environment();
log_open();
TEST_REQ_RUNNING_SYSTEMD(rc = test_cgroup_mask());
test_cg_mask_to_string();
return rc;
}

View File

@ -311,7 +311,7 @@ static void test_config_parse(unsigned i, const char *s) {
r = config_parse(NULL, name, f,
"Section\0",
config_item_table_lookup, items,
false, false, true, NULL);
CONFIG_PARSE_WARN, NULL);
switch (i) {
case 0 ... 3:

View File

@ -288,11 +288,47 @@ static void test_endswith_no_case(void) {
}
static void test_delete_chars(void) {
char *r;
char input[] = " hello, waldo. abc";
char *s, input[] = " hello, waldo. abc";
r = delete_chars(input, WHITESPACE);
assert_se(streq(r, "hello,waldo.abc"));
s = delete_chars(input, WHITESPACE);
assert_se(streq(s, "hello,waldo.abc"));
assert_se(s == input);
}
static void test_delete_trailing_chars(void) {
char *s,
input1[] = " \n \r k \n \r ",
input2[] = "kkkkthiskkkiskkkaktestkkk",
input3[] = "abcdef";
s = delete_trailing_chars(input1, WHITESPACE);
assert_se(streq(s, " \n \r k"));
assert_se(s == input1);
s = delete_trailing_chars(input2, "kt");
assert_se(streq(s, "kkkkthiskkkiskkkaktes"));
assert_se(s == input2);
s = delete_trailing_chars(input3, WHITESPACE);
assert_se(streq(s, "abcdef"));
assert_se(s == input3);
s = delete_trailing_chars(input3, "fe");
assert_se(streq(s, "abcd"));
assert_se(s == input3);
}
static void test_skip_leading_chars(void) {
char input1[] = " \n \r k \n \r ",
input2[] = "kkkkthiskkkiskkkaktestkkk",
input3[] = "abcdef";
assert_se(streq(skip_leading_chars(input1, WHITESPACE), "k \n \r "));
assert_se(streq(skip_leading_chars(input2, "k"), "thiskkkiskkkaktestkkk"));
assert_se(streq(skip_leading_chars(input2, "tk"), "hiskkkiskkkaktestkkk"));
assert_se(streq(skip_leading_chars(input3, WHITESPACE), "abcdef"));
assert_se(streq(skip_leading_chars(input3, "bcaef"), "def"));
}
static void test_in_charset(void) {
@ -361,6 +397,8 @@ int main(int argc, char *argv[]) {
test_endswith();
test_endswith_no_case();
test_delete_chars();
test_delete_trailing_chars();
test_skip_leading_chars();
test_in_charset();
test_split_pair();
test_first_word();

View File

@ -114,7 +114,7 @@ int manager_parse_config_file(Manager *m) {
CONF_PATHS_NULSTR("systemd/timesyncd.conf.d"),
"Time\0",
config_item_perf_lookup, timesyncd_gperf_lookup,
false, m);
CONFIG_PARSE_WARN, m);
if (r < 0)
return r;

View File

@ -310,7 +310,7 @@ static int parse_password(const char *filename, char **wall) {
r = config_parse(NULL, filename, NULL,
NULL,
config_item_table_lookup, items,
true, false, true, NULL);
CONFIG_PARSE_RELAXED|CONFIG_PARSE_WARN, NULL);
if (r < 0)
return r;

View File

@ -175,7 +175,7 @@ static int load_link(link_config_ctx *ctx, const char *filename) {
r = config_parse(NULL, filename, file,
"Match\0Link\0Ethernet\0",
config_item_perf_lookup, link_config_gperf_lookup,
false, false, true, link);
CONFIG_PARSE_WARN, link);
if (r < 0)
return r;
else

View File

@ -1967,7 +1967,7 @@ void udev_rules_apply_to_event(struct udev_rules *rules,
} else {
int count;
util_remove_trailing_chars(result, '\n');
delete_trailing_chars(result, "\n");
if (IN_SET(esc, ESCAPE_UNSET, ESCAPE_REPLACE)) {
count = util_replace_chars(result, UDEV_ALLOWED_CHARS_INPUT);
if (count > 0)

View File

@ -85,7 +85,7 @@ static int adm_builtin(struct udev *udev, int argc, char *argv[]) {
strscpyl(filename, sizeof(filename), "/sys", syspath, NULL);
else
strscpy(filename, sizeof(filename), syspath);
util_remove_trailing_chars(filename, '/');
delete_trailing_chars(filename, "/");
dev = udev_device_new_from_syspath(udev, filename);
if (dev == NULL) {

View File

@ -116,7 +116,7 @@ static int adm_test(struct udev *udev, int argc, char *argv[]) {
strscpyl(filename, sizeof(filename), "/sys", syspath, NULL);
else
strscpy(filename, sizeof(filename), syspath);
util_remove_trailing_chars(filename, '/');
delete_trailing_chars(filename, "/");
dev = udev_device_new_from_synthetic_event(udev, filename, action);
if (dev == NULL) {