+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
#include <string.h>
#include <sys/stat.h>
#include <sys/xattr.h>
+#include <sysexits.h>
#include <time.h>
#include <unistd.h>
char *path;
char *argument;
char **xattrs;
-#ifdef HAVE_ACL
+#if HAVE_ACL
acl_t acl_access;
acl_t acl_default;
#endif
static OrderedHashmap *items = NULL, *globs = NULL;
static Set *unix_sockets = NULL;
+static int specifier_machine_id_safe(char specifier, void *data, void *userdata, char **ret);
+
static const Specifier specifier_table[] = {
- { 'm', specifier_machine_id, NULL },
+ { 'm', specifier_machine_id_safe, NULL },
{ 'b', specifier_boot_id, NULL },
{ 'H', specifier_host_name, NULL },
{ 'v', specifier_kernel_release, NULL },
+
+ { 'U', specifier_user_id, NULL },
+ { 'u', specifier_user_name, NULL },
+ { 'h', specifier_user_home, NULL },
{}
};
+static int specifier_machine_id_safe(char specifier, void *data, void *userdata, char **ret) {
+ int r;
+
+ /* If /etc/machine_id is missing (e.g. in a chroot environment), returns
+ * a recognizable error so that the caller can skip the rule
+ * gracefully. */
+
+ r = specifier_machine_id(specifier, data, userdata, ret);
+ if (r == -ENOENT)
+ return -ENOKEY;
+
+ return r;
+}
+
+static int log_unresolvable_specifier(const char *filename, unsigned line) {
+ static bool notified = false;
+
+ /* This is called when /etc is not fully initialized (e.g. in a chroot
+ * environment) where some specifiers are unresolvable. These cases are
+ * not considered as an error so log at LOG_NOTICE only for the first
+ * time and then downgrade this to LOG_DEBUG for the rest. */
+
+ log_full(notified ? LOG_DEBUG : LOG_NOTICE,
+ "[%s:%u] Failed to resolve specifier: uninitialized /etc detected, skipping",
+ filename, line);
+
+ if (!notified)
+ log_notice("All rules containing unresolvable specifiers will be skipped.");
+
+ notified = true;
+ return 0;
+}
+
static bool needs_glob(ItemType t) {
return IN_SET(t,
WRITE_FILE,
static int dir_is_mount_point(DIR *d, const char *subdir) {
- union file_handle_union h = FILE_HANDLE_INIT;
int mount_id_parent, mount_id;
int r_p, r;
- r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0);
+ r_p = name_to_handle_at_loop(dirfd(d), ".", NULL, &mount_id_parent, 0);
if (r_p < 0)
r_p = -errno;
- h.handle.handle_bytes = MAX_HANDLE_SZ;
- r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0);
+ r = name_to_handle_at_loop(dirfd(d), subdir, NULL, &mount_id, 0);
if (r < 0)
r = -errno;
/* got only one handle; assume different mount points if one
* of both queries was not supported by the filesystem */
- if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP)
+ if (IN_SET(r_p, -ENOSYS, -EOPNOTSUPP) || IN_SET(r, -ENOSYS, -EOPNOTSUPP))
return true;
/* return error */
log_debug("Removing directory \"%s\".", sub_path);
if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0)
- if (errno != ENOENT && errno != ENOTEMPTY) {
+ if (!IN_SET(errno, ENOENT, ENOTEMPTY)) {
log_error_errno(errno, "rmdir(%s): %m", sub_path);
r = -errno;
}
* O_PATH. */
fd = open(path, O_NOFOLLOW|O_CLOEXEC|O_PATH);
- if (fd < 0)
- return log_error_errno(errno, "Adjusting owner and mode for %s failed: %m", path);
+ if (fd < 0) {
+ int level = LOG_ERR, r = -errno;
+
+ /* Option "e" operates only on existing objects. Do not
+ * print errors about non-existent files or directories */
+ if (i->type == EMPTY_DIRECTORY && errno == ENOENT) {
+ level = LOG_DEBUG;
+ r = 0;
+ }
+
+ log_full_errno(level, errno, "Adjusting owner and mode for %s failed: %m", path);
+
+ return r;
+ }
if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0)
return log_error_errno(errno, "Failed to fstat() file %s: %m", path);
if (S_ISLNK(st.st_mode))
- log_debug("Skipping mode an owner fix for symlink %s.", path);
+ log_debug("Skipping mode and owner fix for symlink %s.", path);
else {
char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
xsprintf(fn, "/proc/self/fd/%i", fd);
p = i->argument;
for (;;) {
- _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL;
+ _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL;
r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE);
if (r < 0)
if (r <= 0)
break;
- r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced);
- if (r < 0)
- return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr);
-
- r = split_pair(xattr_replaced, "=", &name, &value);
+ r = split_pair(xattr, "=", &name, &value);
if (r < 0) {
log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr);
continue;
n = strlen(*value);
log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path);
- if (lsetxattr(path, *name, *value, n, 0) < 0) {
- log_error("Setting extended attribute %s=%s on %s failed: %m", *name, *value, path);
- return -errno;
- }
+ if (lsetxattr(path, *name, *value, n, 0) < 0)
+ return log_error_errno(errno, "Setting extended attribute %s=%s on %s failed: %m",
+ *name, *value, path);
}
return 0;
}
static int parse_acls_from_arg(Item *item) {
-#ifdef HAVE_ACL
+#if HAVE_ACL
int r;
assert(item);
return 0;
}
-#ifdef HAVE_ACL
+#if HAVE_ACL
static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) {
_cleanup_(acl_free_charpp) char *t = NULL;
_cleanup_(acl_freep) acl_t dup = NULL;
static int path_set_acls(Item *item, const char *path) {
int r = 0;
-#ifdef HAVE_ACL
+#if HAVE_ACL
char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
_cleanup_close_ int fd = -1;
struct stat st;
{ 's', FS_SECRM_FL }, /* Secure deletion */
{ 'u', FS_UNRM_FL }, /* Undelete */
{ 't', FS_NOTAIL_FL }, /* file tail should not be merged */
- { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies*/
+ { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies */
{ 'C', FS_NOCOW_FL }, /* Do not cow file */
};
v = attributes[i].value;
- SET_FLAG(value, v, (mode == MODE_ADD || mode == MODE_SET));
+ SET_FLAG(value, v, IN_SET(mode, MODE_ADD, MODE_SET));
mask |= v;
}
r = chattr_fd(fd, f, item->attribute_mask);
if (r < 0)
- log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING,
+ log_full_errno(IN_SET(r, -ENOTTY, -EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING,
r,
"Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m",
path, item->attribute_value, item->attribute_mask);
}
if (i->argument) {
- _cleanup_free_ char *unescaped = NULL, *replaced = NULL;
-
log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path);
- r = cunescape(i->argument, 0, &unescaped);
- if (r < 0)
- return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument);
-
- r = specifier_printf(unescaped, specifier_table, NULL, &replaced);
- if (r < 0)
- return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped);
-
- r = loop_write(fd, replaced, strlen(replaced), false);
+ r = loop_write(fd, i->argument, strlen(i->argument), false);
if (r < 0)
return log_error_errno(r, "Failed to write file \"%s\": %m", path);
} else
d = opendir_nomod(path);
if (!d)
- return errno == ENOENT || errno == ENOTDIR ? 0 : -errno;
+ return IN_SET(errno, ENOENT, ENOTDIR, ELOOP) ? 0 : -errno;
FOREACH_DIRENT_ALL(de, d, r = -errno) {
_cleanup_free_ char *p = NULL;
static int glob_item(Item *i, action_t action, bool recursive) {
_cleanup_globfree_ glob_t g = {
- .gl_closedir = (void (*)(void *)) closedir,
- .gl_readdir = (struct dirent *(*)(void *)) readdir,
.gl_opendir = (void *(*)(const char *)) opendir_nomod,
- .gl_lstat = lstat,
- .gl_stat = stat,
};
int r = 0, k;
char **fn;
- errno = 0;
- k = glob(i->path, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g);
- if (k != 0 && k != GLOB_NOMATCH)
- return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path);
+ k = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g);
+ if (k < 0 && k != -ENOENT)
+ return log_error_errno(k, "glob(%s) failed: %m", i->path);
STRV_FOREACH(fn, g.gl_pathv) {
k = action(i, *fn);
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode);
static int create_item(Item *i) {
- _cleanup_free_ char *resolved = NULL;
struct stat st;
int r = 0;
int q = 0;
break;
case COPY_FILES: {
- r = specifier_printf(i->argument, specifier_table, NULL, &resolved);
- if (r < 0)
- return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument);
-
- log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path);
- r = copy_tree(resolved, i->path, i->uid_set ? i->uid : UID_INVALID, i->gid_set ? i->gid : GID_INVALID, COPY_REFLINK);
+ log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path);
+ r = copy_tree(i->argument, i->path, i->uid_set ? i->uid : UID_INVALID, i->gid_set ? i->gid : GID_INVALID, COPY_REFLINK);
if (r == -EROFS && stat(i->path, &st) == 0)
r = -EEXIST;
if (r != -EEXIST)
return log_error_errno(r, "Failed to copy files to %s: %m", i->path);
- if (stat(resolved, &a) < 0)
- return log_error_errno(errno, "stat(%s) failed: %m", resolved);
+ if (stat(i->argument, &a) < 0)
+ return log_error_errno(errno, "stat(%s) failed: %m", i->argument);
if (stat(i->path, &b) < 0)
return log_error_errno(errno, "stat(%s) failed: %m", i->path);
if (r < 0) {
int k;
- if (r != -EEXIST && r != -EROFS)
+ if (!IN_SET(r, -EEXIST, -EROFS))
return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path);
k = is_dir(i->path, false);
log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path);
}
- /* fall through */
-
+ _fallthrough_;
case EMPTY_DIRECTORY:
r = path_set_perms(i, i->path);
if (q < 0)
}
case CREATE_SYMLINK: {
- r = specifier_printf(i->argument, specifier_table, NULL, &resolved);
- if (r < 0)
- return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument);
-
mac_selinux_create_file_prepare(i->path, S_IFLNK);
- r = symlink(resolved, i->path);
+ r = symlink(i->argument, i->path);
mac_selinux_create_file_clear();
if (r < 0) {
_cleanup_free_ char *x = NULL;
if (errno != EEXIST)
- return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path);
+ return log_error_errno(errno, "symlink(%s, %s) failed: %m", i->argument, i->path);
r = readlink_malloc(i->path, &x);
- if (r < 0 || !streq(resolved, x)) {
+ if (r < 0 || !streq(i->argument, x)) {
if (i->force) {
mac_selinux_create_file_prepare(i->path, S_IFLNK);
- r = symlink_atomic(resolved, i->path);
+ r = symlink_atomic(i->argument, i->path);
mac_selinux_create_file_clear();
+ if (IN_SET(r, -EEXIST, -ENOTEMPTY)) {
+ r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL);
+ if (r < 0)
+ return log_error_errno(r, "rm -fr %s failed: %m", i->path);
+
+ mac_selinux_create_file_prepare(i->path, S_IFLNK);
+ r = symlink(i->argument, i->path) < 0 ? -errno : 0;
+ mac_selinux_create_file_clear();
+ }
if (r < 0)
- return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path);
+ return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path);
creation = CREATION_FORCE;
} else {
case CREATE_SUBVOLUME:
case CREATE_SUBVOLUME_INHERIT_QUOTA:
case CREATE_SUBVOLUME_NEW_QUOTA:
- case EMPTY_DIRECTORY:
case TRUNCATE_DIRECTORY:
case IGNORE_PATH:
case COPY_FILES:
clean_item_instance(i, i->path);
return 0;
+ case EMPTY_DIRECTORY:
case IGNORE_DIRECTORY_PATH:
return glob_item(i, clean_item_instance, false);
default:
}
}
+ if (chase_symlinks(i->path, NULL, CHASE_NO_AUTOFS, NULL) == -EREMOTE)
+ return t;
+
r = arg_create ? create_item(i) : 0;
q = arg_remove ? remove_item(i) : 0;
p = arg_clean ? clean_item(i) : 0;
free(i->argument);
strv_free(i->xattrs);
-#ifdef HAVE_ACL
+#if HAVE_ACL
acl_free(i->acl_access);
acl_free(i->acl_default);
#endif
/* no matches, so we should include this path only if we
* have no whitelist at all */
- if (strv_length(arg_include_prefixes) == 0)
+ if (strv_isempty(arg_include_prefixes))
return true;
log_debug("Entry \"%s\" does not match any include prefix, skipping.", path);
return false;
}
-static int parse_line(const char *fname, unsigned line, const char *buffer) {
+static int specifier_expansion_from_arg(Item *i) {
+ _cleanup_free_ char *unescaped = NULL, *resolved = NULL;
+ char **xattr;
+ int r;
+
+ assert(i);
+
+ if (i->argument == NULL)
+ return 0;
+
+ switch (i->type) {
+ case COPY_FILES:
+ case CREATE_SYMLINK:
+ case CREATE_FILE:
+ case TRUNCATE_FILE:
+ case WRITE_FILE:
+ r = cunescape(i->argument, 0, &unescaped);
+ if (r < 0)
+ return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument);
+
+ r = specifier_printf(unescaped, specifier_table, NULL, &resolved);
+ if (r < 0)
+ return r;
+
+ free_and_replace(i->argument, resolved);
+ break;
+
+ case SET_XATTR:
+ case RECURSIVE_SET_XATTR:
+ assert(i->xattrs);
+
+ STRV_FOREACH (xattr, i->xattrs) {
+ r = specifier_printf(*xattr, specifier_table, NULL, &resolved);
+ if (r < 0)
+ return r;
+
+ free_and_replace(*xattr, resolved);
+ }
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config) {
_cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
_cleanup_(item_free_contents) Item i = {};
&group,
&age,
NULL);
- if (r < 0)
+ if (r < 0) {
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ /* invalid quoting and such or an unknown specifier */
+ *invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line);
+ }
+
else if (r < 2) {
+ *invalid_config = true;
log_error("[%s:%u] Syntax error.", fname, line);
return -EIO;
}
}
if (isempty(action)) {
+ *invalid_config = true;
log_error("[%s:%u] Command too short '%s'.", fname, line, action);
return -EINVAL;
}
else if (action[pos] == '+' && !force)
force = true;
else {
+ *invalid_config = true;
log_error("[%s:%u] Unknown modifiers in command '%s'",
fname, line, action);
return -EINVAL;
i.force = force;
r = specifier_printf(path, specifier_table, NULL, &i.path);
+ if (r == -ENOKEY)
+ return log_unresolvable_specifier(fname, line);
if (r < 0) {
- log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path);
- return r;
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ *invalid_config = true;
+ return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, path);
}
switch (i.type) {
case WRITE_FILE:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Write file requires argument.", fname, line);
return -EBADMSG;
}
if (!i.argument)
return log_oom();
} else if (!path_is_absolute(i.argument)) {
+ *invalid_config = true;
log_error("[%s:%u] Source path is not absolute.", fname, line);
return -EBADMSG;
}
unsigned major, minor;
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Device file requires argument.", fname, line);
return -EBADMSG;
}
if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
+ *invalid_config = true;
log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
return -EBADMSG;
}
case SET_XATTR:
case RECURSIVE_SET_XATTR:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Set extended attribute requires argument.", fname, line);
return -EBADMSG;
}
case SET_ACL:
case RECURSIVE_SET_ACL:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Set ACLs requires argument.", fname, line);
return -EBADMSG;
}
case SET_ATTRIBUTE:
case RECURSIVE_SET_ATTRIBUTE:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Set file attribute requires argument.", fname, line);
return -EBADMSG;
}
r = parse_attribute_from_arg(&i);
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ *invalid_config = true;
if (r < 0)
return r;
break;
default:
log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type);
+ *invalid_config = true;
return -EBADMSG;
}
if (!path_is_absolute(i.path)) {
log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path);
+ *invalid_config = true;
return -EBADMSG;
}
if (!should_include_path(i.path))
return 0;
+ r = specifier_expansion_from_arg(&i);
+ if (r == -ENOKEY)
+ return log_unresolvable_specifier(fname, line);
+ if (r < 0) {
+ if (IN_SET(r, -EINVAL, -EBADSLT))
+ *invalid_config = true;
+ return log_error_errno(r, "[%s:%u] Failed to substitute specifiers in argument: %m",
+ fname, line);
+ }
+
if (arg_root) {
char *p;
r = get_user_creds(&u, &i.uid, NULL, NULL, NULL);
if (r < 0) {
- log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
- return r;
+ *invalid_config = true;
+ return log_error_errno(r, "[%s:%u] Unknown user '%s'.", fname, line, user);
}
i.uid_set = true;
r = get_group_creds(&g, &i.gid);
if (r < 0) {
+ *invalid_config = true;
log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
return r;
}
}
if (parse_mode(mm, &m) < 0) {
+ *invalid_config = true;
log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
return -EBADMSG;
}
}
if (parse_sec(a, &i.age) < 0) {
+ *invalid_config = true;
log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
return -EBADMSG;
}
for (n = 0; n < existing->count; n++) {
if (!item_compatible(existing->items + n, &i)) {
- log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.",
- fname, line, i.path);
+ log_notice("[%s:%u] Duplicate line for path \"%s\", ignoring.",
+ fname, line, i.path);
return 0;
}
}
" --boot Execute actions only safe at boot\n"
" --prefix=PATH Only apply rules with the specified prefix\n"
" --exclude-prefix=PATH Ignore rules with the specified prefix\n"
- " --root=PATH Operate on an alternate filesystem root\n",
- program_invocation_short_name);
+ " --root=PATH Operate on an alternate filesystem root\n"
+ , program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
return 1;
}
-static int read_config_file(const char *fn, bool ignore_enoent) {
+static int read_config_file(const char *fn, bool ignore_enoent, bool *invalid_config) {
_cleanup_fclose_ FILE *_f = NULL;
FILE *f;
char line[LINE_MAX];
FOREACH_LINE(line, f, break) {
char *l;
int k;
+ bool invalid_line = false;
v++;
l = strstrip(line);
- if (*l == '#' || *l == 0)
+ if (IN_SET(*l, 0, '#'))
continue;
- k = parse_line(fn, v, l);
- if (k < 0 && r == 0)
- r = k;
+ k = parse_line(fn, v, l, &invalid_line);
+ if (k < 0) {
+ if (invalid_line)
+ /* Allow reporting with a special code if the caller requested this */
+ *invalid_config = true;
+ else if (r == 0)
+ /* The first error becomes our return value */
+ r = k;
+ }
}
/* we have to determine age parameter for each entry of type X */
int r, k;
ItemArray *a;
Iterator iterator;
+ bool invalid_config = false;
r = parse_argv(argc, argv);
if (r <= 0)
int j;
for (j = optind; j < argc; j++) {
- k = read_config_file(argv[j], false);
+ k = read_config_file(argv[j], false, &invalid_config);
if (k < 0 && r == 0)
r = k;
}
_cleanup_strv_free_ char **files = NULL;
char **f;
- r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
+ r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, conf_file_dirs);
if (r < 0) {
log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m");
goto finish;
}
STRV_FOREACH(f, files) {
- k = read_config_file(*f, true);
+ k = read_config_file(*f, true, &invalid_config);
if (k < 0 && r == 0)
r = k;
}
}
finish:
- while ((a = ordered_hashmap_steal_first(items)))
- item_array_free(a);
-
- while ((a = ordered_hashmap_steal_first(globs)))
- item_array_free(a);
-
- ordered_hashmap_free(items);
- ordered_hashmap_free(globs);
+ ordered_hashmap_free_with_destructor(items, item_array_free);
+ ordered_hashmap_free_with_destructor(globs, item_array_free);
free(arg_include_prefixes);
free(arg_exclude_prefixes);
mac_selinux_finish();
- return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+ if (r < 0)
+ return EXIT_FAILURE;
+ else if (invalid_config)
+ return EX_DATAERR;
+ else
+ return EXIT_SUCCESS;
}