<xi:include href="version-info.xml" xpointer="v250"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--fuzzy</option></term>
+ <term><option>-z</option></term>
+
+ <listitem><para>When used with the <command>user</command> or <command>group</command> command, do a
+ fuzzy string search. Any specified arguments will be matched against the user name, the real name of
+ the user record, the email address, and other descriptive strings of the user or group
+ record. Moreover, instead of precise matching, a substring match or a match allowing slight
+ deviations in spelling is applied.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--disposition=</option></term>
+
+ <listitem><para>When used with the <command>user</command> or <command>group</command> command,
+ filters by disposition of the record. Takes one of <literal>intrinsic</literal>,
+ <literal>system</literal>, <literal>regular</literal>, <literal>dynamic</literal>,
+ <literal>container</literal>. May be used multiple times, in which case only users matching any of
+ the specified dispositions are shown.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-I</option></term>
+ <term><option>-S</option></term>
+ <term><option>-R</option></term>
+
+ <listitem><para>Shortcuts for <option>--disposition=intrinsic</option>,
+ <option>--disposition=system</option>, <option>--disposition=regular</option>,
+ respectively.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--uid-min=</option></term>
+ <term><option>--uid-max=</option></term>
+
+ <listitem><para>When used with the <command>user</command> or <command>group</command> command,
+ filters the output by UID/GID ranges. Takes numeric minimum resp. maximum UID/GID values. Shows only
+ records within the specified range. When applied to the <command>user</command> command matches
+ against UIDs, when applied to the <command>group</command> command against GIDs (despite the name of
+ the switch). If unspecified defaults to 0 (for the minimum) and 4294967294 (for the maximum), i.e. by
+ default no filtering is applied as the whole UID/GID range is covered.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
+
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="no-legend" />
<xi:include href="standard-options.xml" xpointer="help" />
static UserDBFlags arg_userdb_flags = 0;
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
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 bool arg_fuzzy = false;
STATIC_DESTRUCTOR_REGISTER(arg_services, strv_freep);
FOREACH_ELEMENT(i, uid_range_table) {
_cleanup_free_ char *name = NULL, *comment = NULL;
+ if (!FLAGS_SET(arg_disposition_mask, UINT64_C(1) << i->disposition))
+ continue;
+
if (!uid_range_covers(p, i->first, i->last - i->first + 1))
continue;
int ret = 0, r;
if (arg_output < 0)
- arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
+ arg_output = argc > 1 && !arg_fuzzy ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) {
table = table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order");
(void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, (size_t) 5, (size_t) 6, (size_t) 7);
}
- if (argc > 1)
+ UserDBMatch match = {
+ .disposition_mask = arg_disposition_mask,
+ .uid_min = arg_uid_min,
+ .uid_max = arg_uid_max,
+ };
+
+ if (argc > 1 && !arg_fuzzy)
STRV_FOREACH(i, argv + 1) {
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
uid_t uid;
else
log_error_errno(r, "Failed to find user %s: %m", *i);
- if (ret >= 0)
- ret = r;
+ RET_GATHER(ret, r);
+ } else if (!user_record_match(ur, &match)) {
+ log_error("User '%s' does not match filter.", *i);
+ RET_GATHER(ret, -ENOEXEC);
} else {
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
}
else {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
+ _cleanup_strv_free_ char **names = NULL;
+
+ if (argc > 1) {
+ names = strv_copy(argv + 1);
+ if (!names)
+ return log_oom();
+
+ match.fuzzy_names = names;
+ }
r = userdb_all(arg_userdb_flags, &iterator);
if (r == -ENOLINK) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */
if (r < 0)
return log_error_errno(r, "Failed acquire next user: %m");
+ if (!user_record_match(ur, &match))
+ continue;
+
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
int ret = 0, r;
if (arg_output < 0)
- arg_output = argc > 1 ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
+ arg_output = argc > 1 && !arg_fuzzy ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) {
table = table_new(" ", "name", "disposition", "gid", "description", "order");
(void) table_set_display(table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4);
}
- if (argc > 1)
+ UserDBMatch match = {
+ .disposition_mask = arg_disposition_mask,
+ .gid_min = arg_uid_min,
+ .gid_max = arg_uid_max,
+ };
+
+ if (argc > 1 && !arg_fuzzy)
STRV_FOREACH(i, argv + 1) {
_cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
gid_t gid;
else
log_error_errno(r, "Failed to find group %s: %m", *i);
- if (ret >= 0)
- ret = r;
+ RET_GATHER(ret, r);
+ } else if (!group_record_match(gr, &match)) {
+ log_error("Group '%s' does not match filter.", *i);
+ RET_GATHER(ret, -ENOEXEC);
} else {
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
}
else {
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
+ _cleanup_strv_free_ char **names = NULL;
+
+ if (argc > 1) {
+ names = strv_copy(argv + 1);
+ if (!names)
+ return log_oom();
+
+ match.fuzzy_names = names;
+ }
r = groupdb_all(arg_userdb_flags, &iterator);
if (r == -ENOLINK)
if (r < 0)
return log_error_errno(r, "Failed acquire next group: %m");
+ if (!group_record_match(gr, &match))
+ continue;
+
if (draw_separator && arg_output == OUTPUT_FRIENDLY)
putchar('\n');
" --multiplexer=BOOL Control whether to use the multiplexer\n"
" --json=pretty|short JSON output mode\n"
" --chain Chain another command\n"
+ " --uid-min=ID Filter by minimum UID/GID (default 0)\n"
+ " --uid-max=ID Filter by maximum UID/GID (default 4294967294)\n"
+ " -z --fuzzy Do a fuzzy name search\n"
+ " --disposition=VALUE Filter by disposition\n"
+ " -I Equivalent to --disposition=intrinsic\n"
+ " -S Equivalent to --disposition=system\n"
+ " -R Equivalent to --disposition=regular\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ARG_MULTIPLEXER,
ARG_JSON,
ARG_CHAIN,
+ ARG_UID_MIN,
+ ARG_UID_MAX,
+ ARG_DISPOSITION,
};
static const struct option options[] = {
{ "multiplexer", required_argument, NULL, ARG_MULTIPLEXER },
{ "json", required_argument, NULL, ARG_JSON },
{ "chain", no_argument, NULL, ARG_CHAIN },
+ { "uid-min", required_argument, NULL, ARG_UID_MIN },
+ { "uid-max", required_argument, NULL, ARG_UID_MAX },
+ { "fuzzy", required_argument, NULL, 'z' },
+ { "disposition", required_argument, NULL, ARG_DISPOSITION },
{}
};
int c;
c = getopt_long(argc, argv,
- arg_chain ? "+hjs:N" : "hjs:N", /* When --chain was used disable parsing of further switches */
+ arg_chain ? "+hjs:NISRz" : "hjs:NISRz", /* When --chain was used disable parsing of further switches */
options, NULL);
if (c < 0)
break;
arg_chain = true;
break;
+ case ARG_DISPOSITION: {
+ UserDisposition d = user_disposition_from_string(optarg);
+ if (d < 0)
+ return log_error_errno(d, "Unknown user disposition: %s", optarg);
+
+ if (arg_disposition_mask == UINT64_MAX)
+ arg_disposition_mask = 0;
+
+ arg_disposition_mask |= UINT64_C(1) << d;
+ break;
+ }
+
+ case 'I':
+ if (arg_disposition_mask == UINT64_MAX)
+ arg_disposition_mask = 0;
+
+ arg_disposition_mask |= UINT64_C(1) << USER_INTRINSIC;
+ break;
+
+ case 'S':
+ if (arg_disposition_mask == UINT64_MAX)
+ arg_disposition_mask = 0;
+
+ arg_disposition_mask |= UINT64_C(1) << USER_SYSTEM;
+ break;
+
+ case 'R':
+ if (arg_disposition_mask == UINT64_MAX)
+ arg_disposition_mask = 0;
+
+ arg_disposition_mask |= UINT64_C(1) << USER_REGULAR;
+ break;
+
+ case ARG_UID_MIN:
+ r = parse_uid(optarg, &arg_uid_min);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --uid-min= value: %s", optarg);
+ break;
+
+ case ARG_UID_MAX:
+ r = parse_uid(optarg, &arg_uid_max);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg);
+ break;
+
+ case 'z':
+ arg_fuzzy = true;
+ break;
+
case '?':
return -EINVAL;
}
}
+ if (arg_uid_min > arg_uid_max)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", arg_uid_min, arg_uid_max);
+
+ /* If not mask was specified, use the all bits on mask */
+ if (arg_disposition_mask == UINT64_MAX)
+ arg_disposition_mask = USER_DISPOSITION_MASK_MAX;
+
return 1;
}