build: Don't remove adu.png on make clean.
[adu.git] / select.c
1 /*
2  * Copyright (C) 2008 Andre Noll <maan@tuebingen.mpg.de>
3  *
4  * Licensed under the GPL v2. For licencing details see COPYING.
5  */
6
7 /** \file select.c \brief The select mode of adu. */
8
9 #include <dirent.h> /* readdir() */
10 #include <sys/types.h>
11 #include <regex.h>
12
13 #include "format.h"
14 #include "adu.h"
15 #include "gcc-compat.h"
16 #include "cmdline.h"
17 #include "fd.h"
18 #include "string.h"
19 #include "error.h"
20 #include "user.h"
21 #include "select.cmdline.h"
22 #include "select.h"
23
24 /** \cond */
25 /* global list */
26 #define GLOBAL_LIST_ATOMS \
27         ATOM(size, SIZE) \
28         ATOM(files, COUNT) \
29         ATOM(dirname, STRING) \
30
31 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
32 struct atom global_list_atoms[] = {
33         GLOBAL_LIST_ATOMS
34         {.name = NULL}
35 };
36 #undef ATOM
37 #define ATOM(x, y) gla_ ## x,
38 enum global_list_atoms {GLOBAL_LIST_ATOMS};
39 #undef ATOM
40
41 /* global summary */
42 #define GLOBAL_SUMMARY_ATOMS \
43         ATOM(dirs, COUNT) \
44         ATOM(files, COUNT) \
45         ATOM(size, SIZE)
46
47 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
48 struct atom global_summary_atoms[] = {
49         GLOBAL_SUMMARY_ATOMS
50         {.name = NULL}
51 };
52 #undef ATOM
53 #define ATOM(x, y) gsa_ ## x,
54 enum global_summary_atoms {GLOBAL_SUMMARY_ATOMS};
55 #undef ATOM
56
57 /* user list */
58 #define USER_LIST_ATOMS \
59         ATOM(pw_name, STRING) \
60         ATOM(uid, ID) \
61         ATOM(size, SIZE) \
62         ATOM(files, COUNT) \
63         ATOM(dirname, STRING) \
64
65 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
66 struct atom user_list_atoms[] = {
67         USER_LIST_ATOMS
68         {.name = NULL}
69 };
70 #undef ATOM
71 #define ATOM(x, y) ula_ ## x,
72 enum user_list_atoms {USER_LIST_ATOMS};
73 #undef ATOM
74
75 /* user list header */
76 #define USER_LIST_HEADER_TRAILER_ATOMS \
77         ATOM(pw_name, STRING) \
78         ATOM(uid, ID)
79
80 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
81 struct atom user_list_header_trailer_atoms[] = {
82         USER_LIST_HEADER_TRAILER_ATOMS
83         {.name = NULL}
84 };
85 #undef ATOM
86 #define ATOM(x, y) ulha_ ## x,
87 enum user_list_header_trailer_atoms {USER_LIST_HEADER_TRAILER_ATOMS};
88 #undef ATOM
89
90 /* user summary */
91 #define USER_SUMMARY_ATOMS \
92         ATOM(pw_name, STRING) \
93         ATOM(uid, ID) \
94         ATOM(dirs, COUNT) \
95         ATOM(files, COUNT) \
96         ATOM(size, SIZE)
97
98 #define ATOM(x, y) { .name = #x, .type = AT_ ## y},
99 struct atom user_summary_atoms[] = {
100         USER_SUMMARY_ATOMS
101         {.name = NULL}
102 };
103 #undef ATOM
104 #define ATOM(x, y) usa_ ## x,
105 enum user_summary_atoms {USER_SUMMARY_ATOMS};
106 #undef ATOM
107
108 /** \endcond */
109
110 struct global_list_info {
111         uint32_t count;
112         int ret;
113         int osl_errno;
114         struct format_info *fi;
115         regex_t *preg;
116         int inverse_matching;
117 };
118
119 struct global_summary_info {
120         /** Global dir count. */
121         uint64_t num_dirs;
122         /** Global files count. */
123         uint64_t num_files;
124         /** Global bytes count. */
125         uint64_t num_bytes;
126         regex_t *preg;
127         int inverse_matching;
128         int ret;
129         int osl_errno;
130 };
131
132 struct user_list_info {
133         uint32_t count;
134         struct user_info *ui;
135         struct format_info *fi;
136         regex_t *preg;
137         int inverse_matching;
138         int ret;
139         int osl_errno;
140 };
141
142 struct user_list_format_info {
143         struct format_info *fi;
144         struct format_info *header_fi;
145         struct format_info *trailer_fi;
146 };
147
148 struct user_summary_info {
149         struct user_info *ui;
150         /** Total number of files owned by this user. */
151         uint64_t files;
152         /** Total number of bytes owned by this user. */
153         uint64_t bytes;
154         /** Total number of directories that contain at least one file */
155         uint64_t dirs;
156         int ret;
157         int osl_errno;
158         regex_t *preg;
159         int inverse_matching;
160 };
161
162 struct user_summary_loop_data {
163         unsigned num_admissible_users;
164         struct user_summary_info *usis;
165         struct user_summary_info *current;
166         struct format_info *fi;
167 };
168
169 static FILE *output_file;
170
171 __printf_1_2 static int output(const char const *fmt, ...)
172 {
173         va_list argp;
174         int ret;
175
176         va_start(argp, fmt);
177         ret = vfprintf(output_file, fmt, argp);
178         va_end(argp);
179         return ret < 0? -E_OUTPUT : 1;
180 }
181
182 static int get_dir_name_by_number(uint64_t *dirnum, char **name)
183 {
184         char *result = NULL, *tmp;
185         struct osl_row *row;
186         uint64_t val = *dirnum;
187         struct osl_object obj;
188         int ret;
189         char *pfx;
190
191 again:
192         obj.data = &val;
193         obj.size = sizeof(val);
194         ret = osl(osl_get_row(dir_table, DT_NUM, &obj, &row));
195         if (ret < 0)
196                 goto out;
197         ret = osl(osl_get_object(dir_table, row, DT_PARENT_NUM, &obj));
198         if (ret < 0)
199                 goto out;
200         val = *(uint64_t *)obj.data;
201         ret = osl(osl_get_object(dir_table, row, DT_NAME, &obj));
202         if (ret < 0)
203                 goto out;
204         pfx = (select_conf.print_base_dir_given || val)? (char *)obj.data :  ".";
205         tmp = make_message("%s/%s", pfx, result? result : "");
206         free(result);
207         result = tmp;
208         if (val)
209                 goto again;
210 out:
211         if (ret < 0) {
212                 free(result);
213                 *name = NULL;
214         } else {
215                 assert(result);
216                 *name = result;
217         }
218         return ret;
219 }
220
221 static int get_dir_name_of_row(struct osl_row *dir_table_row, char **name)
222 {
223         struct osl_object obj;
224         int ret;
225
226         *name = NULL;
227         ret = osl(osl_get_object(dir_table, dir_table_row, DT_NUM, &obj));
228         if (ret < 0)
229                 return ret;
230         return get_dir_name_by_number((uint64_t *)obj.data, name);
231 }
232
233 static int get_dir_name_of_user_row(struct osl_row *user_table_row,
234                 struct user_info *ui, char **dirname)
235 {
236         struct osl_object obj;
237         int ret = osl(osl_get_object(ui->table, user_table_row,
238                 UT_DIR_NUM, &obj));
239
240         if (ret < 0)
241                 return ret;
242         return get_dir_name_by_number((uint64_t *)obj.data, dirname);
243 }
244
245 static int get_num_files_of_row(struct osl_row *row, uint64_t *num_files)
246 {
247         struct osl_object obj;
248         int ret = osl(osl_get_object(dir_table, row, DT_FILES, &obj));
249         if (ret < 0)
250                 return ret;
251         *num_files = *(uint64_t *)obj.data;
252         return 1;
253 }
254
255 static int get_num_user_files(struct osl_row *row, struct user_info *ui,
256                 uint64_t *num_files)
257 {
258         struct osl_object obj;
259         int ret = osl(osl_get_object(ui->table, row, UT_FILES, &obj));
260
261         if (ret < 0)
262                 return ret;
263         *num_files = *(uint64_t *)obj.data;
264         return 1;
265 }
266
267 static int get_num_bytes_of_row(struct osl_row *row, uint64_t *num_bytes)
268 {
269         struct osl_object obj;
270         int ret = osl(osl_get_object(dir_table, row, DT_BYTES, &obj));
271         if (ret < 0)
272                 return ret;
273         *num_bytes = *(uint64_t *)obj.data;
274         return 1;
275 }
276
277 static int get_num_user_bytes(struct osl_row *row, struct user_info *ui,
278                 uint64_t *num_bytes)
279 {
280         struct osl_object obj;
281         int ret = osl(osl_get_object(ui->table, row, UT_BYTES, &obj));
282
283         if (ret < 0)
284                 return ret;
285         *num_bytes = *(uint64_t *)obj.data;
286         return 1;
287 }
288
289 static void free_regex(regex_t *preg)
290 {
291         if (!preg)
292                 return;
293         regfree(preg);
294         free(preg);
295 }
296
297 static int compile_regex(regex_t **preg, int *invert)
298 {
299         int ret;
300         size_t size;
301         char *buf, *p = select_conf.pattern_arg;
302
303         if (!select_conf.pattern_given || !p[0]) {
304                 *preg = NULL;
305                 return 0;
306         }
307         if (p[0] == '!') {
308                 if (!p[1]) {
309                         *preg = NULL;
310                         return -E_REGEX;
311                 }
312                 *invert = 1;
313                 p++;
314         } else
315                 *invert = 0;
316         *preg = adu_malloc(sizeof(regex_t));
317         ret = regcomp(*preg, p, 0);
318         if (!ret)
319                 return 1;
320         size = regerror(ret, *preg, NULL, 0);
321         buf = adu_malloc(size);
322         regerror(ret, *preg, buf, size);
323         ERROR_LOG("%s\n", buf);
324         free(buf);
325         free_regex(*preg);
326         *preg = NULL;
327         return -E_REGEX;
328 }
329
330 static int dir_is_admissible(char *dirname, regex_t *preg, int inverse_matching)
331 {
332         int ret;
333
334         if (!preg)
335                 return 1;
336         ret = regexec(preg, dirname, 0, NULL, 0);
337         if (ret == REG_NOMATCH && !inverse_matching)
338                 return 0;
339         if (ret != REG_NOMATCH && inverse_matching)
340                 return 0;
341         return 1;
342 }
343
344 static int check_loop_return(int ret, int loop_ret, int loop_osl_errno)
345 {
346         if (ret >= 0)
347                 return ret;
348         assert(ret == -E_OSL);
349         if (osl_errno != E_OSL_LOOP)
350                 /* error not caused by loop function returning negative. */
351                 return ret;
352         assert(loop_ret < 0);
353         if (loop_ret == -E_LOOP_COMPLETE) /* no error */
354                 return 1;
355         if (loop_ret == -E_OSL) { /* osl error in loop function */
356                 assert(loop_osl_errno);
357                 osl_errno = loop_osl_errno;
358         }
359         return loop_ret;
360 }
361
362 static int adu_loop_reverse(struct osl_table *t, unsigned col_num, void *private_data,
363                 osl_rbtree_loop_func *func, int *loop_ret, int *loop_osl_errno)
364 {
365         int ret = osl(osl_rbtree_loop_reverse(t, col_num, private_data, func));
366         return check_loop_return(ret, *loop_ret, *loop_osl_errno);
367 }
368
369 static int global_summary_loop_function(struct osl_row *row, void *data)
370 {
371         struct global_summary_info *gsi = data;
372         int ret;
373         uint64_t num;
374
375         if (gsi->preg) {
376                 char *dirname;
377                 ret = get_dir_name_of_row(row, &dirname);
378                 if (ret < 0)
379                         goto err;
380                 ret = dir_is_admissible(dirname, gsi->preg, gsi->inverse_matching);
381                 free(dirname);
382                 if (!ret)
383                         return 1;
384         }
385
386         ret = get_num_files_of_row(row, &num);
387         if (ret < 0)
388                 goto err;
389         gsi->num_files += num;
390
391         ret = get_num_bytes_of_row(row, &num);
392         if (ret < 0)
393                 goto err;
394         gsi->num_bytes += num;
395         gsi->num_dirs++;
396         return 1;
397 err:
398         gsi->ret = ret;
399         gsi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
400         return ret;
401 }
402
403 static int print_global_summary(struct format_info *fi)
404 {
405         int ret;
406         char *buf;
407         struct global_summary_info gsi = {.num_dirs = 0};
408         char *header = select_conf.header_given? select_conf.header_arg :
409                 "Global summary\n";
410
411         union atom_value values[] = {
412                 [gsa_dirs] = {.num_value = 0ULL},
413                 [gsa_files] = {.num_value =  0ULL},
414                 [gsa_size] = {.num_value =  0ULL}
415         };
416
417         ret = compile_regex(&gsi.preg, &gsi.inverse_matching);
418         if (ret < 0)
419                 return ret;
420         ret = adu_loop_reverse(dir_table, DT_BYTES, &gsi,
421                 global_summary_loop_function, &gsi.ret, &gsi.osl_errno);
422         free_regex(gsi.preg);
423         if (ret < 0)
424                 return ret;
425         values[gsa_dirs].num_value = (long long unsigned)gsi.num_dirs;
426         values[gsa_files].num_value = (long long unsigned)gsi.num_files;
427         values[gsa_size].num_value = (long long unsigned)gsi.num_bytes;
428
429         ret = output("%s", header);
430         if (ret < 0)
431                 return ret;
432         buf = format_items(fi, values);
433         ret = output("%s", buf);
434         free(buf);
435         if (ret < 0)
436                 return ret;
437         return output("%s", select_conf.trailer_arg);
438 }
439
440 /* row: a pointer to a row of the *user* table */
441 static int user_summary_loop_function(struct osl_row *row, void *data)
442 {
443         struct user_summary_info *usi = data;
444         uint64_t num;
445         int ret;
446
447         if (usi->preg) {
448                 char *dirname;
449                 ret = get_dir_name_of_user_row(row, usi->ui, &dirname);
450                 if (ret < 0)
451                         goto err;
452                 ret = dir_is_admissible(dirname, usi->preg, usi->inverse_matching);
453                 free(dirname);
454                 if (!ret)
455                         return 1;
456         }
457         ret = get_num_user_files(row, usi->ui, &num);
458         if (ret < 0)
459                 goto err;
460         usi->files += num;
461         ret = get_num_user_bytes(row, usi->ui, &num);
462         if (ret < 0)
463                 goto err;
464         usi->bytes += num;
465         usi->dirs++;
466         return 1;
467 err:
468         usi->ret = ret;
469         usi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
470         return ret;
471 }
472
473 static int compute_user_summary(struct user_info *ui, void *data)
474 {
475         struct user_summary_loop_data *usld = data;
476         struct user_summary_info *usi = usld->current++;
477         int ret = compile_regex(&usi->preg, &usi->inverse_matching);
478
479         if (ret < 0)
480                 return ret;
481         usi->ui = ui;
482         ret = adu_loop_reverse(ui->table, UT_BYTES, usi, user_summary_loop_function,
483                 &usi->ret, &usi->osl_errno);
484         free_regex(usi->preg);
485         return ret;
486 }
487
488 static int print_user_summary_line(struct user_summary_info *usi,
489                 struct format_info *fi)
490 {
491         struct user_info *ui = usi->ui;
492         union atom_value values[] = {
493                 [usa_pw_name] = {.string_value = ui->pw_name?
494                         ui->pw_name : "?"},
495                 [usa_uid] = {.num_value = (long long unsigned)ui->uid},
496                 [usa_dirs] = {.num_value = (long long unsigned)usi->dirs},
497                 [usa_files] = {.num_value = (long long unsigned)usi->files},
498                 [usa_size] = {.num_value =  (long long unsigned)usi->bytes}
499         };
500         char *buf = format_items(fi, values);
501         int ret = output("%s", buf);
502
503         free(buf);
504         return ret;
505 }
506
507 static int name_comp(const void *a, const void *b)
508 {
509         const struct user_summary_info *x = a, *y = b;
510         char *n1 = x->ui->pw_name;
511         char *n2 = y->ui->pw_name;
512
513         if (!n1)
514                 return 1;
515         if (!n2)
516                 return -1;
517         return strcmp(n1, n2);
518 }
519
520 static int uid_comp(const void *a, const void *b)
521 {
522         const struct user_summary_info *x = a, *y = b;
523         return -NUM_COMPARE(x->ui->uid, y->ui->uid);
524 }
525
526 static int dir_count_comp(const void *a, const void *b)
527 {
528         const struct user_summary_info *x = a, *y = b;
529         return NUM_COMPARE(x->dirs, y->dirs);
530 }
531
532 static int file_count_comp(const void *a, const void *b)
533 {
534         const struct user_summary_info *x = a, *y = b;
535         return NUM_COMPARE(x->files, y->files);
536 }
537
538 static int size_comp(const void *a, const void *b)
539 {
540         const struct user_summary_info *x = a, *y = b;
541         return NUM_COMPARE(x->bytes, y->bytes);
542 }
543
544 static int count_admissible_users(__a_unused struct user_info *ui, void *data)
545 {
546         struct user_summary_loop_data *usld = data;
547         usld->num_admissible_users++;
548         return 1;
549 }
550
551 static int print_user_summary(struct format_info *fi)
552 {
553         int i, ret;
554         int (*comp)(const void *a, const void *b);
555         struct user_summary_loop_data usld = { .fi = fi};
556         char *header = select_conf.header_given? select_conf.header_arg :
557                 "User summary\n";
558
559         ret = output("%s", header);
560         if (ret < 0)
561                 return ret;
562         ret = for_each_admissible_user(count_admissible_users, &usld);
563         if (ret < 0)
564                 return ret;
565         if (usld.num_admissible_users == 0)
566                 return 1;
567         usld.usis = adu_calloc(usld.num_admissible_users
568                 * sizeof(struct user_summary_info));
569         usld.current = usld.usis;
570         ret = for_each_admissible_user(compute_user_summary, &usld);
571         if (ret < 0)
572                 goto out;
573         switch (select_conf.user_summary_sort_arg) {
574         case user_summary_sort_arg_name:
575                 comp = name_comp;
576                 break;
577         case user_summary_sort_arg_uid:
578                 comp = uid_comp;
579                 break;
580         case user_summary_sort_arg_dir_count:
581                 comp = dir_count_comp;
582                 break;
583         case user_summary_sort_arg_file_count:
584                 comp = file_count_comp;
585                 break;
586         case user_summary_sort_arg_size:
587                 comp = size_comp;
588                 break;
589         default: /* this should never happen, but anyway */
590                 comp = size_comp;
591                 break;
592         }
593         qsort(usld.usis, usld.num_admissible_users,
594                 sizeof(struct user_summary_info), comp);
595         for (i = 0; i < usld.num_admissible_users; i++) {
596                 if (select_conf.limit_arg >= 0 && i > select_conf.limit_arg)
597                         break;
598                 ret = print_user_summary_line(usld.usis + i, usld.fi);
599                 if (ret < 0)
600                         goto out;
601         }
602         ret = output("%s", select_conf.trailer_arg);
603 out:
604         free(usld.usis);
605         return ret;
606 }
607
608 static int user_list_loop_function(struct osl_row *row, void *data)
609 {
610         struct user_list_info *uli = data;
611         union atom_value values[] = {
612                 [ula_pw_name] = {.string_value = uli->ui->pw_name?
613                         uli->ui->pw_name : "?"},
614                 [ula_uid] = {.num_value = (long long unsigned)uli->ui->uid},
615                 [ula_files] = {.num_value = 0ULL},
616                 [ula_size] = {.num_value =  0ULL},
617                 [ula_dirname] = {.string_value = NULL}
618         };
619         uint64_t num;
620         int ret;
621         char *dirname = NULL, *buf;
622
623         check_signals();
624         ret = -E_LOOP_COMPLETE;
625         if (!uli->count)
626                 goto err;
627
628         ret = get_dir_name_of_user_row(row, uli->ui, &dirname);
629         if (ret < 0)
630                 goto err;
631         if (!dir_is_admissible(dirname, uli->preg, uli->inverse_matching)) {
632                 free(dirname);
633                 return 1;
634         }
635         values[ula_dirname].string_value = dirname;
636
637         ret = get_num_user_files(row, uli->ui, &num);
638         if (ret < 0)
639                 goto err;
640         values[ula_files].num_value = num;
641
642         ret = get_num_user_bytes(row, uli->ui, &num);
643         if (ret < 0)
644                 goto err;
645         values[ula_size].num_value = num;
646
647         buf = format_items(uli->fi, values);
648         free(dirname);
649         dirname = NULL;
650         ret = output("%s", buf);
651         free(buf);
652         if (ret < 0)
653                 goto err;
654         uli->count--;
655         return ret;
656 err:
657         free(dirname);
658         uli->ret = ret;
659         uli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
660         return ret;
661 }
662
663 static int print_user_list(struct user_info *ui, void *data)
664 {
665         struct user_list_format_info *ulfi = data;
666         int ret;
667         enum user_table_columns sort_column;
668         struct user_list_info uli = {
669                 .ui = ui,
670                 .fi = ulfi->fi,
671                 .count = select_conf.limit_arg
672         };
673         union atom_value header_trailer_values[] = {
674                 [ulha_uid] = {.num_value = (long long unsigned)ui->uid},
675                 [ulha_pw_name] = {.string_value = ui->pw_name?
676                         ui->pw_name : "?"}
677         };
678         char *buf = format_items(ulfi->header_fi, header_trailer_values);
679
680         ret = output("%s", buf);
681         free(buf);
682         if (ret < 0)
683                 return ret;
684         if (select_conf.list_sort_arg == list_sort_arg_file_count)
685                 sort_column = UT_FILES;
686         else
687                 sort_column = UT_BYTES;
688
689         ret = compile_regex(&uli.preg, &uli.inverse_matching);
690         if (ret < 0)
691                 return ret;
692         ret = adu_loop_reverse(ui->table, sort_column, &uli,
693                 user_list_loop_function, &uli.ret, &uli.osl_errno);
694         free_regex(uli.preg);
695         if (ret < 0)
696                 return ret;
697         buf = format_items(ulfi->trailer_fi, header_trailer_values);
698         ret = output("%s", buf);
699         free(buf);
700         return ret;
701 }
702
703 static int print_user_lists(struct format_info *fi)
704 {
705         struct user_list_format_info ulfi = {.fi = fi};
706         char *header_fmt = select_conf.header_given?
707                 select_conf.header_arg : "uid %(uid)(%(pw_name)):\n";
708         char *trailer_fmt = select_conf.trailer_arg;
709         int ret = parse_format_string(header_fmt,
710                 user_list_header_trailer_atoms, &ulfi.header_fi);
711         if (ret < 0)
712                 return ret;
713         ret = parse_format_string(trailer_fmt,
714                 user_list_header_trailer_atoms, &ulfi.trailer_fi);
715         if (ret < 0)
716                 return ret;
717         ret = for_each_admissible_user(print_user_list, &ulfi);
718         free_format_info(ulfi.header_fi);
719         free_format_info(ulfi.trailer_fi);
720         return ret;
721 }
722
723 static int global_list_loop_function(struct osl_row *row, void *data)
724 {
725         struct global_list_info *gli = data;
726         union atom_value values[] = {
727                 [gla_size] = {.num_value = 0ULL},
728                 [gla_files] = {.num_value =  0ULL},
729                 [gla_dirname] = {.string_value = NULL}
730         };
731         uint64_t num_files, num_bytes;
732         char *dirname = NULL, *buf;
733         int ret;
734
735         check_signals();
736         ret = -E_LOOP_COMPLETE;
737         if (!gli->count)
738                 goto err;
739
740         ret = get_dir_name_of_row(row, &dirname);
741         if (ret < 0)
742                 goto err;
743         if (!dir_is_admissible(dirname, gli->preg, gli->inverse_matching)) {
744                 free(dirname);
745                 return 1;
746         }
747         values[gla_dirname].string_value = dirname;
748
749         ret = get_num_files_of_row(row, &num_files);
750         if (ret < 0)
751                 goto err;
752         values[gla_files].num_value = (long long unsigned)num_files;
753
754         ret = get_num_bytes_of_row(row, &num_bytes);
755         if (ret < 0)
756                 goto err;
757         values[gla_size].num_value = (long long unsigned)num_bytes;
758
759         buf = format_items(gli->fi, values);
760         free(dirname);
761         dirname = NULL;
762         ret = output("%s", buf);
763         free(buf);
764         if (ret < 0)
765                 goto err;
766         if (gli->count > 0)
767                 gli->count--;
768         return ret;
769 err:
770         free(dirname);
771         gli->ret = ret;
772         gli->osl_errno = (ret == -E_OSL)? osl_errno : 0;
773         return -1;
774 }
775
776 static int print_global_list(struct format_info *fi)
777 {
778         int ret;
779         enum dir_table_columns sort_column;
780         struct global_list_info gli = {
781                 .fi = fi,
782                 .count = select_conf.limit_arg
783         };
784         char *header = select_conf.header_given?
785                 select_conf.header_arg : "Global list\n";
786
787         ret = output("%s", header);
788         if (ret < 0)
789                 return ret;
790         if (select_conf.list_sort_arg == list_sort_arg_file_count)
791                 sort_column = DT_FILES;
792         else
793                 sort_column = DT_BYTES;
794         ret = compile_regex(&gli.preg, &gli.inverse_matching);
795         if (ret < 0)
796                 return ret;
797         ret = adu_loop_reverse(dir_table, sort_column, &gli,
798                 global_list_loop_function, &gli.ret, &gli.osl_errno);
799         free_regex(gli.preg);
800         if (ret < 0)
801                 return ret;
802         return output("%s", select_conf.trailer_arg);
803 }
804
805 static int print_statistics(struct format_info *fi)
806 {
807         switch (select_conf.select_mode_arg) {
808         case select_mode_arg_global_list:
809                 return print_global_list(fi);
810         case select_mode_arg_global_summary:
811                 return print_global_summary(fi);
812         case select_mode_arg_user_list:
813                 return print_user_lists(fi);
814         case select_mode_arg_user_summary:
815                 return print_user_summary(fi);
816         default:
817                 ERROR_LOG("bad select mode\n");
818                 return -ERRNO_TO_ERROR(EINVAL);
819         };
820 }
821
822 static int open_pipe(char *path)
823 {
824         int p[2], ret;
825         char **argv;
826
827         ret = pipe(p);
828         if (ret < 0)
829                 return ERRNO_TO_ERROR(errno);
830         ret = fork();
831         if (ret < 0)
832                 return ERRNO_TO_ERROR(errno);
833         if (ret) { /* parent */
834                 DEBUG_LOG("created process %d\n", ret);
835                 close(p[0]);
836                 output_file = fdopen(p[1], "w");
837                 if (!output_file)
838                         return ERRNO_TO_ERROR(errno);
839                 return 1;
840         }
841         close(p[1]);
842         if (p[0] != STDIN_FILENO)
843                 dup2(p[0], STDIN_FILENO);
844         DEBUG_LOG("executing %s\n", path);
845         split_args(path, &argv, " \t");
846         execvp(argv[0], argv);
847         ERROR_LOG("error executing %s: %s\n", path,
848                 adu_strerror(ERRNO_TO_ERROR(errno)));
849         _exit(EXIT_FAILURE);
850 }
851
852 static int open_output_stream(void)
853 {
854         char *p;
855         int ret, flags = O_WRONLY | O_CREAT;
856
857         if (!select_conf.output_given)
858                 goto use_stdout;
859         p = select_conf.output_arg;
860         switch (p[0]) {
861         case '\0': /* empty string */
862                 goto bad_output_arg;
863         case '-':
864                 if (!p[1]) /* "-" means stdout */
865                         goto use_stdout;
866                 /* string starting with a dash */
867                 flags |= O_EXCL;
868                 goto open_file;
869         case '>':
870                 if (!p[1]) /* ">" is invalid */
871                         goto bad_output_arg;
872                 if (p[1] != '>') {
873                         p++;
874                         flags |= O_TRUNC;
875                         goto open_file;
876                 }
877                 /* string starting with ">>" */
878                 if (!p[2]) /* ">>" is invalid */
879                         goto bad_output_arg;
880                 flags |= O_APPEND;
881                 p += 2;
882                 goto open_file;
883         case '|':
884                 if (!p[1]) /* "|" is invalid */
885                         goto bad_output_arg;
886                 p++;
887                 return open_pipe(p);
888         default: /* args starts with no magic character */
889                 flags |= O_EXCL;
890                 goto open_file;
891         }
892 use_stdout:
893         output_file = stdout;
894         return 1;
895 bad_output_arg:
896         output_file = NULL;
897         return -E_BAD_OUTPUT_ARG;
898 open_file:
899         /*
900          * glibc's 'x' mode to fopen is not portable, so use open() and
901          * fdopen().
902          */
903         ret = open(p, flags, 0644);
904         if (ret < 0)
905                 return -ERRNO_TO_ERROR(errno);
906         output_file = fdopen(ret, "w");
907         if (!output_file)
908                 return -ERRNO_TO_ERROR(errno);
909         return 1;
910 }
911
912 /**
913  * Execute a select query.
914  *
915  * \param admissible_uids User IDs to take into account.
916  * \param fi Format information.
917  *
918  * Called once in select mode or for each \a run command in interactive mode.
919  *
920  * Open the output stream and the dir table if not already open. For each
921  * admissible uid, the user table is opened if necessary. After these
922  * preparations, the output according to \a select_mode and \a fi is written to
923  * the output stream.
924  *
925  * \return Standard.
926  */
927 int run_select_query(struct uid_range *admissible_uids, struct format_info *fi)
928 {
929         int ret = open_output_stream();
930
931         if (ret < 0)
932                 goto out;
933         ret = open_dir_table(0);
934         if (ret < 0)
935                 goto out;
936         check_signals();
937         ret = open_admissible_user_tables(admissible_uids);
938         if (ret < 0)
939                 goto out;
940         check_signals();
941         ret = print_statistics(fi);
942 out:
943         if (output_file && output_file != stdout) {
944                 fclose(output_file);
945                 output_file = NULL;
946         }
947         return ret;
948 }
949
950 /** Default format string for global_list mode. */
951 #define GLOBAL_LIST_DFLT_FMT "%(size:r:8) %(files:r:8) %(dirname)\n"
952 /** Default format string for global_summary mode. */
953 #define GLOBAL_SUMMARY_DFLT_FMT "#directories: %(dirs), #files: %(files), size: %(size)\n"
954 /** Default format string for user_list mode. */
955 #define USER_LIST_DFLT_FMT "%(size:r:5) %(files:r:5) %(dirname)\n"
956 /** Default format string for user_summary mode. */
957 #define USER_SUMMARY_DFLT_FMT "%(pw_name:l:16) %(uid:r:6) %(dirs:r:5) %(files:r:5) %(size:r:5)\n"
958
959 static int setup_format_string(char *fmt, struct format_info **fi)
960 {
961         struct atom *atoms;
962
963         if (!fmt)
964                 INFO_LOG("using default format string\n");
965         switch (select_conf.select_mode_arg) {
966         case select_mode_arg_global_list:
967                 if (!fmt)
968                         fmt = GLOBAL_LIST_DFLT_FMT;
969                 atoms = global_list_atoms;
970                 break;
971         case select_mode_arg_global_summary:
972                 if (!fmt)
973                         fmt = GLOBAL_SUMMARY_DFLT_FMT;
974                 atoms = global_summary_atoms;
975                 break;
976         case select_mode_arg_user_list:
977                 if (!fmt)
978                         fmt = USER_LIST_DFLT_FMT;
979                 atoms = user_list_atoms;
980                 break;
981         case select_mode_arg_user_summary:
982                 if (!fmt)
983                         fmt = USER_SUMMARY_DFLT_FMT;
984                 atoms = user_summary_atoms;
985                 break;
986         default:
987                 ERROR_LOG("bad select mode\n");
988                 return -ERRNO_TO_ERROR(EINVAL);
989         };
990         INFO_LOG("format string: %s\n", fmt);
991         return parse_format_string(fmt, atoms, fi);
992 }
993
994 /**
995  * Parse a given format string.
996  *
997  * \param string The format string to parse.
998  * \param params gengetopt parameters.
999  * \param admissible_uids The array of admissible uid ranges.
1000  * \param fi The format info to be used with format_items().
1001  *
1002  * If \a string is not \p NULL, it is broken down into its components using
1003  * \ref create_argv() and the resulting argument vector is passed together with
1004  * \a params to gengetopt's command line parser. If --help or --detailed-help
1005  * was specified in \a string, the corresponding help text is printed and the
1006  * function returns zero.
1007  *
1008  * Otherwise, any --uid or --user options are parsed and transformed into an
1009  * array of admissible uids which is returned via \a admissible_uids.
1010  *
1011  * Finally, the format string given by --format (or the default format string
1012  * for the given select mode if no --format option was given in \a string) is
1013  * parsed as well resulting in a format_info structure which is returned via
1014  * \a fi. The caller uses the \a fi pointer later to format each output line.
1015  *
1016  * \return Negative on errors, zero if --help or --detailed-help was given,
1017  * positive otherwise.
1018  *
1019  * \sa format_items().
1020  */
1021 int parse_select_options(char *string, struct select_cmdline_parser_params *params,
1022                 struct uid_range **admissible_uids, struct format_info **fi)
1023 {
1024         int ret, num_uid_ranges;
1025         const char **line;
1026         char *fmt = NULL;
1027
1028         if (string) {
1029                 int argc;
1030                 char **argv;
1031
1032                 ret = create_argv(string, &argv);
1033                 if (ret < 0)
1034                         return ret;
1035                 argc = ret;
1036                 ret = select_cmdline_parser_ext(argc, argv, &select_conf, params);
1037                 free_argv(argv);
1038                 if (ret)
1039                         return -E_SYNTAX;
1040                 if (select_conf.help_given || select_conf.detailed_help_given)
1041                         goto help;
1042                 fmt = select_conf.format_arg;
1043         }
1044         ret = parse_uid_arg(select_conf.uid_arg, admissible_uids);
1045         if (ret < 0)
1046                 return ret;
1047         num_uid_ranges = ret;
1048         ret = append_users(select_conf.user_arg, select_conf.user_given,
1049                 admissible_uids, num_uid_ranges);
1050         if (ret < 0)
1051                 return ret;
1052         return setup_format_string(fmt, fi);
1053 help:
1054         line = select_conf.detailed_help_given?
1055                 select_args_info_detailed_help : select_args_info_help;
1056         if (!output_file)
1057                 output_file = stdout;
1058         for (; *line; line++) {
1059                 ret = output("%s\n", *line);
1060                 if (ret < 0)
1061                         return ret;
1062         }
1063         return 0;
1064 }
1065
1066 /**
1067  * Main function for select mode.
1068  *
1069  * \return Standard.
1070  */
1071 int com_select(void)
1072 {
1073         struct uid_range *admissible_uids = NULL;
1074         int ret;
1075         struct format_info *fi;
1076         struct select_cmdline_parser_params params = {
1077                 .override = 1,
1078                 .initialize = 1,
1079                 .check_required = 1,
1080                 .check_ambiguity = 1,
1081                 .print_errors = 1
1082         };
1083
1084         ret = parse_select_options(conf.select_options_arg, &params,
1085                 &admissible_uids, &fi);
1086         if (ret > 0) {
1087                 ret = read_uid_file();
1088                 if (ret < 0)
1089                         goto out;
1090                 ret = run_select_query(admissible_uids, fi);
1091                 free_format_info(fi);
1092         }
1093 out:
1094         select_cmdline_parser_free(&select_conf);
1095         return ret;
1096 }