Add format string doku, simplify format string handling.
[adu.git] / select.c
1 /*
2  * Copyright (C) 2008 Andre Noll <maan@systemlinux.org>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file select.c The select mode of adu. */
8
9 #include <dirent.h> /* readdir() */
10 #include "format.h"
11 #include "adu.h"
12 #include "gcc-compat.h"
13 #include "cmdline.h"
14 #include "fd.h"
15 #include "string.h"
16 #include "error.h"
17 #include "portable_io.h"
18
19 /* global list */
20 #define GLOBAL_LIST_ATOMS \
21         ATOM(size, SIZE) \
22         ATOM(files, COUNT) \
23         ATOM(dirname, STRING) \
24
25 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
26 struct atom global_list_atoms[] = {
27         GLOBAL_LIST_ATOMS
28         {.name = NULL}
29 };
30 #undef ATOM
31 #define ATOM(x, y) gla_ ## x,
32 enum global_list_atoms {GLOBAL_LIST_ATOMS};
33 #undef ATOM
34
35 /* global summary */
36 #define GLOBAL_SUMMARY_ATOMS \
37         ATOM(dirs, COUNT) \
38         ATOM(files, COUNT) \
39         ATOM(size, SIZE)
40
41 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
42 struct atom global_summary_atoms[] = {
43         GLOBAL_SUMMARY_ATOMS
44         {.name = NULL}
45 };
46 #undef ATOM
47 #define ATOM(x, y) gsa_ ## x,
48 enum global_summary_atoms {GLOBAL_SUMMARY_ATOMS};
49 #undef ATOM
50
51 /* user list */
52 #define USER_LIST_ATOMS \
53         ATOM(pw_name, STRING) \
54         ATOM(uid, ID) \
55         ATOM(size, SIZE) \
56         ATOM(files, COUNT) \
57         ATOM(dirname, STRING) \
58
59 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
60 struct atom user_list_atoms[] = {
61         USER_LIST_ATOMS
62         {.name = NULL}
63 };
64 #undef ATOM
65 #define ATOM(x, y) ula_ ## x,
66 enum user_list_atoms {USER_LIST_ATOMS};
67 #undef ATOM
68
69 /* user summary */
70 #define USER_SUMMARY_ATOMS \
71         ATOM(pw_name, STRING) \
72         ATOM(uid, ID) \
73         ATOM(dirs, COUNT) \
74         ATOM(files, COUNT) \
75         ATOM(size, SIZE)
76
77 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
78 struct atom user_summary_atoms[] = {
79         USER_SUMMARY_ATOMS
80         {.name = NULL}
81 };
82 #undef ATOM
83 #define ATOM(x, y) usa_ ## x,
84 enum user_summary_atoms {USER_SUMMARY_ATOMS};
85 #undef ATOM
86
87 struct global_list_info {
88         uint32_t count;
89         int ret;
90         int osl_errno;
91         struct format_info *fi;
92 };
93
94 struct global_summary_info {
95         /** Global dir count. */
96         uint64_t num_dirs;
97         /** Global files count. */
98         uint64_t num_files;
99         /** Global bytes count. */
100         uint64_t num_bytes;
101         int ret;
102         int osl_errno;
103 };
104
105 struct user_list_info {
106         uint32_t count;
107         struct user_info *ui;
108         struct format_info *fi;
109         int ret;
110         int osl_errno;
111 };
112
113 struct user_summary_info {
114         struct user_info *ui;
115         int ret;
116         int osl_errno;
117 };
118
119
120 static FILE *output_file;
121
122 __printf_1_2 static int output(const char const *fmt, ...)
123 {
124         va_list argp;
125         int ret;
126
127         va_start(argp, fmt);
128         ret = vfprintf(output_file, fmt, argp);
129         va_end(argp);
130         return ret < 0? -E_OUTPUT : 1;
131 }
132
133 static int get_dir_name_by_number(uint64_t *dirnum, char **name)
134 {
135         char *result = NULL, *tmp;
136         struct osl_row *row;
137         uint64_t val = *dirnum;
138         struct osl_object obj;
139         int ret;
140         char *pfx;
141
142 again:
143         obj.data = &val;
144         obj.size = sizeof(val);
145         ret = osl(osl_get_row(dir_table, DT_NUM, &obj, &row));
146         if (ret < 0)
147                 goto out;
148         ret = osl(osl_get_object(dir_table, row, DT_PARENT_NUM, &obj));
149         if (ret < 0)
150                 goto out;
151         val = *(uint64_t *)obj.data;
152         ret = osl(osl_get_object(dir_table, row, DT_NAME, &obj));
153         if (ret < 0)
154                 goto out;
155         pfx = (select_conf.print_base_dir_given || val)? (char *)obj.data :  ".";
156         tmp = make_message("%s/%s", pfx, result? result : "");
157         free(result);
158         result = tmp;
159         if (val)
160                 goto again;
161 out:
162         if (ret < 0) {
163                 free(result);
164                 *name = NULL;
165         } else {
166                 assert(result);
167                 *name = result;
168         }
169         return ret;
170 }
171
172 static int get_dir_name_of_row(struct osl_row *dir_table_row, char **name)
173 {
174         struct osl_object obj;
175         int ret;
176
177         *name = NULL;
178         ret = osl(osl_get_object(dir_table, dir_table_row, DT_NUM, &obj));
179         if (ret < 0)
180                 return ret;
181         return get_dir_name_by_number((uint64_t *)obj.data, name);
182 }
183
184 static int get_dir_name_of_user_row(struct osl_row *user_table_row,
185                 struct user_info *ui, char **dirname)
186 {
187         struct osl_object obj;
188         int ret = osl(osl_get_object(ui->table, user_table_row,
189                 UT_DIR_NUM, &obj));
190
191         if (ret < 0)
192                 return ret;
193         return get_dir_name_by_number((uint64_t *)obj.data, dirname);
194 }
195
196 static int get_num_files_of_row(struct osl_row *row, uint64_t *num_files)
197 {
198         struct osl_object obj;
199         int ret = osl(osl_get_object(dir_table, row, DT_FILES, &obj));
200         if (ret < 0)
201                 return ret;
202         *num_files = *(uint64_t *)obj.data;
203         return 1;
204 }
205
206 static int get_num_user_files(struct osl_row *row, struct user_info *ui,
207                 uint64_t *num_files)
208 {
209         struct osl_object obj;
210         int ret = osl(osl_get_object(ui->table, row, UT_FILES, &obj));
211
212         if (ret < 0)
213                 return ret;
214         *num_files = *(uint64_t *)obj.data;
215         return 1;
216 }
217
218 static int get_num_bytes_of_row(struct osl_row *row, uint64_t *num_bytes)
219 {
220         struct osl_object obj;
221         int ret = osl(osl_get_object(dir_table, row, DT_BYTES, &obj));
222         if (ret < 0)
223                 return ret;
224         *num_bytes = *(uint64_t *)obj.data;
225         return 1;
226 }
227
228 static int get_num_user_bytes(struct osl_row *row, struct user_info *ui,
229                 uint64_t *num_bytes)
230 {
231         struct osl_object obj;
232         int ret = osl(osl_get_object(ui->table, row, UT_BYTES, &obj));
233
234         if (ret < 0)
235                 return ret;
236         *num_bytes = *(uint64_t *)obj.data;
237         return 1;
238 }
239
240 static int check_loop_return(int ret, int loop_ret, int loop_osl_errno)
241 {
242         if (ret >= 0)
243                 return ret;
244         assert(ret == -E_OSL);
245         if (osl_errno != E_OSL_LOOP)
246                 /* error not caused by loop function returning negative. */
247                 return ret;
248         assert(loop_ret < 0);
249         if (loop_ret == -E_LOOP_COMPLETE) /* no error */
250                 return 1;
251         if (loop_ret == -E_OSL) { /* osl error in loop function */
252                 assert(loop_osl_errno);
253                 osl_errno = loop_osl_errno;
254         }
255         return loop_ret;
256 }
257
258 static int adu_loop_reverse(struct osl_table *t, unsigned col_num, void *private_data,
259                 osl_rbtree_loop_func *func, int *loop_ret, int *loop_osl_errno)
260 {
261         int ret = osl(osl_rbtree_loop_reverse(t, col_num, private_data, func));
262         return check_loop_return(ret, *loop_ret, *loop_osl_errno);
263 }
264
265 static int global_summary_loop_function(struct osl_row *row, void *data)
266 {
267         struct global_summary_info *gsi = data;
268         int ret;
269         uint64_t num;
270
271         ret = get_num_files_of_row(row, &num);
272         if (ret < 0)
273                 goto err;
274         gsi->num_files += num;
275
276         ret = get_num_bytes_of_row(row, &num);
277         if (ret < 0)
278                 goto err;
279         gsi->num_bytes += num;
280         gsi->num_dirs++;
281         return 1;
282 err:
283         gsi->ret = ret;
284         gsi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
285         return -1;
286 }
287
288 static int print_global_summary(struct format_info *fi)
289 {
290         int ret;
291         char *buf;
292         struct global_summary_info gsi = {.num_dirs = 0};
293
294         union atom_value values[] = {
295                 [gsa_dirs] = {.num_value = 0ULL},
296                 [gsa_files] = {.num_value =  0ULL},
297                 [gsa_size] = {.num_value =  0ULL}
298         };
299
300         ret = adu_loop_reverse(dir_table, DT_BYTES, &gsi,
301                 global_summary_loop_function, &gsi.ret, &gsi.osl_errno);
302         if (ret < 0)
303                 return ret;
304         values[gsa_dirs].num_value = (long long unsigned)gsi.num_dirs;
305         values[gsa_files].num_value = (long long unsigned)gsi.num_files;
306         values[gsa_size].num_value = (long long unsigned)gsi.num_bytes;
307         if (!select_conf.no_headers_given) {
308                 ret = output("Global summary\n");
309                 if (ret < 0)
310                         return ret;
311         }
312         buf = format_items(fi, values);
313         ret = output("%s", buf);
314         free(buf);
315         return ret;
316 }
317
318 static int user_summary_loop_function(struct osl_row *row, void *data)
319 {
320         struct user_summary_info *usi = data;
321         uint64_t num;
322         int ret;
323
324         ret = get_num_user_files(row, usi->ui, &num);
325         if (ret < 0)
326                 goto err;
327         usi->ui->files += num;
328         ret = get_num_user_bytes(row, usi->ui, &num);
329         if (ret < 0)
330                 goto err;
331         usi->ui->bytes += num;
332         usi->ui->dirs++;
333         return 1;
334 err:
335         usi->ret = ret;
336         usi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
337         return -1;
338 }
339
340 static int compute_user_summary(struct user_info *ui, __a_unused void *data)
341 {
342         struct user_summary_info usi = {.ui = ui};
343
344         return adu_loop_reverse(ui->table, UT_BYTES, &usi, user_summary_loop_function,
345                 &usi.ret, &usi.osl_errno);
346 }
347
348 static int print_user_summary_line(struct user_info *ui, void *data)
349 {
350         struct format_info *fi = data;
351         union atom_value values[] = {
352                 [usa_pw_name] = {.string_value = ui->pw_name?
353                         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}
358         };
359         char *buf;
360         int ret;
361
362         buf = format_items(fi, values);
363         ret = output("%s", buf);
364         free(buf);
365         return ret;
366 }
367
368 static int name_comp(const void *a, const void *b)
369 {
370         char *x = ((struct user_info *)a)->pw_name;
371         char *y = ((struct user_info *)b)->pw_name;
372
373         if (!x)
374                 return 1;
375         if (!y)
376                 return -1;
377         return strcmp(x, y);
378 }
379
380 static int uid_comp(const void *a, const void *b)
381 {
382         return -NUM_COMPARE(((struct user_info *)a)->uid,
383                 ((struct user_info *)b)->uid);
384 }
385
386 static int dir_count_comp(const void *a, const void *b)
387 {
388         return NUM_COMPARE(((struct user_info *)a)->dirs,
389                 ((struct user_info *)b)->dirs);
390 }
391
392 static int file_count_comp(const void *a, const void *b)
393 {
394         return NUM_COMPARE(((struct user_info *)a)->files,
395                 ((struct user_info *)b)->files);
396 }
397
398 static int size_comp(const void *a, const void *b)
399 {
400         return NUM_COMPARE(((struct user_info *)a)->bytes,
401                 ((struct user_info *)b)->bytes);
402 }
403
404 static int print_user_summary(struct format_info *fi)
405 {
406         /*
407          * The comparators for sorting the user summary.
408          *
409          * This is an array of pointers to functions taking two constant void *
410          * pointers and returning an int.
411          */
412         static int (*summary_comparators[])(const void *, const void *) = {
413                 [user_summary_sort_arg_name] = name_comp,
414                 [user_summary_sort_arg_uid] = uid_comp,
415                 [user_summary_sort_arg_dir_count] = dir_count_comp,
416                 [user_summary_sort_arg_file_count] = file_count_comp,
417                 [user_summary_sort_arg_size] = size_comp,
418         };
419
420         if (!select_conf.no_headers_given) {
421                 int ret = output("User summary\n");
422                 if (ret < 0)
423                         return ret;
424         }
425         int ret = for_each_admissible_user(compute_user_summary, fi);
426         if (ret < 0)
427                 return ret;
428         sort_hash_table(summary_comparators[select_conf.user_summary_sort_arg]);
429         return for_each_admissible_user(print_user_summary_line, fi);
430 }
431
432 static int user_list_loop_function(struct osl_row *row, void *data)
433 {
434         struct user_list_info *uli = data;
435         union atom_value values[] = {
436                 [ula_pw_name] = {.string_value = uli->ui->pw_name?
437                         uli->ui->pw_name : "?"},
438                 [ula_uid] = {.num_value = (long long unsigned)uli->ui->uid},
439                 [ula_files] = {.num_value = 0ULL},
440                 [ula_size] = {.num_value =  0ULL},
441                 [ula_dirname] = {.string_value = NULL}
442         };
443         uint64_t num;
444         int ret;
445         char *dirname, *buf;
446
447         check_signals();
448         ret = -E_LOOP_COMPLETE;
449         if (!uli->count)
450                 goto err;
451
452         ret = get_num_user_files(row, uli->ui, &num);
453         if (ret < 0)
454                 goto err;
455         values[ula_files].num_value = num;
456
457         ret = get_num_user_bytes(row, uli->ui, &num);
458         if (ret < 0)
459                 goto err;
460         values[ula_size].num_value = num;
461
462         ret = get_dir_name_of_user_row(row, uli->ui, &dirname);
463         if (ret < 0)
464                 goto err;
465         values[ula_dirname].string_value = dirname;
466
467         buf = format_items(uli->fi, values);
468         free(dirname);
469         ret = output("%s", buf);
470         free(buf);
471         uli->count--;
472         return ret;
473 err:
474         uli->ret = ret;
475         uli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
476         return -1;
477 }
478
479 static int print_user_list(struct user_info *ui, void *data)
480 {
481         struct format_info *fi = data;
482         int ret;
483         enum user_table_columns sort_column = UT_BYTES;
484         struct user_list_info uli = {
485                 .ui = ui,
486                 .fi = fi,
487                 .count = select_conf.limit_arg
488         };
489
490         if (select_conf.list_sort_arg == list_sort_arg_file_count)
491                 sort_column = UT_FILES;
492
493         if (!select_conf.no_headers_given) {
494                 ret = output("%s (uid %u)\n",
495                         ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid);
496                 if (ret < 0)
497                         return ret;
498         }
499         return adu_loop_reverse(ui->table, sort_column, &uli, user_list_loop_function,
500                 &uli.ret, &uli.osl_errno);
501 }
502
503 static int global_list_loop_function(struct osl_row *row, void *data)
504 {
505         struct global_list_info *gli = data;
506         union atom_value values[] = {
507                 [gla_size] = {.num_value = 0ULL},
508                 [gla_files] = {.num_value =  0ULL},
509                 [gla_dirname] = {.string_value = NULL}
510         };
511         uint64_t num_files, num_bytes;
512         char *dirname, *buf;
513         int ret;
514
515         check_signals();
516         ret = -E_LOOP_COMPLETE;
517         if (!gli->count)
518                 goto err;
519
520         ret = get_num_files_of_row(row, &num_files);
521         if (ret < 0)
522                 goto err;
523         values[gla_files].num_value = (long long unsigned)num_files;
524
525         ret = get_num_bytes_of_row(row, &num_bytes);
526         if (ret < 0)
527                 goto err;
528         values[gla_size].num_value = (long long unsigned)num_bytes;
529
530         ret = get_dir_name_of_row(row, &dirname);
531         if (ret < 0)
532                 goto err;
533         values[gla_dirname].string_value = dirname;
534
535         buf = format_items(gli->fi, values);
536         free(dirname);
537         ret = output("%s", buf);
538         free(buf);
539         if (gli->count > 0)
540                 gli->count--;
541         return ret;
542 err:
543         gli->ret = ret;
544         gli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
545         return -1;
546 }
547
548 static int print_global_list(struct format_info *fi)
549 {
550         int ret;
551         enum dir_table_columns sort_column = DT_BYTES;
552         struct global_list_info gli = {
553                 .fi = fi,
554                 .count = select_conf.limit_arg
555         };
556
557         if (!select_conf.no_headers_given) {
558                 ret = output("Global list\n");
559                 if (ret < 0)
560                         return ret;
561         }
562         if (select_conf.list_sort_arg == list_sort_arg_file_count)
563                 sort_column = DT_FILES;
564         return adu_loop_reverse(dir_table, sort_column, &gli,
565                 global_list_loop_function, &gli.ret, &gli.osl_errno);
566 }
567
568 static int print_statistics(struct format_info *fi)
569 {
570         switch (select_conf.select_mode_arg) {
571                 case select_mode_arg_global_list:
572                         return print_global_list(fi);
573                 case select_mode_arg_global_summary:
574                         return print_global_summary(fi);
575                 case select_mode_arg_user_list:
576                         return for_each_admissible_user(print_user_list, fi);
577                 case select_mode_arg_user_summary:
578                         return print_user_summary(fi);
579         };
580         ERROR_LOG("bad select mode\n");
581         return ERRNO_TO_ERROR(-EINVAL);
582 }
583
584 static int read_uid_file(struct uid_range *admissible_uids)
585 {
586         size_t size;
587         uint32_t n;
588         char *filename = get_uid_list_name(), *map;
589         int ret = mmap_full_file(filename, O_RDONLY, (void **)&map, &size, NULL);
590         unsigned bits;
591
592         if (ret < 0) {
593                 INFO_LOG("failed to map %s\n", filename);
594                 free(filename);
595                 return ret;
596         }
597         num_uids = size / 4;
598         INFO_LOG("found %u uids in %s\n", (unsigned)num_uids, filename);
599         free(filename);
600         /*
601          * Compute number of hash table bits. The hash table size must be a
602          * power of two and larger than the number of uids.
603          */
604         bits = 2;
605         while (1 << bits < num_uids)
606                 bits++;
607         create_hash_table(bits);
608         for (n = 0; n < num_uids; n++) {
609                 uint32_t uid = read_u32(map + n * sizeof(uid));
610                 ret = search_uid(uid, admissible_uids, OPEN_USER_TABLE, NULL);
611                 if (ret < 0)
612                         goto out;
613         }
614 out:
615         adu_munmap(map, size);
616         return ret;
617 }
618
619 int run_select_query(struct uid_range *admissible_uids, struct format_info *fi)
620 {
621         int ret;
622
623         if (select_conf.output_given && strcmp(select_conf.output_arg, "-")) {
624                 output_file = fopen(select_conf.output_arg, "w");
625                 if (!output_file)
626                         return -ERRNO_TO_ERROR(errno);
627         } else
628                 output_file = stdout;
629
630         ret = open_dir_table(0);
631         if (ret < 0)
632                 goto out;
633         check_signals();
634         ret = read_uid_file(admissible_uids);
635         if (ret < 0)
636                 goto out;
637         check_signals();
638         ret = print_statistics(fi);
639 out:
640         close_all_tables();
641         if (output_file != stdout)
642                 fclose(output_file);
643         return ret;
644 }
645
646 #define GLOBAL_LIST_DFLT_FMT "%(size:r:8) %(files:r:8) %(dirname)\n"
647 #define GLOBAL_SUMMARY_DFLT_FMT "#directories: %(dirs), #files: %(files), size: %(size)\n\n"
648 #define USER_LIST_DFLT_FMT "%(size:r:5) %(files:r:5) %(dirname)\n"
649 #define USER_SUMMARY_DFLT_FMT "%(pw_name:l:16) %(uid:r:5) %(dirs:r:5) %(files:r:5) %(size:r:5)\n"
650
651 /* return: < 0: error, >0: OK, == 0: help given */
652 int parse_select_options(char *string, struct select_cmdline_parser_params *params,
653                 struct uid_range **admissible_uids, struct format_info **fi)
654 {
655         int ret;
656         const char **line;
657         char *fmt = NULL;
658         struct atom *atoms;
659
660         if (conf.select_options_given) {
661                 int argc;
662                 char **argv;
663
664                 ret = create_argv(string, &argv);
665                 if (ret < 0)
666                         return ret;
667                 argc = ret;
668                 ret = select_cmdline_parser_ext(argc, argv, &select_conf, params);
669                 free_argv(argv);
670                 if (ret)
671                         return -E_SYNTAX;
672                 if (select_conf.help_given || select_conf.detailed_help_given)
673                         goto help;
674                 fmt = select_conf.format_arg;
675
676         }
677         ret = parse_uid_arg(select_conf.uid_arg, admissible_uids);
678         if (ret < 0)
679                 return ret;
680
681         switch (select_conf.select_mode_arg) {
682                 case select_mode_arg_global_list:
683                         if (!fmt)
684                                 fmt = GLOBAL_LIST_DFLT_FMT;
685                         atoms = global_list_atoms;
686                 case select_mode_arg_global_summary:
687                         if (!fmt)
688                                 fmt = GLOBAL_SUMMARY_DFLT_FMT;
689                         atoms = global_summary_atoms;
690                 case select_mode_arg_user_list:
691                         if (!fmt)
692                                 fmt = USER_LIST_DFLT_FMT;
693                         atoms = user_list_atoms;
694                 case select_mode_arg_user_summary:
695                         if (!fmt)
696                                 fmt = USER_SUMMARY_DFLT_FMT;
697                         atoms = user_summary_atoms;
698         };
699         return parse_format_string(fmt, atoms, fi);
700 help:
701         line = select_conf.detailed_help_given?
702                 select_args_info_detailed_help : select_args_info_help;
703         if (!output_file)
704                 output_file = stdout;
705         for (; *line; line++) {
706                 ret = output("%s\n", *line);
707                 if (ret < 0)
708                         return ret;
709         }
710         return 0;
711 }
712
713 int com_select(void)
714 {
715         struct uid_range *admissible_uids = NULL;
716         int ret;
717         struct format_info *fi;
718         struct select_cmdline_parser_params params = {
719                 .override = 1,
720                 .initialize = 1,
721                 .check_required = 1,
722                 .check_ambiguity = 1,
723                 .print_errors = 1
724         };
725
726         select_cmdline_parser_init(&select_conf);
727         ret = parse_select_options(conf.select_options_arg, &params,
728                 &admissible_uids, &fi);
729         if (ret <= 0) /* do not run query if help was given */
730                 return ret;
731         ret = run_select_query(admissible_uids, fi);
732         free_format_info(fi);
733         return ret;
734 }