diff --git a/docs/USER_GROUP_API.md b/docs/USER_GROUP_API.md
index 1109ad81da..3341c77986 100644
--- a/docs/USER_GROUP_API.md
+++ b/docs/USER_GROUP_API.md
@@ -175,6 +175,7 @@ method GetUserRecord(
dispositionMask: ?[]string,
uidMin: ?int,
uidMax: ?int,
+ uuid: ?string,
service : string
) -> (
record : object,
@@ -188,6 +189,7 @@ method GetGroupRecord(
dispositionMask: ?[]string,
gidMin: ?int,
gidMax: ?int,
+ uuid: ?string,
service : string
) -> (
record : object,
@@ -222,7 +224,7 @@ If neither of the two parameters are set the whole user database is enumerated.
In this case the method call needs to be made with `more` set, so that multiple method call replies may be generated as
effect, each carrying one user record.
-The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` fields permit
+The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` and `uuid` fields permit
*additional* filtering of the returned set of user records. The `fuzzyNames`
parameter shall be one or more strings that shall be searched for in "fuzzy"
way. What specifically this means is left for the backend to decide, but
@@ -232,19 +234,20 @@ carry identifying information for the user. The `dispositionMask` field shall
be one of more user record `disposition` strings. If specified only user
records matching one of the specified dispositions should be enumerated. The
`uidMin` and `uidMax` fields specify a minimum and maximum value for the UID of
-returned records. Inline searching for `uid` and `userName` support for
+returned records. The `uuid` field specifies to search for the user record associated
+with the specified UUID. Inline searching for `uid` and `userName` support for
filtering with these four additional parameters is optional, and clients are
expected to be able to do client-side filtering in case the parameters are not
supported by a service. The service should return the usual `InvalidParameter`
error for the relevant parameter if one is passed and it does not support
it. If a request is made specifying `uid` or `userName` and a suitable record
is found, but the specified filter via `fuzzyNames`, `dispositionMask`,
-`uidMin`, or `uidMax` does not match, a `NonMatchingRecordFound` error should
+`uidMin`, `uidMax` or `uuid` does not match, a `NonMatchingRecordFound` error should
be returned.
Or to say this differently: the *primary search keys* are
`userName`/`groupName` and `uid`/`gid` and the *secondary search filters* are
-`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`. If no entry matching
+`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`, `uuid`. If no entry matching
either of the primary search keys are found `NoRecordFound()` is returned. If
one is found that matches one but not the other primary search key
`ConflictingRecordFound()` is returned. If an entry is found that matches the
diff --git a/man/userdbctl.xml b/man/userdbctl.xml
index c705b6a783..5100e9db93 100644
--- a/man/userdbctl.xml
+++ b/man/userdbctl.xml
@@ -227,6 +227,15 @@
+
+
+
+ When used with the user or group command,
+ filters the output to the record with the specified UUID. If unspecified, no UUID-based filtering is applied.
+
+
+
+
diff --git a/src/shared/user-record.c b/src/shared/user-record.c
index b55875d6ce..1fd292dba8 100644
--- a/src/shared/user-record.c
+++ b/src/shared/user-record.c
@@ -2777,7 +2777,8 @@ bool userdb_match_is_set(const UserDBMatch *match) {
return !strv_isempty(match->fuzzy_names) ||
!FLAGS_SET(match->disposition_mask, USER_DISPOSITION_MASK_ALL) ||
match->uid_min > 0 ||
- match->uid_max < UID_INVALID-1;
+ match->uid_max < UID_INVALID-1 ||
+ !sd_id128_is_null(match->uuid);
}
void userdb_match_done(UserDBMatch *match) {
@@ -2836,6 +2837,9 @@ bool user_record_match(UserRecord *u, const UserDBMatch *match) {
if (!BIT_SET(match->disposition_mask, user_record_disposition(u)))
return false;
+ if (!sd_id128_is_null(match->uuid) && !sd_id128_equal(match->uuid, u->uuid))
+ return false;
+
if (!strv_isempty(match->fuzzy_names)) {
/* Note this array of names is sparse, i.e. various entries listed in it will be
diff --git a/src/shared/user-record.h b/src/shared/user-record.h
index f411214453..e4384e50b7 100644
--- a/src/shared/user-record.h
+++ b/src/shared/user-record.h
@@ -513,6 +513,7 @@ typedef struct UserDBMatch {
uid_t uid_max;
gid_t gid_max;
};
+ sd_id128_t uuid;
} UserDBMatch;
#define USER_DISPOSITION_MASK_ALL ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1))
@@ -522,6 +523,7 @@ typedef struct UserDBMatch {
.disposition_mask = USER_DISPOSITION_MASK_ALL, \
.uid_min = 0, \
.uid_max = UID_INVALID-1, \
+ .uuid = SD_ID128_NULL, \
}
/* Maybe useful when we want to resolve root and system user/group but want to refuse nobody user/group. */
diff --git a/src/shared/userdb.c b/src/shared/userdb.c
index 49850ff216..c7a1595211 100644
--- a/src/shared/userdb.c
+++ b/src/shared/userdb.c
@@ -799,27 +799,29 @@ nomatch:
return 0;
}
-static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
+static int query_append_common(sd_json_variant **query, const UserDBMatch *match) {
int r;
+ _cleanup_strv_free_ char **dispositions = NULL;
assert(query);
- if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
- return 0;
+ uint64_t mask = match->disposition_mask;
+ if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) {
+ for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
+ if (!BIT_SET(mask, d))
+ continue;
- _cleanup_strv_free_ char **dispositions = NULL;
- for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
- if (!BITS_SET(mask, d))
- continue;
-
- r = strv_extend(&dispositions, user_disposition_to_string(d));
- if (r < 0)
- return r;
+ r = strv_extend(&dispositions, user_disposition_to_string(d));
+ if (r < 0)
+ return r;
+ }
}
return sd_json_variant_merge_objectbo(
query,
- SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
+ SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
+ SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(match->uuid), "uuid", SD_JSON_BUILD_UUID(match->uuid)),
+ SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(dispositions), "dispositionMask", SD_JSON_BUILD_STRV(dispositions)));
}
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
@@ -832,13 +834,12 @@ static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *ma
r = sd_json_variant_merge_objectbo(
query,
- SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
if (r < 0)
return r;
- return query_append_disposition_mask(query, match->disposition_mask);
+ return query_append_common(query, match);
}
static int userdb_by_name_fallbacks(
@@ -1298,13 +1299,13 @@ static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *ma
r = sd_json_variant_merge_objectbo(
query,
- SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
+
if (r < 0)
return r;
- return query_append_disposition_mask(query, match->disposition_mask);
+ return query_append_common(query, match);
}
static int groupdb_by_name_fallbacks(
diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c
index 4fbf8f671c..8efdc7c133 100644
--- a/src/userdb/userdbctl.c
+++ b/src/userdb/userdbctl.c
@@ -57,6 +57,7 @@ static bool arg_chain = false;
static uint64_t arg_disposition_mask = UINT64_MAX;
static uid_t arg_uid_min = 0;
static uid_t arg_uid_max = UID_INVALID-1;
+static sd_id128_t arg_uuid = SD_ID128_NULL;
static bool arg_fuzzy = false;
static bool arg_boundaries = true;
static sd_json_variant *arg_from_file = NULL;
@@ -411,7 +412,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
int ret = 0, r;
if (arg_output < 0)
- arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
+ arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) || !sd_id128_is_null(arg_uuid) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) {
table = table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order");
@@ -431,6 +432,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
.disposition_mask = arg_disposition_mask,
.uid_min = arg_uid_min,
.uid_max = arg_uid_max,
+ .uuid = arg_uuid,
};
if (arg_from_file) {
@@ -753,7 +755,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
int ret = 0, r;
if (arg_output < 0)
- arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
+ arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) || !sd_id128_is_null(arg_uuid) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) {
table = table_new(" ", "name", "disposition", "gid", "description", "order");
@@ -772,6 +774,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
.disposition_mask = arg_disposition_mask,
.gid_min = arg_uid_min,
.gid_max = arg_uid_max,
+ .uuid = arg_uuid,
};
if (arg_from_file) {
@@ -1588,6 +1591,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CHAIN,
ARG_UID_MIN,
ARG_UID_MAX,
+ ARG_UUID,
ARG_DISPOSITION,
ARG_BOUNDARIES,
};
@@ -1608,6 +1612,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "chain", no_argument, NULL, ARG_CHAIN },
{ "uid-min", required_argument, NULL, ARG_UID_MIN },
{ "uid-max", required_argument, NULL, ARG_UID_MAX },
+ { "uuid", required_argument, NULL, ARG_UUID },
{ "fuzzy", no_argument, NULL, 'z' },
{ "disposition", required_argument, NULL, ARG_DISPOSITION },
{ "boundaries", required_argument, NULL, ARG_BOUNDARIES },
@@ -1791,6 +1796,12 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg);
break;
+ case ARG_UUID:
+ r = sd_id128_from_string(optarg, &arg_uuid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --uuid= value: %s", optarg);
+ break;
+
case 'z':
arg_fuzzy = true;
break;
diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c
index 0282cc37dc..fc698e841d 100644
--- a/src/userdb/userwork.c
+++ b/src/userdb/userwork.c
@@ -149,6 +149,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete
{ "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 },
{ "uidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_min), 0 },
{ "uidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_max), 0 },
+ { "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(LookupParameters, match.uuid), 0 },
{}
};
@@ -293,6 +294,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet
{ "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 },
{ "gidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_min), 0 },
{ "gidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_max), 0 },
+ { "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(LookupParameters, match.uuid), 0 },
{}
};