1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include "format-util.h"
6 #include "glyph-util.h"
10 #include "pretty-print.h"
11 #include "process-util.h"
12 #include "rlimit-util.h"
15 #include "terminal-util.h"
16 #include "user-record-show.h"
17 #include "user-util.h"
20 const char *user_record_state_color(const char *state
) {
21 if (STR_IN_SET(state
, "unfixated", "absent"))
23 else if (streq(state
, "active"))
24 return ansi_highlight_green();
25 else if (STR_IN_SET(state
, "locked", "dirty"))
26 return ansi_highlight_yellow();
31 void user_record_show(UserRecord
*hr
, bool show_full_group_info
) {
32 _cleanup_strv_free_
char **langs
= NULL
;
33 const char *hd
, *ip
, *shell
;
39 printf(" User name: %s\n",
40 user_record_user_name_and_realm(hr
));
45 color
= user_record_state_color(hr
->state
);
47 printf(" State: %s%s%s\n",
48 strempty(color
), hr
->state
, color
? ansi_normal() : "");
51 printf(" Disposition: %s\n", user_disposition_to_string(user_record_disposition(hr
)));
53 if (hr
->last_change_usec
!= USEC_INFINITY
) {
54 printf(" Last Change: %s\n", FORMAT_TIMESTAMP(hr
->last_change_usec
));
56 if (hr
->last_change_usec
> now(CLOCK_REALTIME
))
57 printf(" %sModification time lies in the future, system clock wrong?%s\n",
58 ansi_highlight_yellow(), ansi_normal());
61 if (hr
->last_password_change_usec
!= USEC_INFINITY
&&
62 hr
->last_password_change_usec
!= hr
->last_change_usec
)
63 printf(" Last Passw.: %s\n", FORMAT_TIMESTAMP(hr
->last_password_change_usec
));
65 r
= user_record_test_blocked(hr
);
69 printf(" Login OK: %sno%s (record is locked)\n", ansi_highlight_red(), ansi_normal());
73 printf(" Login OK: %sno%s (record not valid yet))\n", ansi_highlight_red(), ansi_normal());
77 printf(" Login OK: %sno%s (record not valid anymore))\n", ansi_highlight_red(), ansi_normal());
84 if (r
< 0 && r
!= -ESTALE
) {
86 printf(" Login OK: %sno%s (%m)\n", ansi_highlight_red(), ansi_normal());
90 if (is_nologin_shell(user_record_shell(hr
))) {
91 printf(" Login OK: %sno%s (nologin shell)\n", ansi_highlight_red(), ansi_normal());
95 y
= user_record_ratelimit_next_try(hr
);
96 if (y
!= USEC_INFINITY
&& y
> now(CLOCK_REALTIME
)) {
97 printf(" Login OK: %sno%s (ratelimit)\n", ansi_highlight_red(), ansi_normal());
101 printf(" Login OK: %syes%s\n", ansi_highlight_green(), ansi_normal());
105 r
= user_record_test_password_change_required(hr
);
109 printf(" Password OK: %schange now%s\n", ansi_highlight_yellow(), ansi_normal());
113 printf(" Password OK: %sexpired%s (change now!)\n", ansi_highlight_yellow(), ansi_normal());
117 printf(" Password OK: %sexpired%s (for good)\n", ansi_highlight_red(), ansi_normal());
121 printf(" Password OK: %sexpires soon%s\n", ansi_highlight_yellow(), ansi_normal());
125 printf(" Password OK: %sno timestamp%s\n", ansi_highlight_red(), ansi_normal());
129 printf(" Password OK: %schange not permitted%s\n", ansi_highlight_yellow(), ansi_normal());
133 printf(" Password OK: %slast password change in future%s\n", ansi_highlight_yellow(), ansi_normal());
139 printf(" Password OK: %sno%s (%m)\n", ansi_highlight_yellow(), ansi_normal());
143 if (strv_isempty(hr
->hashed_password
)) {
144 if (hr
->incomplete
) /* Record might be incomplete, due to privs */
146 printf(" Password OK: %sno%s (none set)\n", ansi_highlight(), ansi_normal());
149 if (strv_contains(hr
->hashed_password
, "")) {
150 printf(" Password OK: %sno%s (empty set)\n", ansi_highlight_red(), ansi_normal());
153 bool has_valid_passwords
= false;
154 STRV_FOREACH(p
, hr
->hashed_password
)
155 if (!hashed_password_is_locked_or_invalid(*p
)) {
156 has_valid_passwords
= true;
159 if (has_valid_passwords
)
160 printf(" Password OK: %syes%s\n", ansi_highlight_green(), ansi_normal());
162 printf(" Password OK: %sno%s (locked)\n", ansi_highlight(), ansi_normal());
164 if (uid_is_valid(hr
->uid
))
165 printf(" UID: " UID_FMT
"\n", hr
->uid
);
166 if (gid_is_valid(hr
->gid
)) {
167 if (show_full_group_info
) {
168 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
170 r
= groupdb_by_gid(hr
->gid
, 0, &gr
);
173 printf(" GID: " GID_FMT
" (unresolvable: %m)\n", hr
->gid
);
175 printf(" GID: " GID_FMT
" (%s)\n", hr
->gid
, gr
->group_name
);
177 printf(" GID: " GID_FMT
"\n", hr
->gid
);
178 } else if (uid_is_valid(hr
->uid
)) /* Show UID as GID if not separately configured */
179 printf(" GID: " GID_FMT
"\n", (gid_t
) hr
->uid
);
181 if (show_full_group_info
) {
182 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
184 r
= membershipdb_by_user(hr
->user_name
, 0, &iterator
);
187 printf(" Aux. Groups: (can't acquire: %m)\n");
189 const char *prefix
= " Aux. Groups:";
192 _cleanup_free_
char *group
= NULL
;
194 r
= membershipdb_iterator_get(iterator
, NULL
, &group
);
199 printf("%s (can't iterate: %m)\n", prefix
);
203 printf("%s %s\n", prefix
, group
);
209 if (hr
->real_name
&& !streq(hr
->real_name
, hr
->user_name
))
210 printf(" Real Name: %s\n", hr
->real_name
);
212 hd
= user_record_home_directory(hr
);
214 printf(" Directory: %s", hd
);
216 if (hr
->fallback_home_directory
&& hr
->use_fallback
)
217 printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal());
222 if (hr
->blob_directory
) {
223 _cleanup_free_
char **filenames
= NULL
;
224 size_t n_filenames
= 0;
226 r
= hashmap_dump_keys_sorted(hr
->blob_manifest
, (void***) &filenames
, &n_filenames
);
229 printf(" Blob Dir.: %s (can't iterate: %m)\n", hr
->blob_directory
);
231 printf(" Blob Dir.: %s\n", hr
->blob_directory
);
233 for (size_t i
= 0; i
< n_filenames
; i
++) {
234 _cleanup_free_
char *path
= NULL
, *link
= NULL
, *hash
= NULL
;
235 const char *filename
= filenames
[i
];
236 const uint8_t *hash_bytes
= hashmap_get(hr
->blob_manifest
, filename
);
237 bool last
= i
== n_filenames
- 1;
239 path
= path_join(hr
->blob_directory
, filename
);
241 (void) terminal_urlify_path(path
, filename
, &link
);
242 hash
= hexmem(hash_bytes
, SHA256_DIGEST_SIZE
);
244 printf(" %s %s %s(%s)%s\n",
245 special_glyph(last
? SPECIAL_GLYPH_TREE_RIGHT
: SPECIAL_GLYPH_TREE_BRANCH
),
248 hash
?: "can't display hash",
253 storage
= user_record_storage(hr
);
254 if (storage
>= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */
255 printf(" Storage: %s%s\n", user_storage_to_string(storage
),
256 storage
== USER_LUKS
? " (strong encryption)" :
257 storage
== USER_FSCRYPT
? " (weak encryption)" :
258 IN_SET(storage
, USER_DIRECTORY
, USER_SUBVOLUME
) ? " (no encryption)" : "");
260 ip
= user_record_image_path(hr
);
261 if (ip
&& !streq_ptr(ip
, hd
))
262 printf(" Image Path: %s\n", ip
);
264 b
= user_record_removable(hr
);
266 printf(" Removable: %s\n", yes_no(b
));
268 shell
= user_record_shell(hr
);
270 printf(" Shell: %s", shell
);
272 if (hr
->fallback_shell
&& hr
->use_fallback
)
273 printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal());
278 if (hr
->email_address
)
279 printf(" Email: %s\n", hr
->email_address
);
281 printf(" Location: %s\n", hr
->location
);
282 if (hr
->password_hint
)
283 printf(" Passw. Hint: %s\n", hr
->password_hint
);
285 printf(" Icon Name: %s\n", hr
->icon_name
);
288 printf(" Time Zone: %s\n", hr
->time_zone
);
290 r
= user_record_languages(hr
, &langs
);
293 printf(" Languages: (can't acquire: %m)\n");
294 } else if (!strv_isempty(langs
)) {
295 STRV_FOREACH(i
, langs
)
296 printf(i
== langs
? " Languages: %s" : ", %s", *i
);
301 printf(" Locked: %s\n", yes_no(hr
->locked
));
303 if (hr
->not_before_usec
!= UINT64_MAX
)
304 printf(" Not Before: %s\n", FORMAT_TIMESTAMP(hr
->not_before_usec
));
306 if (hr
->not_after_usec
!= UINT64_MAX
)
307 printf(" Not After: %s\n", FORMAT_TIMESTAMP(hr
->not_after_usec
));
309 if (hr
->umask
!= MODE_INVALID
)
310 printf(" UMask: 0%03o\n", hr
->umask
);
312 if (nice_is_valid(hr
->nice_level
))
313 printf(" Nice: %i\n", hr
->nice_level
);
315 for (int j
= 0; j
< _RLIMIT_MAX
; j
++) {
317 printf(" Limit: RLIMIT_%s=%" PRIu64
":%" PRIu64
"\n",
318 rlimit_to_string(j
), (uint64_t) hr
->rlimits
[j
]->rlim_cur
, (uint64_t) hr
->rlimits
[j
]->rlim_max
);
321 if (hr
->tasks_max
!= UINT64_MAX
)
322 printf(" Tasks Max: %" PRIu64
"\n", hr
->tasks_max
);
324 if (hr
->memory_high
!= UINT64_MAX
)
325 printf(" Memory High: %s\n", FORMAT_BYTES(hr
->memory_high
));
327 if (hr
->memory_max
!= UINT64_MAX
)
328 printf(" Memory Max: %s\n", FORMAT_BYTES(hr
->memory_max
));
330 if (hr
->cpu_weight
== CGROUP_WEIGHT_IDLE
)
331 printf(" CPU Weight: %s\n", "idle");
332 else if (hr
->cpu_weight
!= UINT64_MAX
)
333 printf(" CPU Weight: %" PRIu64
"\n", hr
->cpu_weight
);
335 if (hr
->io_weight
!= UINT64_MAX
)
336 printf(" IO Weight: %" PRIu64
"\n", hr
->io_weight
);
338 if (hr
->access_mode
!= MODE_INVALID
)
339 printf(" Access Mode: 0%03o\n", user_record_access_mode(hr
));
341 uint64_t caps
= user_record_capability_bounding_set(hr
);
342 if (caps
!= UINT64_MAX
) {
343 _cleanup_free_
char *scaps
= NULL
;
345 (void) capability_set_to_string_negative(caps
, &scaps
);
346 printf(" Bound. Caps: %s\n", strna(scaps
));
349 caps
= user_record_capability_ambient_set(hr
);
350 if (caps
!= UINT64_MAX
) {
351 _cleanup_free_
char *scaps
= NULL
;
353 (void) capability_set_to_string(caps
, &scaps
);
354 printf("Ambient Caps: %s\n", strna(scaps
));
357 if (storage
== USER_LUKS
) {
358 printf("LUKS Discard: online=%s offline=%s\n", yes_no(user_record_luks_discard(hr
)), yes_no(user_record_luks_offline_discard(hr
)));
360 if (!sd_id128_is_null(hr
->luks_uuid
))
361 printf(" LUKS UUID: " SD_ID128_UUID_FORMAT_STR
"\n", SD_ID128_FORMAT_VAL(hr
->luks_uuid
));
362 if (!sd_id128_is_null(hr
->partition_uuid
))
363 printf(" Part UUID: " SD_ID128_UUID_FORMAT_STR
"\n", SD_ID128_FORMAT_VAL(hr
->partition_uuid
));
364 if (!sd_id128_is_null(hr
->file_system_uuid
))
365 printf(" FS UUID: " SD_ID128_UUID_FORMAT_STR
"\n", SD_ID128_FORMAT_VAL(hr
->file_system_uuid
));
367 if (hr
->file_system_type
)
368 printf(" File System: %s\n", user_record_file_system_type(hr
));
370 if (hr
->luks_extra_mount_options
)
371 printf("LUKS MntOpts: %s\n", hr
->luks_extra_mount_options
);
374 printf(" LUKS Cipher: %s\n", hr
->luks_cipher
);
375 if (hr
->luks_cipher_mode
)
376 printf(" Cipher Mode: %s\n", hr
->luks_cipher_mode
);
377 if (hr
->luks_volume_key_size
!= UINT64_MAX
)
378 printf(" Volume Key: %" PRIu64
"bit\n", hr
->luks_volume_key_size
* 8);
380 if (hr
->luks_pbkdf_type
)
381 printf(" PBKDF Type: %s\n", hr
->luks_pbkdf_type
);
382 if (hr
->luks_pbkdf_hash_algorithm
)
383 printf(" PBKDF Hash: %s\n", hr
->luks_pbkdf_hash_algorithm
);
384 if (hr
->luks_pbkdf_force_iterations
!= UINT64_MAX
)
385 printf(" PBKDF Iters: %" PRIu64
"\n", hr
->luks_pbkdf_force_iterations
);
386 if (hr
->luks_pbkdf_time_cost_usec
!= UINT64_MAX
)
387 printf(" PBKDF Time: %s\n", FORMAT_TIMESPAN(hr
->luks_pbkdf_time_cost_usec
, 0));
388 if (hr
->luks_pbkdf_memory_cost
!= UINT64_MAX
)
389 printf(" PBKDF Bytes: %s\n", FORMAT_BYTES(hr
->luks_pbkdf_memory_cost
));
391 if (hr
->luks_pbkdf_parallel_threads
!= UINT64_MAX
)
392 printf("PBKDF Thread: %" PRIu64
"\n", hr
->luks_pbkdf_parallel_threads
);
393 if (hr
->luks_sector_size
!= UINT64_MAX
)
394 printf(" Sector Size: %" PRIu64
"\n", hr
->luks_sector_size
);
396 } else if (storage
== USER_CIFS
) {
398 if (hr
->cifs_service
)
399 printf("CIFS Service: %s\n", hr
->cifs_service
);
401 if (hr
->cifs_extra_mount_options
)
402 printf("CIFS MntOpts: %s\n", hr
->cifs_extra_mount_options
);
405 if (hr
->cifs_user_name
)
406 printf(" CIFS User: %s\n", user_record_cifs_user_name(hr
));
408 printf(" CIFS Domain: %s\n", hr
->cifs_domain
);
410 if (storage
!= USER_CLASSIC
)
411 printf(" Mount Flags: %s %s %s\n",
412 hr
->nosuid
? "nosuid" : "suid",
413 hr
->nodev
? "nodev" : "dev",
414 hr
->noexec
? "noexec" : "exec");
416 if (hr
->skeleton_directory
)
417 printf(" Skel. Dir.: %s\n", user_record_skeleton_directory(hr
));
419 if (hr
->disk_size
!= UINT64_MAX
)
420 printf(" Disk Size: %s\n", FORMAT_BYTES(hr
->disk_size
));
422 if (hr
->disk_usage
!= UINT64_MAX
) {
423 if (hr
->disk_size
!= UINT64_MAX
) {
426 permille
= (unsigned) DIV_ROUND_UP(hr
->disk_usage
* 1000U, hr
->disk_size
); /* Round up! */
427 printf(" Disk Usage: %s (= %u.%01u%%)\n",
428 FORMAT_BYTES(hr
->disk_usage
),
429 permille
/ 10, permille
% 10);
431 printf(" Disk Usage: %s\n", FORMAT_BYTES(hr
->disk_usage
));
434 if (hr
->disk_free
!= UINT64_MAX
) {
435 if (hr
->disk_size
!= UINT64_MAX
) {
436 const char *color_on
, *color_off
;
439 permille
= (unsigned) ((hr
->disk_free
* 1000U) / hr
->disk_size
); /* Round down! */
441 /* Color the output red or yellow if we are below 10% resp. 25% free. Because 10% and
442 * 25% can be a lot of space still, let's additionally make some absolute
443 * restrictions: 1G and 2G */
444 if (permille
<= 100U &&
445 hr
->disk_free
< 1024U*1024U*1024U /* 1G */) {
446 color_on
= ansi_highlight_red();
447 color_off
= ansi_normal();
448 } else if (permille
<= 250U &&
449 hr
->disk_free
< 2U*1024U*1024U*1024U /* 2G */) {
450 color_on
= ansi_highlight_yellow();
451 color_off
= ansi_normal();
453 color_on
= color_off
= "";
455 printf(" Disk Free: %s%s (= %u.%01u%%)%s\n",
457 FORMAT_BYTES(hr
->disk_free
),
458 permille
/ 10, permille
% 10,
461 printf(" Disk Free: %s\n", FORMAT_BYTES(hr
->disk_free
));
464 if (hr
->disk_floor
!= UINT64_MAX
)
465 printf(" Disk Floor: %s\n", FORMAT_BYTES(hr
->disk_floor
));
467 if (hr
->disk_ceiling
!= UINT64_MAX
)
468 printf("Disk Ceiling: %s\n", FORMAT_BYTES(hr
->disk_ceiling
));
470 if (hr
->good_authentication_counter
!= UINT64_MAX
)
471 printf(" Good Auth.: %" PRIu64
"\n", hr
->good_authentication_counter
);
473 if (hr
->last_good_authentication_usec
!= UINT64_MAX
)
474 printf(" Last Good: %s\n", FORMAT_TIMESTAMP(hr
->last_good_authentication_usec
));
476 if (hr
->bad_authentication_counter
!= UINT64_MAX
)
477 printf(" Bad Auth.: %" PRIu64
"\n", hr
->bad_authentication_counter
);
479 if (hr
->last_bad_authentication_usec
!= UINT64_MAX
)
480 printf(" Last Bad: %s\n", FORMAT_TIMESTAMP(hr
->last_bad_authentication_usec
));
482 t
= user_record_ratelimit_next_try(hr
);
483 if (t
!= USEC_INFINITY
) {
484 usec_t n
= now(CLOCK_REALTIME
);
487 printf(" Next Try: anytime\n");
489 printf(" Next Try: %sin %s%s\n",
490 ansi_highlight_red(),
491 FORMAT_TIMESPAN(t
- n
, USEC_PER_SEC
),
495 if (storage
!= USER_CLASSIC
)
496 printf(" Auth. Limit: %" PRIu64
" attempts per %s\n", user_record_ratelimit_burst(hr
),
497 FORMAT_TIMESPAN(user_record_ratelimit_interval_usec(hr
), 0));
499 if (hr
->enforce_password_policy
>= 0)
500 printf(" Passwd Pol.: %s\n", yes_no(hr
->enforce_password_policy
));
502 if (hr
->password_change_min_usec
!= UINT64_MAX
||
503 hr
->password_change_max_usec
!= UINT64_MAX
||
504 hr
->password_change_warn_usec
!= UINT64_MAX
||
505 hr
->password_change_inactive_usec
!= UINT64_MAX
) {
507 printf(" Passwd Chg.:");
509 if (hr
->password_change_min_usec
!= UINT64_MAX
) {
510 printf(" min %s", FORMAT_TIMESPAN(hr
->password_change_min_usec
, 0));
512 if (hr
->password_change_max_usec
!= UINT64_MAX
)
516 if (hr
->password_change_max_usec
!= UINT64_MAX
)
517 printf(" max %s", FORMAT_TIMESPAN(hr
->password_change_max_usec
, 0));
519 if (hr
->password_change_warn_usec
!= UINT64_MAX
)
520 printf("/warn %s", FORMAT_TIMESPAN(hr
->password_change_warn_usec
, 0));
522 if (hr
->password_change_inactive_usec
!= UINT64_MAX
)
523 printf("/inactive %s", FORMAT_TIMESPAN(hr
->password_change_inactive_usec
, 0));
528 if (hr
->password_change_now
>= 0)
529 printf("Pas. Ch. Now: %s\n", yes_no(hr
->password_change_now
));
531 if (hr
->drop_caches
>= 0 || user_record_drop_caches(hr
))
532 printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr
)));
534 if (hr
->auto_resize_mode
>= 0)
535 printf(" Auto Resize: %s\n", auto_resize_mode_to_string(user_record_auto_resize_mode(hr
)));
537 if (hr
->rebalance_weight
!= REBALANCE_WEIGHT_UNSET
) {
540 rb
= user_record_rebalance_weight(hr
);
541 if (rb
== REBALANCE_WEIGHT_OFF
)
542 printf(" Rebalance: off\n");
544 printf(" Rebalance: weight %" PRIu64
"\n", rb
);
547 if (!strv_isempty(hr
->ssh_authorized_keys
))
548 printf("SSH Pub. Key: %zu\n", strv_length(hr
->ssh_authorized_keys
));
550 if (!strv_isempty(hr
->pkcs11_token_uri
))
551 STRV_FOREACH(i
, hr
->pkcs11_token_uri
)
552 printf(i
== hr
->pkcs11_token_uri
?
553 "PKCS11 Token: %s\n" :
556 if (hr
->n_fido2_hmac_credential
> 0)
557 printf(" FIDO2 Token: %zu\n", hr
->n_fido2_hmac_credential
);
559 if (!strv_isempty(hr
->recovery_key_type
))
560 printf("Recovery Key: %zu\n", strv_length(hr
->recovery_key_type
));
562 k
= strv_length(hr
->hashed_password
);
564 printf(" Passwords: %snone%s\n",
565 user_record_disposition(hr
) == USER_REGULAR
? ansi_highlight_yellow() : ansi_normal(), ansi_normal());
567 printf(" Passwords: %zu\n", k
);
569 if (hr
->signed_locally
>= 0)
570 printf(" Local Sig.: %s\n", yes_no(hr
->signed_locally
));
572 if (hr
->stop_delay_usec
!= UINT64_MAX
)
573 printf(" Stop Delay: %s\n", FORMAT_TIMESPAN(hr
->stop_delay_usec
, 0));
575 if (hr
->auto_login
>= 0)
576 printf("Autom. Login: %s\n", yes_no(hr
->auto_login
));
578 if (hr
->preferred_session_launcher
)
579 printf("Sess. Launch: %s\n", hr
->preferred_session_launcher
);
580 if (hr
->preferred_session_type
)
581 printf("Session Type: %s\n", hr
->preferred_session_type
);
583 if (hr
->kill_processes
>= 0)
584 printf(" Kill Proc.: %s\n", yes_no(hr
->kill_processes
));
587 printf(" Service: %s\n", hr
->service
);
590 void group_record_show(GroupRecord
*gr
, bool show_full_user_info
) {
593 printf(" Group name: %s\n",
594 group_record_group_name_and_realm(gr
));
596 printf(" Disposition: %s\n", user_disposition_to_string(group_record_disposition(gr
)));
598 if (gr
->last_change_usec
!= USEC_INFINITY
)
599 printf(" Last Change: %s\n", FORMAT_TIMESTAMP(gr
->last_change_usec
));
601 if (gid_is_valid(gr
->gid
))
602 printf(" GID: " GID_FMT
"\n", gr
->gid
);
604 if (show_full_user_info
) {
605 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
607 r
= membershipdb_by_group(gr
->group_name
, 0, &iterator
);
610 printf(" Members: (can't acquire: %m)");
612 const char *prefix
= " Members:";
615 _cleanup_free_
char *user
= NULL
;
617 r
= membershipdb_iterator_get(iterator
, &user
, NULL
);
622 printf("%s (can't iterate: %m\n", prefix
);
626 printf("%s %s\n", prefix
, user
);
631 const char *prefix
= " Members:";
633 STRV_FOREACH(i
, gr
->members
) {
634 printf("%s %s\n", prefix
, *i
);
639 if (!strv_isempty(gr
->administrators
)) {
640 const char *prefix
= " Admins:";
642 STRV_FOREACH(i
, gr
->administrators
) {
643 printf("%s %s\n", prefix
, *i
);
648 if (gr
->description
&& !streq(gr
->description
, gr
->group_name
))
649 printf(" Description: %s\n", gr
->description
);
651 if (!strv_isempty(gr
->hashed_password
))
652 printf(" Passwords: %zu\n", strv_length(gr
->hashed_password
));
655 printf(" Service: %s\n", gr
->service
);