2 * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
4 * Licensed under the GPL v2. For licencing details see COPYING.
7 /** \file select.c The select mode of adu. */
9 #include <dirent.h> /* readdir() */
12 #include "gcc-compat.h"
17 #include "portable_io.h"
19 /** Global dir count. */
20 static uint64_t num_dirs;
21 /** Global files count. */
22 static uint64_t num_files;
23 /** Global bytes count. */
24 static uint64_t num_bytes;
27 /** The decimal representation of an uint64_t never exceeds that size. */
28 #define FORMATED_VALUE_SIZE 25
30 #define GLOBAL_LIST_ATOMS \
33 ATOM(dirname, STRING) \
35 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
36 struct atom global_list_atoms[] = {
41 #define ATOM(x, y) gla_ ## x,
42 enum global_list_atoms {GLOBAL_LIST_ATOMS};
45 #define GLOBAL_SUMMARY_ATOMS \
50 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
51 struct atom global_summary_atoms[] = {
56 #define ATOM(x, y) gsa_ ## x,
57 enum global_summary_atoms {GLOBAL_SUMMARY_ATOMS};
60 #define USER_SUMMARY_ATOMS \
61 ATOM(pw_name, STRING) \
67 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
68 struct atom user_summary_atoms[] = {
73 #define ATOM(x, y) usa_ ## x,
74 enum user_summary_atoms {USER_SUMMARY_ATOMS};
79 /* these get filled in by the select command. */
80 static char count_unit_buf[4] = "( )", size_unit_buf[4] = "( )";
82 struct global_list_info {
86 struct format_info *fi;
89 enum user_stats_flags {
90 USF_PRINT_DIRNAME = 1,
93 USF_COMPUTE_SUMMARY = 8,
96 struct user_stats_info {
98 enum user_stats_flags flags;
101 struct user_info *ui;
104 static const uint64_t size_unit_divisors[] = {
105 [size_unit_arg_b] = 1ULL,
106 [size_unit_arg_k] = 1024ULL,
107 [size_unit_arg_m] = 1024ULL * 1024ULL,
108 [size_unit_arg_g] = 1024ULL * 1024ULL * 1024ULL,
109 [size_unit_arg_t] = 1024ULL * 1024ULL * 1024ULL * 1024ULL,
112 static const uint64_t count_unit_divisors[] = {
114 [count_unit_arg_n] = 1ULL,
115 [count_unit_arg_k] = 1000ULL,
116 [count_unit_arg_m] = 1000ULL * 1000ULL,
117 [count_unit_arg_g] = 1000ULL * 1000ULL * 1000ULL,
118 [count_unit_arg_t] = 1000ULL * 1000ULL * 1000ULL * 1000ULL,
121 static const char size_unit_abbrevs[] = " BKMGT";
122 static const char count_unit_abbrevs[] = " kmgt";
123 static enum enum_size_unit format_size_value(enum enum_size_unit unit,
124 uint64_t value, int print_unit, char *result)
126 enum enum_size_unit u = unit;
127 char unit_buf[2] = "\0\0";
129 if (unit == size_unit_arg_h) /* human readable */
130 for (u = size_unit_arg_b; u < size_unit_arg_t &&
131 value > size_unit_divisors[u + 1]; u++)
134 unit_buf[0] = size_unit_abbrevs[u];
135 sprintf(result, "%llu%s",
136 (long long unsigned)value / size_unit_divisors[u], unit_buf);
140 static enum enum_count_unit format_count_value(enum enum_count_unit unit,
141 uint64_t value, int print_unit, char *result)
143 enum enum_count_unit u = unit;
144 char unit_buf[2] = "\0\0";
146 if (unit == count_unit_arg_h) /* human readable */
147 for (u = count_unit_arg_n; u < count_unit_arg_t &&
148 value > count_unit_divisors[u + 1]; u++)
151 unit_buf[0] = count_unit_abbrevs[u];
152 sprintf(result, "%llu%s",
153 (long long unsigned)value / count_unit_divisors[u], unit_buf);
157 static FILE *output_file;
159 __printf_1_2 static int output(const char const *fmt, ...)
165 ret = vfprintf(output_file, fmt, argp);
167 return ret < 0? -E_OUTPUT : 1;
170 static int get_dir_name_by_number(uint64_t *dirnum, char **name)
172 char *result = NULL, *tmp;
174 uint64_t val = *dirnum;
175 struct osl_object obj;
181 obj.size = sizeof(val);
182 ret = osl(osl_get_row(dir_table, DT_NUM, &obj, &row));
185 ret = osl(osl_get_object(dir_table, row, DT_PARENT_NUM, &obj));
188 val = *(uint64_t *)obj.data;
189 ret = osl(osl_get_object(dir_table, row, DT_NAME, &obj));
192 pfx = (select_conf.print_base_dir_given || val)? (char *)obj.data : ".";
193 tmp = make_message("%s/%s", pfx, result? result : "");
209 static int get_dir_name_of_row(struct osl_row *dir_table_row, char **name)
211 struct osl_object obj;
215 ret = osl(osl_get_object(dir_table, dir_table_row, DT_NUM, &obj));
218 return get_dir_name_by_number((uint64_t *)obj.data, name);
221 static int user_stats_loop_function(struct osl_row *row, void *data)
223 struct user_stats_info *usi = data;
224 struct osl_object obj;
225 int ret, summary = usi->flags & GSF_COMPUTE_SUMMARY;
226 char formated_value[FORMATED_VALUE_SIZE];
229 if (!usi->count && !summary) {
230 ret = -E_LOOP_COMPLETE;
233 if (summary || (usi->count && (usi->flags & USF_PRINT_FILES))) {
235 ret = osl(osl_get_object(usi->ui->table, row, UT_FILES, &obj));
238 files = *(uint64_t *)obj.data;
239 if (usi->count && (usi->flags & USF_PRINT_FILES)) {
240 format_count_value(select_conf.count_unit_arg, files,
241 select_conf.count_unit_arg == count_unit_arg_h,
243 ret = output("\t%s%s", formated_value,
244 (usi->flags & (USF_PRINT_BYTES | USF_PRINT_DIRNAME))?
250 usi->ui->files += files;
252 if (summary || (usi->count && (usi->flags & USF_PRINT_BYTES))) {
254 ret = osl(osl_get_object(usi->ui->table, row, UT_BYTES, &obj));
257 bytes = *(uint64_t *)obj.data;
258 if (usi->count && (usi->flags & USF_PRINT_BYTES)) {
259 format_size_value(select_conf.size_unit_arg, bytes,
260 select_conf.size_unit_arg == size_unit_arg_h,
262 ret = output("%s%s%s",
263 (usi->flags & USF_PRINT_FILES)? "" : "\t",
265 usi->flags & USF_PRINT_DIRNAME? "\t" : "\n"
271 usi->ui->bytes += bytes;
276 if (usi->count && (usi->flags & USF_PRINT_DIRNAME)) {
278 ret = osl(osl_get_object(usi->ui->table, row, UT_DIR_NUM, &obj));
281 ret = get_dir_name_by_number((uint64_t *)obj.data, &dirname);
284 ret = output("%s%s\n",
285 (usi->flags & (USF_PRINT_BYTES | USF_PRINT_FILES))? "" : "\t",
296 usi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
300 static int check_loop_return(int ret, int loop_ret, int loop_osl_errno)
304 assert(ret == -E_OSL);
305 if (osl_errno != E_OSL_LOOP)
306 /* error not caused by loop function returning negative. */
308 assert(loop_ret < 0);
309 if (loop_ret == -E_LOOP_COMPLETE) /* no error */
311 if (loop_ret == -E_OSL) { /* osl error in loop function */
312 assert(loop_osl_errno);
313 osl_errno = loop_osl_errno;
318 static int adu_loop_reverse(struct osl_table *t, unsigned col_num, void *private_data,
319 osl_rbtree_loop_func *func, int *loop_ret, int *loop_osl_errno)
321 int ret = osl(osl_rbtree_loop_reverse(t, col_num, private_data, func));
322 return check_loop_return(ret, *loop_ret, *loop_osl_errno);
325 static int print_global_summary(struct format_info *fi)
329 union atom_value values[] = {
330 [gsa_dirs] = {.num_value = (long long unsigned)num_dirs},
331 [gsa_files] = {.num_value = (long long unsigned)num_files},
332 [gsa_size] = {.num_value = (long long unsigned)num_bytes}
335 if (select_conf.no_global_summary_given)
337 if (!select_conf.no_headers_given) {
338 ret = output("Global summary\n");
342 buf = format_items(fi, values);
343 ret = output("%s", buf);
348 static int print_user_summary_line(struct user_info *ui, __a_unused void *data)
350 struct format_info *fi = data;
351 union atom_value values[] = {
352 [usa_pw_name] = {.string_value = ui->pw_name?
354 [usa_uid] = {.num_value = (long long unsigned)ui->uid},
355 [usa_dirs] = {.num_value = (long long unsigned)ui->dirs},
356 [usa_files] = {.num_value = (long long unsigned)ui->files},
357 [usa_size] = {.num_value = (long long unsigned)ui->bytes}
359 char *buf = format_items(fi, values);
360 int ret = output("%s", buf);
366 static int name_comp(const void *a, const void *b)
368 char *x = ((struct user_info *)a)->pw_name;
369 char *y = ((struct user_info *)b)->pw_name;
378 static int uid_comp(const void *a, const void *b)
380 return -NUM_COMPARE(((struct user_info *)a)->uid,
381 ((struct user_info *)b)->uid);
384 static int dir_count_comp(const void *a, const void *b)
386 return NUM_COMPARE(((struct user_info *)a)->dirs,
387 ((struct user_info *)b)->dirs);
390 static int file_count_comp(const void *a, const void *b)
392 return NUM_COMPARE(((struct user_info *)a)->files,
393 ((struct user_info *)b)->files);
396 static int size_comp(const void *a, const void *b)
398 return NUM_COMPARE(((struct user_info *)a)->bytes,
399 ((struct user_info *)b)->bytes);
403 * The comparators for sorting the user summary.
405 * This is an array of pointers to functions taking two constant void *
406 * pointers and returning an int.
408 static int (*summary_comparators[])(const void *, const void *) = {
409 [user_summary_sort_arg_name] = name_comp,
410 [user_summary_sort_arg_uid] = uid_comp,
411 [user_summary_sort_arg_dir_count] = dir_count_comp,
412 [user_summary_sort_arg_file_count] = file_count_comp,
413 [user_summary_sort_arg_size] = size_comp,
416 static int print_user_summary(struct format_info *fi)
418 if (select_conf.no_user_summary_given)
420 if (!select_conf.no_headers_given) {
421 int ret = output("User summary\n");
425 sort_hash_table(summary_comparators[select_conf.user_summary_sort_arg]);
426 return for_each_admissible_user(print_user_summary_line, fi);
429 static int print_user_list(struct user_info *ui, __a_unused void *data)
432 struct user_stats_info usi;
433 enum enum_user_list ula = select_conf.user_list_arg;
434 int print_size_list = (ula == user_list_arg_size
435 || ula == user_list_arg_both);
437 if (print_size_list) {
438 usi.count = select_conf.limit_arg;
440 usi.flags = USF_PRINT_DIRNAME | USF_PRINT_BYTES | USF_COMPUTE_SUMMARY;
441 if (!select_conf.no_headers_given) {
442 ret = output("%s (uid %u), by size%s:\n",
443 ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid,
448 ret = adu_loop_reverse(ui->table, UT_BYTES, &usi, user_stats_loop_function,
449 &usi.ret, &usi.osl_errno);
456 if (ula == user_list_arg_file_count || ula == user_list_arg_both) {
457 if (!select_conf.no_headers_given) {
458 ret = output("%s (uid %u), by file count%s:\n",
459 ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid,
464 usi.count = select_conf.limit_arg,
466 usi.flags = USF_PRINT_DIRNAME | USF_PRINT_FILES;
467 ret = adu_loop_reverse(ui->table, UT_FILES, &usi, user_stats_loop_function,
468 &usi.ret, &usi.osl_errno);
475 if (ula == user_list_arg_none && !select_conf.no_user_summary_given) {
476 usi.count = select_conf.limit_arg;
478 usi.flags = USF_COMPUTE_SUMMARY;
479 ret = adu_loop_reverse(ui->table, UT_FILES, &usi, user_stats_loop_function,
480 &usi.ret, &usi.osl_errno);
487 static int print_user_lists(void)
489 return for_each_admissible_user(print_user_list, NULL);
492 static int get_num_files_of_row(struct osl_row *row, uint64_t *num_files)
494 struct osl_object obj;
495 int ret = osl(osl_get_object(dir_table, row, DT_FILES, &obj));
498 *num_files = *(uint64_t *)obj.data;
502 static int get_num_bytes_of_row(struct osl_row *row, uint64_t *num_bytes)
504 struct osl_object obj;
505 int ret = osl(osl_get_object(dir_table, row, DT_BYTES, &obj));
508 *num_bytes = *(uint64_t *)obj.data;
512 static int global_list_loop_function(struct osl_row *row, void *data)
514 struct global_list_info *gli = data;
515 union atom_value values[] = {
516 [gla_size] = {.num_value = 0ULL},
517 [gla_files] = {.num_value = 0ULL},
518 [gla_dirname] = {.string_value = NULL}
520 uint64_t num_files, num_bytes;
525 ret = -E_LOOP_COMPLETE;
529 ret = get_num_files_of_row(row, &num_files);
532 values[gla_files].num_value = (long long unsigned)num_files;
534 ret = get_num_bytes_of_row(row, &num_bytes);
537 values[gla_size].num_value = (long long unsigned)num_bytes;
539 ret = get_dir_name_of_row(row, &dirname);
542 values[gla_dirname].string_value = dirname;
544 buf = format_items(gli->fi, values);
546 ret = output("%s", buf);
553 gli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
557 static int print_global_list(struct format_info *fi)
560 enum dir_table_columns sort_column = DT_BYTES;
561 struct global_list_info gli = {
563 .count = select_conf.limit_arg
566 if (!select_conf.no_headers_given) {
567 ret = output("Global list\n");
571 if (select_conf.sort_arg == sort_arg_files)
572 sort_column = DT_FILES;
573 return adu_loop_reverse(dir_table, sort_column, &gli,
574 global_list_loop_function, &gli.ret, &gli.osl_errno);
577 static int print_statistics(struct select_format_info *sli)
581 switch (select_conf.select_mode_arg) {
582 case select_mode_arg_global_list:
583 ret = print_global_list(sli->global_list_fi);
584 free_format_info(sli->global_list_fi);
586 case select_mode_arg_global_summary:
587 ret = print_global_summary(sli->global_summary_fi);
588 free_format_info(sli->global_summary_fi);
590 case select_mode_arg_user_list:
591 ret = print_user_lists();
593 case select_mode_arg_user_summary:
594 ret = print_user_summary(sli->user_summary_fi);
595 free_format_info(sli->user_summary_fi);
598 ERROR_LOG("bad select mode\n");
599 return ERRNO_TO_ERROR(-EINVAL);
602 static int read_uid_file(struct uid_range *admissible_uids)
606 char *filename = get_uid_list_name(), *map;
607 int ret = mmap_full_file(filename, O_RDONLY, (void **)&map, &size, NULL);
611 INFO_LOG("failed to map %s\n", filename);
616 INFO_LOG("found %u uids in %s\n", (unsigned)num_uids, filename);
619 * Compute number of hash table bits. The hash table size must be a
620 * power of two and larger than the number of uids.
623 while (1 << bits < num_uids)
625 create_hash_table(bits);
626 for (n = 0; n < num_uids; n++) {
627 uint32_t uid = read_u32(map + n * sizeof(uid));
628 ret = search_uid(uid, admissible_uids, OPEN_USER_TABLE, NULL);
633 adu_munmap(map, size);
637 int run_select_query(struct uid_range *admissible_uids,
638 struct select_format_info *sfi)
642 if (select_conf.output_given && strcmp(select_conf.output_arg, "-")) {
643 output_file = fopen(select_conf.output_arg, "w");
645 return -ERRNO_TO_ERROR(errno);
647 output_file = stdout;
649 if (select_conf.count_unit_arg != count_unit_arg_h)
650 count_unit_buf[1] = count_unit_abbrevs[select_conf.count_unit_arg];
652 count_unit_buf[0] = '\0';
653 if (select_conf.size_unit_arg != size_unit_arg_h)
654 size_unit_buf[1] = size_unit_abbrevs[select_conf.size_unit_arg];
656 size_unit_buf[0] = '\0';
658 ret = open_dir_table(0);
662 ret = read_uid_file(admissible_uids);
666 ret = print_statistics(sfi);
669 if (output_file != stdout)
674 /* return: < 0: error, >0: OK, == 0: help given */
675 int parse_select_options(char *string, struct select_cmdline_parser_params *params,
676 struct uid_range **admissible_uids, struct select_format_info *sfi)
681 if (conf.select_options_given) {
685 ret = create_argv(string, &argv);
689 ret = select_cmdline_parser_ext(argc, argv, &select_conf, params);
693 if (select_conf.help_given || select_conf.detailed_help_given)
697 ret = parse_uid_arg(select_conf.uid_arg, admissible_uids);
700 ret = parse_format_string(select_conf.user_summary_format_arg,
701 user_summary_atoms, &sfi->user_summary_fi);
704 ret = parse_format_string(select_conf.global_summary_format_arg,
705 global_summary_atoms, &sfi->global_summary_fi);
707 goto global_summary_err;
708 ret = parse_format_string(select_conf.global_list_format_arg,
709 global_list_atoms, &sfi->global_list_fi);
711 goto global_list_err;
714 free_format_info(sfi->global_summary_fi);
716 free_format_info(sfi->user_summary_fi);
719 line = select_conf.detailed_help_given?
720 select_args_info_detailed_help : select_args_info_help;
722 output_file = stdout;
723 for (; *line; line++) {
724 ret = output("%s\n", *line);
733 struct uid_range *admissible_uids = NULL;
734 struct select_format_info sfi;
736 struct select_cmdline_parser_params params = {
740 .check_ambiguity = 1,
744 select_cmdline_parser_init(&select_conf);
745 ret = parse_select_options(conf.select_options_arg, ¶ms,
746 &admissible_uids, &sfi);
747 if (ret <= 0) /* do not run query if help was given */
749 return run_select_query(admissible_uids, &sfi);