diff --git a/man/sysusers.d.xml b/man/sysusers.d.xml
index 2e93715be6..72d8f62399 100644
--- a/man/sysusers.d.xml
+++ b/man/sysusers.d.xml
@@ -101,8 +101,8 @@ u root 0 "Superuser" /root /bin/zshu
Create a system user and group of the specified name should
they not exist yet. The user's primary group will be set to the group
- bearing the same name. The account will be created disabled, so that logins
- are not allowed.
+ bearing the same name unless the ID field specifies it. The account will be
+ created disabled, so that logins are not allowed.
@@ -166,9 +166,10 @@ u root 0 "Superuser" /root /bin/zshuid:gid is also supported to
- allow creating user and group pairs with different numeric UID and GID values. The group with the indicated GID must get created explicitly before or it must already exist. Specifying - for the UID in this syntax
- is also supported.
+ The syntaxes uid:gid and
+ uid:groupname are supported to
+ allow creating users with specific primary groups. The given group must be created explicitly, or it
+ must already exist. Specifying - for the UID in these syntaxes is also supported.
For m lines, this field should contain
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
index 08a2df707b..2771fd959f 100644
--- a/src/sysusers/sysusers.c
+++ b/src/sysusers/sysusers.c
@@ -39,6 +39,7 @@ typedef struct Item {
ItemType type;
char *name;
+ char *group_name;
char *uid_path;
char *gid_path;
char *description;
@@ -1085,18 +1086,15 @@ static int gid_is_ok(gid_t gid) {
return 1;
}
-static int add_group(Item *i) {
+static int get_gid_by_name(const char *name, gid_t *gid) {
void *z;
- int r;
- assert(i);
+ assert(gid);
/* Check the database directly */
- z = hashmap_get(database_by_groupname, i->name);
+ z = hashmap_get(database_by_groupname, name);
if (z) {
- log_debug("Group %s already exists.", i->name);
- i->gid = PTR_TO_GID(z);
- i->gid_set = true;
+ *gid = PTR_TO_GID(z);
return 0;
}
@@ -1105,15 +1103,30 @@ static int add_group(Item *i) {
struct group *g;
errno = 0;
- g = getgrnam(i->name);
+ g = getgrnam(name);
if (g) {
- log_debug("Group %s already exists.", i->name);
- i->gid = g->gr_gid;
- i->gid_set = true;
+ *gid = g->gr_gid;
return 0;
}
if (!IN_SET(errno, 0, ENOENT))
- return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name);
+ return log_error_errno(errno, "Failed to check if group %s already exists: %m", name);
+ }
+
+ return -ENOENT;
+}
+
+static int add_group(Item *i) {
+ int r;
+
+ assert(i);
+
+ r = get_gid_by_name(i->name, &i->gid);
+ if (r != -ENOENT) {
+ if (r < 0)
+ return r;
+ log_debug("Group %s already exists.", i->name);
+ i->gid_set = true;
+ return 0;
}
/* Try to use the suggested numeric gid */
@@ -1214,14 +1227,22 @@ static int process_item(Item *i) {
case ADD_USER: {
Item *j;
- j = ordered_hashmap_get(groups, i->name);
+ j = ordered_hashmap_get(groups, i->group_name ?: i->name);
if (j && j->todo_group) {
- /* When the group with the same name is already in queue,
+ /* When a group with the target name is already in queue,
* use the information about the group and do not create
* duplicated group entry. */
i->gid_set = j->gid_set;
i->gid = j->gid;
i->id_set_strict = true;
+ } else if (i->group_name) {
+ /* When a group name was given instead of a GID and it's
+ * not in queue, then it must already exist. */
+ r = get_gid_by_name(i->group_name, &i->gid);
+ if (r < 0)
+ return log_error_errno(r, "Group %s not found.", i->group_name);
+ i->gid_set = true;
+ i->id_set_strict = true;
} else {
r = add_group(i);
if (r < 0)
@@ -1244,6 +1265,7 @@ static Item* item_free(Item *i) {
return NULL;
free(i->name);
+ free(i->group_name);
free(i->uid_path);
free(i->gid_path);
free(i->description);
@@ -1560,10 +1582,15 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
_cleanup_free_ char *uid = NULL, *gid = NULL;
if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
r = parse_gid(gid, &i->gid);
- if (r < 0)
- return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
- i->gid_set = true;
- i->id_set_strict = true;
+ if (r < 0) {
+ if (valid_user_group_name(gid))
+ i->group_name = TAKE_PTR(gid);
+ else
+ return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
+ } else {
+ i->gid_set = true;
+ i->id_set_strict = true;
+ }
free_and_replace(resolved_id, uid);
}
if (!streq(resolved_id, "-")) {
diff --git a/test/TEST-21-SYSUSERS/test-13.expected-group b/test/TEST-21-SYSUSERS/test-13.expected-group
new file mode 100644
index 0000000000..c78ea54bd3
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-13.expected-group
@@ -0,0 +1,5 @@
+hoge:x:300:
+baz:x:302:
+yyy:x:SYSTEM_GID_MAX:
+foo:x:301:
+ccc:x:305:
diff --git a/test/TEST-21-SYSUSERS/test-13.expected-passwd b/test/TEST-21-SYSUSERS/test-13.expected-passwd
new file mode 100644
index 0000000000..ffc20a8193
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-13.expected-passwd
@@ -0,0 +1,5 @@
+foo:x:301:301::/:NOLOGIN
+aaa:x:303:302::/:NOLOGIN
+bbb:x:304:302::/:NOLOGIN
+ccc:x:305:305::/:NOLOGIN
+zzz:x:306:SYSTEM_GID_MAX::/:NOLOGIN
diff --git a/test/TEST-21-SYSUSERS/test-13.input b/test/TEST-21-SYSUSERS/test-13.input
new file mode 100644
index 0000000000..bad2f09505
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-13.input
@@ -0,0 +1,13 @@
+# Ensure that the semantic for the uid:groupname syntax is correct
+#
+#Type Name ID GECOS HOMEDIR
+g hoge 300 - -
+u foo 301 - -
+
+g baz 302 - -
+u aaa 303:baz - -
+u bbb 304:baz - -
+u ccc 305 - -
+
+g yyy -
+u zzz 306:yyy
diff --git a/test/TEST-21-SYSUSERS/test-14.expected-group b/test/TEST-21-SYSUSERS/test-14.expected-group
new file mode 100644
index 0000000000..2e619bc5cd
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-14.expected-group
@@ -0,0 +1 @@
+pre:x:987:
diff --git a/test/TEST-21-SYSUSERS/test-14.expected-passwd b/test/TEST-21-SYSUSERS/test-14.expected-passwd
new file mode 100644
index 0000000000..62ed4f50af
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-14.expected-passwd
@@ -0,0 +1 @@
+aaa:x:SYSTEM_UID_MAX:987::/:NOLOGIN
diff --git a/test/TEST-21-SYSUSERS/test-14.initial-group b/test/TEST-21-SYSUSERS/test-14.initial-group
new file mode 100644
index 0000000000..2e619bc5cd
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-14.initial-group
@@ -0,0 +1 @@
+pre:x:987:
diff --git a/test/TEST-21-SYSUSERS/test-14.input b/test/TEST-21-SYSUSERS/test-14.input
new file mode 100644
index 0000000000..0a11a2e325
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/test-14.input
@@ -0,0 +1,4 @@
+# Ensure that a preexisting system group can be used as primary
+#
+#Type Name ID GECOS HOMEDIR
+u aaa -:pre
diff --git a/test/TEST-21-SYSUSERS/test.sh b/test/TEST-21-SYSUSERS/test.sh
index add16ea19f..aed921e39e 100755
--- a/test/TEST-21-SYSUSERS/test.sh
+++ b/test/TEST-21-SYSUSERS/test.sh
@@ -23,6 +23,7 @@ preprocess() {
# get this value from config.h, however the autopkgtest fails with
# it
SYSTEM_UID_MAX=$(awk 'BEGIN { uid=999 } /^\s*SYS_UID_MAX\s+/ { uid=$2 } END { print uid }' /etc/login.defs)
+ SYSTEM_GID_MAX=$(awk 'BEGIN { gid=999 } /^\s*SYS_GID_MAX\s+/ { gid=$2 } END { print gid }' /etc/login.defs)
# we can't rely on config.h to get the nologin path, as autopkgtest
# uses pre-compiled binaries, so extract it from the systemd-sysusers
@@ -30,6 +31,7 @@ preprocess() {
NOLOGIN=$(strings $(type -p systemd-sysusers) | grep nologin)
sed -e "s/SYSTEM_UID_MAX/${SYSTEM_UID_MAX}/g" \
+ -e "s/SYSTEM_GID_MAX/${SYSTEM_GID_MAX}/g" \
-e "s#NOLOGIN#${NOLOGIN}#g" "$in"
}
diff --git a/test/TEST-21-SYSUSERS/unhappy-3.expected-err b/test/TEST-21-SYSUSERS/unhappy-3.expected-err
new file mode 100644
index 0000000000..d55b366372
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/unhappy-3.expected-err
@@ -0,0 +1 @@
+Group g1 not found.
diff --git a/test/TEST-21-SYSUSERS/unhappy-3.input b/test/TEST-21-SYSUSERS/unhappy-3.input
new file mode 100644
index 0000000000..64e60dd606
--- /dev/null
+++ b/test/TEST-21-SYSUSERS/unhappy-3.input
@@ -0,0 +1,4 @@
+# Ensure it is not allowed to create groups implicitly in the uid:groupname syntax
+#
+#Type Name ID GECOS HOMEDIR
+u u1 100:g1 -