Rename error message for E_OUTPUT.
[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 ret;
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 ret;
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         if (ret < 0)
472                 goto err;
473         uli->count--;
474         return ret;
475 err:
476         uli->ret = ret;
477         uli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
478         return ret;
479 }
480
481 static int print_user_list(struct user_info *ui, void *data)
482 {
483         struct format_info *fi = data;
484         int ret;
485         enum user_table_columns sort_column = UT_BYTES;
486         struct user_list_info uli = {
487                 .ui = ui,
488                 .fi = fi,
489                 .count = select_conf.limit_arg
490         };
491
492         if (select_conf.list_sort_arg == list_sort_arg_file_count)
493                 sort_column = UT_FILES;
494
495         if (!select_conf.no_headers_given) {
496                 ret = output("%s (uid %u)\n",
497                         ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid);
498                 if (ret < 0)
499                         return ret;
500         }
501         return adu_loop_reverse(ui->table, sort_column, &uli, user_list_loop_function,
502                 &uli.ret, &uli.osl_errno);
503 }
504
505 static int global_list_loop_function(struct osl_row *row, void *data)
506 {
507         struct global_list_info *gli = data;
508         union atom_value values[] = {
509                 [gla_size] = {.num_value = 0ULL},
510                 [gla_files] = {.num_value =  0ULL},
511                 [gla_dirname] = {.string_value = NULL}
512         };
513         uint64_t num_files, num_bytes;
514         char *dirname, *buf;
515         int ret;
516
517         check_signals();
518         ret = -E_LOOP_COMPLETE;
519         if (!gli->count)
520                 goto err;
521
522         ret = get_num_files_of_row(row, &num_files);
523         if (ret < 0)
524                 goto err;
525         values[gla_files].num_value = (long long unsigned)num_files;
526
527         ret = get_num_bytes_of_row(row, &num_bytes);
528         if (ret < 0)
529                 goto err;
530         values[gla_size].num_value = (long long unsigned)num_bytes;
531
532         ret = get_dir_name_of_row(row, &dirname);
533         if (ret < 0)
534                 goto err;
535         values[gla_dirname].string_value = dirname;
536
537         buf = format_items(gli->fi, values);
538         free(dirname);
539         ret = output("%s", buf);
540         free(buf);
541         if (ret < 0)
542                 goto err;
543         if (gli->count > 0)
544                 gli->count--;
545         return ret;
546 err:
547         gli->ret = ret;
548         gli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
549         return -1;
550 }
551
552 static int print_global_list(struct format_info *fi)
553 {
554         int ret;
555         enum dir_table_columns sort_column = DT_BYTES;
556         struct global_list_info gli = {
557                 .fi = fi,
558                 .count = select_conf.limit_arg
559         };
560
561         if (!select_conf.no_headers_given) {
562                 ret = output("Global list\n");
563                 if (ret < 0)
564                         return ret;
565         }
566         if (select_conf.list_sort_arg == list_sort_arg_file_count)
567                 sort_column = DT_FILES;
568         return adu_loop_reverse(dir_table, sort_column, &gli,
569                 global_list_loop_function, &gli.ret, &gli.osl_errno);
570 }
571
572 static int print_statistics(struct format_info *fi)
573 {
574         switch (select_conf.select_mode_arg) {
575                 case select_mode_arg_global_list:
576                         return print_global_list(fi);
577                 case select_mode_arg_global_summary:
578                         return print_global_summary(fi);
579                 case select_mode_arg_user_list:
580                         return for_each_admissible_user(print_user_list, fi);
581                 case select_mode_arg_user_summary:
582                         return print_user_summary(fi);
583         };
584         ERROR_LOG("bad select mode\n");
585         return -ERRNO_TO_ERROR(-EINVAL);
586 }
587
588 static int read_uid_file(struct uid_range *admissible_uids)
589 {
590         size_t size;
591         uint32_t n;
592         char *filename = get_uid_list_name(), *map;
593         int ret = mmap_full_file(filename, O_RDONLY, (void **)&map, &size, NULL);
594         unsigned bits;
595
596         if (ret < 0) {
597                 INFO_LOG("failed to map %s\n", filename);
598                 free(filename);
599                 return ret;
600         }
601         num_uids = size / 4;
602         INFO_LOG("found %u uids in %s\n", (unsigned)num_uids, filename);
603         free(filename);
604         /*
605          * Compute number of hash table bits. The hash table size must be a
606          * power of two and larger than the number of uids.
607          */
608         bits = 2;
609         while (1 << bits < num_uids)
610                 bits++;
611         create_hash_table(bits);
612         for (n = 0; n < num_uids; n++) {
613                 uint32_t uid = read_u32(map + n * sizeof(uid));
614                 ret = search_uid(uid, admissible_uids, OPEN_USER_TABLE, NULL);
615                 if (ret < 0)
616                         goto out;
617         }
618 out:
619         adu_munmap(map, size);
620         return ret;
621 }
622
623 int run_select_query(struct uid_range *admissible_uids, struct format_info *fi)
624 {
625         int ret;
626
627         if (select_conf.output_given && strcmp(select_conf.output_arg, "-")) {
628                 output_file = fopen(select_conf.output_arg, "w");
629                 if (!output_file)
630                         return -ERRNO_TO_ERROR(errno);
631         } else
632                 output_file = stdout;
633
634         ret = open_dir_table(0);
635         if (ret < 0)
636                 goto out;
637         check_signals();
638         ret = read_uid_file(admissible_uids);
639         if (ret < 0)
640                 goto out;
641         check_signals();
642         ret = print_statistics(fi);
643 out:
644         close_all_tables();
645         if (output_file != stdout)
646                 fclose(output_file);
647         return ret;
648 }
649
650 #define GLOBAL_LIST_DFLT_FMT "%(size:r:8) %(files:r:8) %(dirname)\n"
651 #define GLOBAL_SUMMARY_DFLT_FMT "#directories: %(dirs), #files: %(files), size: %(size)\n\n"
652 #define USER_LIST_DFLT_FMT "%(size:r:5) %(files:r:5) %(dirname)\n"
653 #define USER_SUMMARY_DFLT_FMT "%(pw_name:l:16) %(uid:r:5) %(dirs:r:5) %(files:r:5) %(size:r:5)\n"
654
655 /* return: < 0: error, >0: OK, == 0: help given */
656 int parse_select_options(char *string, struct select_cmdline_parser_params *params,
657                 struct uid_range **admissible_uids, struct format_info **fi)
658 {
659         int ret;
660         const char **line;
661         char *fmt = NULL;
662         struct atom *atoms;
663
664         if (string) {
665                 int argc;
666                 char **argv;
667
668                 ret = create_argv(string, &argv);
669                 if (ret < 0)
670                         return ret;
671                 argc = ret;
672                 ret = select_cmdline_parser_ext(argc, argv, &select_conf, params);
673                 free_argv(argv);
674                 if (ret)
675                         return -E_SYNTAX;
676                 if (select_conf.help_given || select_conf.detailed_help_given)
677                         goto help;
678                 fmt = select_conf.format_arg;
679
680         }
681         ret = parse_uid_arg(select_conf.uid_arg, admissible_uids);
682         if (ret < 0)
683                 return ret;
684
685         if (!fmt)
686                 INFO_LOG("using default format string\n");
687         switch (select_conf.select_mode_arg) {
688                 case select_mode_arg_global_list:
689                         if (!fmt)
690                                 fmt = GLOBAL_LIST_DFLT_FMT;
691                         atoms = global_list_atoms;
692                         break;
693                 case select_mode_arg_global_summary:
694                         if (!fmt)
695                                 fmt = GLOBAL_SUMMARY_DFLT_FMT;
696                         atoms = global_summary_atoms;
697                         break;
698                 case select_mode_arg_user_list:
699                         if (!fmt)
700                                 fmt = USER_LIST_DFLT_FMT;
701                         atoms = user_list_atoms;
702                         break;
703                 case select_mode_arg_user_summary:
704                         if (!fmt)
705                                 fmt = USER_SUMMARY_DFLT_FMT;
706                         atoms = user_summary_atoms;
707                         break;
708                 default:
709                         ERROR_LOG("bad select mode\n");
710                         return -ERRNO_TO_ERROR(-EINVAL);
711         };
712         INFO_LOG("format string: %s\n", fmt);
713         return parse_format_string(fmt, atoms, fi);
714 help:
715         line = select_conf.detailed_help_given?
716                 select_args_info_detailed_help : select_args_info_help;
717         if (!output_file)
718                 output_file = stdout;
719         for (; *line; line++) {
720                 ret = output("%s\n", *line);
721                 if (ret < 0)
722                         return ret;
723         }
724         return 0;
725 }
726
727 int com_select(void)
728 {
729         struct uid_range *admissible_uids = NULL;
730         int ret;
731         struct format_info *fi;
732         struct select_cmdline_parser_params params = {
733                 .override = 1,
734                 .initialize = 1,
735                 .check_required = 1,
736                 .check_ambiguity = 1,
737                 .print_errors = 1
738         };
739
740         select_cmdline_parser_init(&select_conf);
741         ret = parse_select_options(conf.select_options_arg, &params,
742                 &admissible_uids, &fi);
743         if (ret <= 0) /* do not run query if help was given */
744                 return ret;
745         ret = run_select_query(admissible_uids, fi);
746         free_format_info(fi);
747         return ret;
748 }