Merge pull request #8058 from keszybz/sysusers-inline

Extend sysusers for package installation scripts
This commit is contained in:
Yu Watanabe
2018-02-05 16:50:51 +09:00
committed by GitHub
17 changed files with 480 additions and 123 deletions
+64
View File
@@ -154,6 +154,70 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const
return 0;
}
int conf_files_insert(char ***strv, const char *root, const char *dirs, const char *path) {
/* Insert a path into strv, at the place honouring the usual sorting rules:
* - we first compare by the basename
* - and then we compare by dirname, allowing just one file with the given
* basename.
* This means that we will
* - add a new entry if basename(path) was not on the list,
* - do nothing if an entry with higher priority was already present,
* - do nothing if our new entry matches the existing entry,
* - replace the existing entry if our new entry has higher priority.
*/
char *t;
unsigned i;
int r;
for (i = 0; i < strv_length(*strv); i++) {
int c;
c = base_cmp(*strv + i, &path);
if (c == 0) {
const char *dir;
/* Oh, we found our spot and it already contains something. */
NULSTR_FOREACH(dir, dirs) {
char *p1, *p2;
p1 = path_startswith((*strv)[i], root);
if (p1)
/* Skip "/" in dir, because p1 is without "/" too */
p1 = path_startswith(p1, dir + 1);
if (p1)
/* Existing entry with higher priority
* or same priority, no need to do anything. */
return 0;
p2 = path_startswith(path, dir);
if (p2) {
/* Our new entry has higher priority */
t = path_join(root, path, NULL);
if (!t)
return log_oom();
return free_and_replace((*strv)[i], t);
}
}
} else if (c > 0)
/* Following files have lower priority, let's go insert our
* new entry. */
break;
/* … we are not there yet, let's continue */
}
t = path_join(root, path, NULL);
if (!t)
return log_oom();
r = strv_insert(strv, i, t);
if (r < 0)
free(t);
return r;
}
int conf_files_list_strv(char ***strv, const char *suffix, const char *root, unsigned flags, const char* const* dirs) {
_cleanup_strv_free_ char **copy = NULL;
+1
View File
@@ -28,3 +28,4 @@ enum {
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir, ...);
int conf_files_list_strv(char ***ret, const char *suffix, const char *root, unsigned flags, const char* const* dirs);
int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dirs);
int conf_files_insert(char ***strv, const char *root, const char *dirs, const char *path);
+6 -3
View File
@@ -446,7 +446,7 @@ int strv_push_pair(char ***l, char *a, char *b) {
return 0;
}
int strv_push_prepend(char ***l, char *value) {
int strv_insert(char ***l, unsigned position, char *value) {
char **c;
unsigned n, m, i;
@@ -454,6 +454,7 @@ int strv_push_prepend(char ***l, char *value) {
return 0;
n = strv_length(*l);
position = MIN(position, n);
/* increase and check for overflow */
m = n + 2;
@@ -464,10 +465,12 @@ int strv_push_prepend(char ***l, char *value) {
if (!c)
return -ENOMEM;
for (i = 0; i < n; i++)
for (i = 0; i < position; i++)
c[i] = (*l)[i];
c[position] = value;
for (i = position; i < n; i++)
c[i+1] = (*l)[i];
c[0] = value;
c[n+1] = NULL;
free(*l);
+6 -1
View File
@@ -54,7 +54,12 @@ int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
int strv_extend_front(char ***l, const char *value);
int strv_push(char ***l, char *value);
int strv_push_pair(char ***l, char *a, char *b);
int strv_push_prepend(char ***l, char *value);
int strv_insert(char ***l, unsigned position, char *value);
static inline int strv_push_prepend(char ***l, char *value) {
return strv_insert(l, 0, value);
}
int strv_consume(char ***l, char *value);
int strv_consume_pair(char ***l, char *a, char *b);
int strv_consume_prepend(char ***l, char *value);
+6 -4
View File
@@ -553,18 +553,18 @@ int take_etc_passwd_lock(const char *root) {
* awfully racy, and thus we just won't do them. */
if (root)
path = prefix_roota(root, "/etc/.pwd.lock");
path = prefix_roota(root, ETC_PASSWD_LOCK_PATH);
else
path = "/etc/.pwd.lock";
path = ETC_PASSWD_LOCK_PATH;
fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
if (fd < 0)
return -errno;
return log_debug_errno(errno, "Cannot open %s: %m", path);
r = fcntl(fd, F_SETLKW, &flock);
if (r < 0) {
safe_close(fd);
return -errno;
return log_debug_errno(errno, "Locking %s failed: %m", path);
}
return fd;
@@ -645,6 +645,8 @@ bool valid_gecos(const char *d) {
}
bool valid_home(const char *p) {
/* Note that this function is also called by valid_shell(), any
* changes must account for that. */
if (isempty(p))
return false;
+11
View File
@@ -63,6 +63,8 @@ int take_etc_passwd_lock(const char *root);
#define UID_NOBODY ((uid_t) 65534U)
#define GID_NOBODY ((gid_t) 65534U)
#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
static inline bool uid_is_dynamic(uid_t uid) {
return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX;
}
@@ -96,6 +98,15 @@ bool valid_user_group_name_or_id(const char *u);
bool valid_gecos(const char *d);
bool valid_home(const char *p);
static inline bool valid_shell(const char *p) {
/* We have the same requirements, so just piggy-back on the home check.
*
* Let's ignore /etc/shells because this is only applicable to real and
* not system users. It is also incompatible with the idea of empty /etc.
*/
return valid_home(p);
}
int maybe_setgroups(size_t size, const gid_t *list);
bool synthesize_nobody(void);
+19
View File
@@ -100,6 +100,7 @@ journalctl --update-catalog >/dev/null 2>&1 || : \
systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \
%{nil}
# Deprecated. Use %sysusers_create_package instead
%sysusers_create() \
systemd-sysusers %{?*} >/dev/null 2>&1 || : \
%{nil}
@@ -108,6 +109,24 @@ systemd-sysusers %{?*} >/dev/null 2>&1 || : \
echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \
%{nil}
# This should be used by package installation scripts which
# require users or groups to be present before the files installed
# by the package are present on disk (for example because some files
# are owned by those users or groups).
#
# Example:
# Source1: %{name}.conf
# ...
# %install
# install -Dt %{buildroot}%{sysusersdir} %SOURCE1
# %pre
# %sysusers_create_package %{name} %SOURCE1
# %files
# %{sysusersdir}/%{name}.conf
%sysusers_create_package() \
echo "%(cat %2)" | systemd-sysusers --replace=%_sysusersdir/%1.conf - >/dev/null 2>&1 || : \
%{nil}
%sysctl_apply() \
@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \
%{nil}
+154 -52
View File
@@ -59,14 +59,18 @@ typedef struct Item {
char *gid_path;
char *description;
char *home;
char *shell;
gid_t gid;
uid_t uid;
bool gid_set:1;
// id_set_strict means that the group with the specified gid must
// exist and that the check if a uid clashes with a gid is skipped
/* When set the group with the specified gid must exist
* and the check if a uid clashes with the gid is skipped.
*/
bool id_set_strict:1;
bool uid_set:1;
bool todo_user:1;
@@ -74,6 +78,8 @@ typedef struct Item {
} Item;
static char *arg_root = NULL;
static const char *arg_replace = NULL;
static bool arg_inline = false;
static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d");
@@ -383,6 +389,10 @@ static int rename_and_apply_smack(const char *temp_path, const char *dest_path)
return r;
}
static const char* default_shell(uid_t uid) {
return uid == 0 ? "/bin/sh" : "/sbin/nologin";
}
static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
_cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
_cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
@@ -450,7 +460,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
/* Initialize the shell to nologin, with one exception:
* for root we patch in something special */
.pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
.pw_shell = i->shell ?: (char*) default_shell(i->uid),
};
errno = 0;
@@ -1224,6 +1234,7 @@ static void item_free(Item *i) {
free(i->gid_path);
free(i->description);
free(i->home);
free(i->shell);
free(i);
}
@@ -1331,6 +1342,9 @@ static bool item_equal(Item *a, Item *b) {
if (!streq_ptr(a->home, b->home))
return false;
if (!streq_ptr(a->shell, b->shell))
return false;
return true;
}
@@ -1344,7 +1358,12 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
{}
};
_cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
_cleanup_free_ char *action = NULL,
*name = NULL, *resolved_name = NULL,
*id = NULL, *resolved_id = NULL,
*description = NULL,
*home = NULL,
*shell, *resolved_shell = NULL;
_cleanup_(item_freep) Item *i = NULL;
Item *existing;
OrderedHashmap *h;
@@ -1357,7 +1376,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
/* Parse columns */
p = buffer;
r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL);
r = extract_many_words(&p, NULL, EXTRACT_QUOTES,
&action, &name, &id, &description, &home, &shell, NULL);
if (r < 0) {
log_error("[%s:%u] Syntax error.", fname, line);
return r;
@@ -1433,6 +1453,24 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
}
/* Verify shell */
if (isempty(shell) || streq(shell, "-"))
shell = mfree(shell);
if (shell) {
r = specifier_printf(shell, specifier_table, NULL, &resolved_shell);
if (r < 0) {
log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, shell);
return r;
}
if (!valid_shell(resolved_shell)) {
log_error("[%s:%u] '%s' is not a valid login shell field.", fname, line, resolved_shell);
return -EINVAL;
}
}
switch (action[0]) {
case ADD_RANGE:
@@ -1446,13 +1484,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@@ -1483,13 +1518,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@@ -1573,6 +1605,9 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
i->home = home;
home = NULL;
i->shell = resolved_shell;
resolved_shell = NULL;
h = users;
break;
@@ -1582,13 +1617,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
return -EINVAL;
}
if (description) {
log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
return -EINVAL;
}
if (home) {
log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line);
if (description || home || shell) {
log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
fname, line, action[0],
description ? "GECOS" : home ? "home directory" : "login shell");
return -EINVAL;
}
@@ -1718,6 +1750,8 @@ static void help(void) {
" -h --help Show this help\n"
" --version Show package version\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --replace=PATH Treat arguments as replacement for PATH\n"
" --inline Treat arguments as configuration lines\n"
, program_invocation_short_name);
}
@@ -1726,12 +1760,16 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_ROOT,
ARG_REPLACE,
ARG_INLINE,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "root", required_argument, NULL, ARG_ROOT },
{ "replace", required_argument, NULL, ARG_REPLACE },
{ "inline", no_argument, NULL, ARG_INLINE },
{}
};
@@ -1757,6 +1795,20 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_REPLACE:
if (!path_is_absolute(optarg) ||
!endswith(optarg, ".conf")) {
log_error("The argument to --replace= must an absolute path to a config file");
return -EINVAL;
}
arg_replace = optarg;
break;
case ARG_INLINE:
arg_inline = true;
break;
case '?':
return -EINVAL;
@@ -1764,14 +1816,76 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached("Unhandled option");
}
if (arg_replace && optind >= argc) {
log_error("When --replace= is given, some configuration items must be specified");
return -EINVAL;
}
return 1;
}
static int parse_arguments(char **args) {
char **arg;
unsigned pos = 1;
int r;
STRV_FOREACH(arg, args) {
if (arg_inline)
/* Use (argument):n, where n==1 for the first positional arg */
r = parse_line("(argument)", pos, *arg);
else
r = read_config_file(*arg, false);
if (r < 0)
return r;
pos++;
}
return 0;
}
static int read_config_files(const char* dirs, char **args) {
_cleanup_strv_free_ char **files = NULL;
_cleanup_free_ char *p = NULL;
char **f;
int r;
r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, dirs);
if (r < 0)
return log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
if (arg_replace) {
r = conf_files_insert(&files, arg_root, dirs, arg_replace);
if (r < 0)
return log_error_errno(r, "Failed to extend sysusers.d file list: %m");
p = path_join(arg_root, arg_replace, NULL);
if (!p)
return log_oom();
}
STRV_FOREACH(f, files)
if (p && path_equal(*f, p)) {
log_debug("Parsing arguments at position \"%s\"…", *f);
r = parse_arguments(args);
if (r < 0)
return r;
} else {
log_debug("Reading config file \"%s\"…", *f);
/* Just warn, ignore result otherwise */
(void) read_config_file(*f, true);
}
return 0;
}
int main(int argc, char *argv[]) {
_cleanup_close_ int lock = -1;
Iterator iterator;
int r, k;
int r;
Item *i;
char *n;
@@ -1791,30 +1905,18 @@ int main(int argc, char *argv[]) {
goto finish;
}
if (optind < argc) {
int j;
for (j = optind; j < argc; j++) {
k = read_config_file(argv[j], false);
if (k < 0 && r == 0)
r = k;
}
} else {
_cleanup_strv_free_ char **files = NULL;
char **f;
r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, conf_file_dirs);
if (r < 0) {
log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
goto finish;
}
STRV_FOREACH(f, files) {
k = read_config_file(*f, true);
if (k < 0 && r == 0)
r = k;
}
}
/* If command line arguments are specified along with --replace, read all
* configuration files and insert the positional arguments at the specified
* place. Otherwise, if command line arguments are specified, execute just
* them, and finally, without --replace= or any positional arguments, just
* read configuration and execute it.
*/
if (arg_replace || optind >= argc)
r = read_config_files(conf_file_dirs, argv + optind);
else
r = parse_arguments(argv + optind);
if (r < 0)
goto finish;
/* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
* whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
@@ -1841,7 +1943,7 @@ int main(int argc, char *argv[]) {
lock = take_etc_passwd_lock(arg_root);
if (lock < 0) {
log_error_errno(lock, "Failed to take lock: %m");
log_error_errno(lock, "Failed to take /etc/passwd lock: %m");
goto finish;
}
+31
View File
@@ -447,6 +447,36 @@ static void test_strv_from_stdarg_alloca(void) {
test_strv_from_stdarg_alloca_one(STRV_MAKE_EMPTY, NULL);
}
static void test_strv_insert(void) {
_cleanup_strv_free_ char **a = NULL;
assert_se(strv_insert(&a, 0, strdup("first")) == 0);
assert_se(streq(a[0], "first"));
assert_se(!a[1]);
assert_se(strv_insert(&a, 0, NULL) == 0);
assert_se(streq(a[0], "first"));
assert_se(!a[1]);
assert_se(strv_insert(&a, 1, strdup("two")) == 0);
assert_se(streq(a[0], "first"));
assert_se(streq(a[1], "two"));
assert_se(!a[2]);
assert_se(strv_insert(&a, 4, strdup("tri")) == 0);
assert_se(streq(a[0], "first"));
assert_se(streq(a[1], "two"));
assert_se(streq(a[2], "tri"));
assert_se(!a[3]);
assert_se(strv_insert(&a, 1, strdup("duo")) == 0);
assert_se(streq(a[0], "first"));
assert_se(streq(a[1], "duo"));
assert_se(streq(a[2], "two"));
assert_se(streq(a[3], "tri"));
assert_se(!a[4]);
}
static void test_strv_push_prepend(void) {
_cleanup_strv_free_ char **a = NULL;
@@ -723,6 +753,7 @@ int main(int argc, char *argv[]) {
test_strv_extend();
test_strv_extendf();
test_strv_from_stdarg_alloca();
test_strv_insert();
test_strv_push_prepend();
test_strv_push();
test_strv_equal();