]> git.tuebingen.mpg.de Git - adu.git/blob - select.c
Add create.c.
[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 "adu.h"
11 #include "gcc-compat.h"
12 #include "cmdline.h"
13 #include "fd.h"
14 #include "string.h"
15 #include "error.h"
16 #include "portable_io.h"
17
18 /** The decimal representation of an uint64_t never exceeds that size. */
19 #define FORMATED_VALUE_SIZE 25
20
21 /* these get filled in by the select command. */
22 static char count_unit_buf[4] = "( )", size_unit_buf[4] = "( )";
23
24 enum global_stats_flags {
25         GSF_PRINT_DIRNAME = 1,
26         GSF_PRINT_BYTES = 2,
27         GSF_PRINT_FILES = 4,
28         GSF_COMPUTE_SUMMARY = 8,
29 };
30
31 struct global_stats_info {
32         uint32_t count;
33         int ret;
34         int osl_errno;
35         enum global_stats_flags flags;
36 };
37
38 enum user_stats_flags {
39         USF_PRINT_DIRNAME = 1,
40         USF_PRINT_BYTES = 2,
41         USF_PRINT_FILES = 4,
42         USF_COMPUTE_SUMMARY = 8,
43 };
44
45 struct user_stats_info {
46         uint32_t count;
47         enum user_stats_flags flags;
48         int ret;
49         int osl_errno;
50         struct user_info *ui;
51 };
52
53 static const uint64_t size_unit_divisors[] = {
54         [size_unit_arg_b] = 1ULL,
55         [size_unit_arg_k] = 1024ULL,
56         [size_unit_arg_m] = 1024ULL * 1024ULL,
57         [size_unit_arg_g] = 1024ULL * 1024ULL * 1024ULL,
58         [size_unit_arg_t] = 1024ULL * 1024ULL * 1024ULL * 1024ULL,
59 };
60
61 static const uint64_t count_unit_divisors[] = {
62
63         [count_unit_arg_n] = 1ULL,
64         [count_unit_arg_k] = 1000ULL,
65         [count_unit_arg_m] = 1000ULL * 1000ULL,
66         [count_unit_arg_g] = 1000ULL * 1000ULL * 1000ULL,
67         [count_unit_arg_t] = 1000ULL * 1000ULL * 1000ULL * 1000ULL,
68 };
69
70 static const char size_unit_abbrevs[] = " BKMGT";
71 static const char count_unit_abbrevs[] = "  kmgt";
72
73 static enum enum_size_unit format_size_value(enum enum_size_unit unit,
74                 uint64_t value, int print_unit, char *result)
75 {
76         enum enum_size_unit u = unit;
77         char unit_buf[2] = "\0\0";
78
79         if (unit == size_unit_arg_h) /* human readable */
80                 for (u = size_unit_arg_b; u < size_unit_arg_t &&
81                                 value > size_unit_divisors[u + 1]; u++)
82                         ; /* nothing */
83         if (print_unit)
84                 unit_buf[0] = size_unit_abbrevs[u];
85         sprintf(result, "%llu%s",
86                 (long long unsigned)value / size_unit_divisors[u], unit_buf);
87         return u;
88 }
89
90 static enum enum_count_unit format_count_value(enum enum_count_unit unit,
91                 uint64_t value, int print_unit, char *result)
92 {
93         enum enum_count_unit u = unit;
94         char unit_buf[2] = "\0\0";
95
96         if (unit == count_unit_arg_h) /* human readable */
97                 for (u = count_unit_arg_n; u < count_unit_arg_t &&
98                                 value > count_unit_divisors[u + 1]; u++)
99                         ; /* nothing */
100         if (print_unit)
101                 unit_buf[0] = count_unit_abbrevs[u];
102         sprintf(result, "%llu%s",
103                 (long long unsigned)value / count_unit_divisors[u], unit_buf);
104         return u;
105 }
106
107 static int get_dir_name_by_number(uint64_t *dirnum, char **name)
108 {
109         char *result = NULL, *tmp;
110         struct osl_row *row;
111         uint64_t val = *dirnum;
112         struct osl_object obj = {.data = &val, .size = sizeof(val)};
113         int ret;
114
115 again:
116         ret = osl(osl_get_row(dir_table, DT_NUM, &obj, &row));
117         if (ret < 0)
118                 goto out;
119         ret = osl(osl_get_object(dir_table, row, DT_NAME, &obj));
120         if (ret < 0)
121                 goto out;
122         if (result) {
123                 tmp = make_message("%s/%s", (char *)obj.data, result);
124                 free(result);
125                 result = tmp;
126         } else
127                 result = adu_strdup((char *)obj.data);
128         ret = osl(osl_get_object(dir_table, row, DT_PARENT_NUM, &obj));
129         if (ret < 0)
130                 goto out;
131         val = *(uint64_t *)obj.data;
132         if (val)
133                 goto again;
134 out:
135         if (ret < 0) {
136                 free(result);
137                 *name = NULL;
138         } else
139                 *name = result;
140         return ret;
141 }
142
143 static int get_dir_name_of_row(struct osl_row *dir_table_row, char **name)
144 {
145         struct osl_object obj;
146         int ret;
147         char *this_dir, *prefix = NULL;
148
149         *name = NULL;
150         ret = osl(osl_get_object(dir_table, dir_table_row, DT_NAME, &obj));
151         if (ret < 0)
152                 return ret;
153         this_dir = adu_strdup((char *)obj.data);
154         ret = osl(osl_get_object(dir_table, dir_table_row, DT_PARENT_NUM, &obj));
155         if (ret < 0)
156                 goto out;
157         if (!*(uint64_t *)obj.data) {
158                 *name = this_dir;
159                 return 1;
160         }
161         ret = get_dir_name_by_number((uint64_t *)obj.data, &prefix);
162         if (ret < 0)
163                 goto out;
164         *name = make_message("%s/%s", prefix, this_dir);
165         free(prefix);
166         ret = 1;
167 out:
168         free(this_dir);
169         return ret;
170 }
171 static int user_stats_loop_function(struct osl_row *row, void *data)
172 {
173         struct user_stats_info *usi = data;
174         struct osl_object obj;
175         int ret, summary = usi->flags & GSF_COMPUTE_SUMMARY;
176         char formated_value[FORMATED_VALUE_SIZE];
177
178         check_signals();
179         if (!usi->count && !summary) {
180                 ret = -E_LOOP_COMPLETE;
181                 goto err;
182         }
183         if (summary || (usi->count && (usi->flags & USF_PRINT_FILES))) {
184                 uint64_t files;
185                 ret = osl(osl_get_object(usi->ui->table, row, UT_FILES, &obj));
186                 if (ret < 0)
187                         goto err;
188                 files = *(uint64_t *)obj.data;
189                 if (usi->count && (usi->flags & USF_PRINT_FILES)) {
190                         format_count_value(conf.count_unit_arg, files,
191                                 conf.count_unit_arg == count_unit_arg_h,
192                                 formated_value);
193                         printf("\t%s%s", formated_value,
194                                 (usi->flags & (USF_PRINT_BYTES | USF_PRINT_DIRNAME))?
195                                         "\t" : "\n"
196                         );
197                 }
198                 if (summary)
199                         usi->ui->files += files;
200         }
201         if (summary || (usi->count && (usi->flags & USF_PRINT_BYTES))) {
202                 uint64_t bytes;
203                 ret = osl(osl_get_object(usi->ui->table, row, UT_BYTES, &obj));
204                 if (ret < 0)
205                         goto err;
206                 bytes = *(uint64_t *)obj.data;
207                 if (usi->count && (usi->flags & USF_PRINT_BYTES)) {
208                         format_size_value(conf.size_unit_arg, bytes,
209                                 conf.size_unit_arg == size_unit_arg_h,
210                                 formated_value);
211                         printf("%s%s%s",
212                                 (usi->flags & USF_PRINT_FILES)? "" : "\t",
213                                 formated_value,
214                                 usi->flags & USF_PRINT_DIRNAME?  "\t" : "\n"
215                         );
216                 }
217                 if (summary) {
218                         usi->ui->bytes += bytes;
219                         usi->ui->dirs++;
220                 }
221
222         }
223         if (usi->count && (usi->flags & USF_PRINT_DIRNAME)) {
224                 char *dirname;
225                 ret = osl(osl_get_object(usi->ui->table, row, UT_DIR_NUM, &obj));
226                 if (ret < 0)
227                         goto err;
228                 ret = get_dir_name_by_number((uint64_t *)obj.data, &dirname);
229                 if (ret < 0)
230                         goto err;
231                 printf("%s%s\n",
232                         (usi->flags & (USF_PRINT_BYTES | USF_PRINT_FILES))? "" : "\t",
233                         dirname);
234                 free(dirname);
235         }
236         if (usi->count > 0)
237                 usi->count--;
238         return 1;
239 err:
240         usi->ret = ret;
241         usi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
242         return -1;
243 }
244
245 static int global_stats_loop_function(struct osl_row *row, void *data)
246 {
247         struct global_stats_info *gsi = data;
248         struct osl_object obj;
249         char *dirname, formated_value[FORMATED_VALUE_SIZE];
250         int ret, summary = gsi->flags & GSF_COMPUTE_SUMMARY;
251
252         check_signals();
253         if (!gsi->count && !summary) {
254                 ret = -E_LOOP_COMPLETE;
255                 goto err;
256         }
257         if (summary || (gsi->count && (gsi->flags & GSF_PRINT_FILES))) {
258                 uint64_t files;
259                 ret = osl(osl_get_object(dir_table, row, DT_FILES, &obj));
260                 if (ret < 0)
261                         goto err;
262                 files = *(uint64_t *)obj.data;
263                 if (gsi->count && (gsi->flags & GSF_PRINT_FILES)) {
264                         format_count_value(conf.count_unit_arg, files,
265                                 conf.count_unit_arg == count_unit_arg_h,
266                                 formated_value);
267                         printf("\t%s%s", formated_value,
268                                 (gsi->flags & (GSF_PRINT_BYTES | GSF_PRINT_DIRNAME))?
269                                 "\t" : "\n");
270                 }
271                 if (summary)
272                         num_files += files;
273         }
274         if (summary || (gsi->count && (gsi->flags & GSF_PRINT_BYTES))) {
275                 uint64_t bytes;
276                 ret = osl(osl_get_object(dir_table, row, DT_BYTES, &obj));
277                 if (ret < 0)
278                         goto err;
279                 bytes = *(uint64_t *)obj.data;
280                 if (gsi->count && (gsi->flags & GSF_PRINT_BYTES)) {
281                         format_size_value(conf.size_unit_arg, bytes,
282                                 conf.size_unit_arg == size_unit_arg_h,
283                                 formated_value);
284                         printf("%s%s%s",
285                                 (gsi->flags & GSF_PRINT_FILES)? "" : "\t",
286                                 formated_value,
287                                 (gsi->flags & GSF_PRINT_DIRNAME)? "\t" : "\n"
288                         );
289                 }
290                 if (summary) {
291                         num_bytes += bytes;
292                         num_dirs++;
293                 }
294         }
295         if (gsi->count && (gsi->flags & GSF_PRINT_DIRNAME)) {
296                 ret = get_dir_name_of_row(row, &dirname);
297                 if (ret < 0)
298                         goto err;
299                 printf("%s%s\n",
300                         (gsi->flags & (GSF_PRINT_BYTES | GSF_PRINT_FILES))? "" : "\t",
301                         dirname);
302                 free(dirname);
303         }
304         if (gsi->count > 0)
305                 gsi->count--;
306         return 1;
307 err:
308         gsi->ret = ret;
309         gsi->osl_errno = (ret == -E_OSL)? osl_errno : 0;
310         return -1;
311 }
312
313 static int check_loop_return(int ret, int loop_ret, int loop_osl_errno)
314 {
315         if (ret >= 0)
316                 return ret;
317         assert(ret == -E_OSL);
318         if (osl_errno != E_OSL_LOOP)
319                 /* error not caused by loop function returning negative. */
320                 return ret;
321         assert(loop_ret < 0);
322         if (loop_ret == -E_LOOP_COMPLETE) /* no error */
323                 return 1;
324         if (loop_ret == -E_OSL) { /* osl error in loop function */
325                 assert(loop_osl_errno);
326                 osl_errno = loop_osl_errno;
327         }
328         return loop_ret;
329 }
330
331 static int adu_loop_reverse(struct osl_table *t, unsigned col_num, void *private_data,
332                 osl_rbtree_loop_func *func, int *loop_ret, int *loop_osl_errno)
333 {
334         int ret = osl(osl_rbtree_loop_reverse(t, col_num, private_data, func));
335         return check_loop_return(ret, *loop_ret, *loop_osl_errno);
336 }
337
338 static void print_global_summary(void)
339 {
340         char d[FORMATED_VALUE_SIZE], f[FORMATED_VALUE_SIZE],
341                 s[FORMATED_VALUE_SIZE];
342         enum enum_count_unit ud, uf;
343         enum enum_size_unit us;
344
345         ud = format_count_value(conf.count_unit_arg, num_dirs, 0, d);
346         uf = format_count_value(conf.count_unit_arg, num_files, 0, f);
347         us = format_size_value(conf.size_unit_arg, num_bytes, 0, s);
348
349         printf("Global summary "
350                 "(dirs(%c)/files(%c)/size(%c))\n"
351                 "\t%s\t%s\t%s\n\n",
352                 count_unit_abbrevs[ud],
353                 count_unit_abbrevs[uf],
354                 size_unit_abbrevs[us],
355                 d, f, s
356         );
357
358 }
359
360 static int print_user_summary_line(struct user_info *ui, __a_unused void *data)
361 {
362         char formated_dir_count[FORMATED_VALUE_SIZE],
363                 formated_file_count[FORMATED_VALUE_SIZE],
364                 formated_bytes[FORMATED_VALUE_SIZE ];
365
366         format_count_value(conf.count_unit_arg, ui->dirs,
367                 conf.count_unit_arg == count_unit_arg_h,
368                 formated_dir_count);
369         format_count_value(conf.count_unit_arg, ui->files,
370                 conf.count_unit_arg == count_unit_arg_h,
371                 formated_file_count);
372         format_size_value(conf.size_unit_arg, ui->bytes,
373                 conf.size_unit_arg == size_unit_arg_h,
374                 formated_bytes);
375         printf("\t%s\t%u\t%s\t%s\t%s\n",
376                 ui->pw_name? ui->pw_name : "?",
377                 (unsigned)ui->uid,
378                 formated_dir_count,
379                 formated_file_count,
380                 formated_bytes
381         );
382         return 1;
383 }
384
385 static void print_user_summary(void)
386 {
387         printf("User summary "
388                 "(pw_name/uid/dirs%s/files%s/size%s):\n",
389                 count_unit_buf, count_unit_buf, size_unit_buf);
390         for_each_admissible_user(print_user_summary_line, NULL);
391 }
392
393 static int print_user_stat(struct user_info *ui, __a_unused void *data)
394 {
395         int ret;
396         struct user_stats_info usi = {
397                 .count = conf.limit_arg,
398                 .ui = ui
399         };
400
401         usi.flags = USF_PRINT_DIRNAME | USF_PRINT_BYTES | USF_COMPUTE_SUMMARY;
402         printf("%s (uid %u), by size%s:\n",
403                 ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid,
404                 size_unit_buf);
405         ret = adu_loop_reverse(ui->table, UT_BYTES, &usi, user_stats_loop_function,
406                 &usi.ret, &usi.osl_errno);
407         if (ret < 0)
408                 return ret;
409         printf("\n%s (uid %u), by file count%s:\n",
410                 ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid,
411                 count_unit_buf);
412         usi.count = conf.limit_arg,
413         usi.flags = USF_PRINT_DIRNAME | USF_PRINT_FILES;
414         ret = adu_loop_reverse(ui->table, UT_FILES, &usi, user_stats_loop_function,
415                 &usi.ret, &usi.osl_errno);
416         if (ret < 0)
417                 return ret;
418         printf("\n");
419         return 1;
420 }
421
422 static int print_user_stats(void)
423 {
424         return for_each_admissible_user(print_user_stat, NULL);
425 }
426
427 static int print_statistics(void)
428 {
429         int ret;
430         struct global_stats_info gsi = {
431                 .count = conf.limit_arg,
432                 .flags = GSF_PRINT_DIRNAME | GSF_PRINT_BYTES | GSF_COMPUTE_SUMMARY
433         };
434
435         printf("By size%s:\n",
436                 size_unit_buf);
437         ret = adu_loop_reverse(dir_table, DT_BYTES, &gsi,
438                 global_stats_loop_function, &gsi.ret, &gsi.osl_errno);
439         if (ret < 0)
440                 return ret;
441         printf("\n");
442
443         gsi.count = conf.limit_arg;
444         gsi.flags = GSF_PRINT_DIRNAME | GSF_PRINT_FILES;
445         printf("By file count%s:\n",
446                 count_unit_buf);
447         ret = adu_loop_reverse(dir_table, DT_FILES, &gsi,
448                 global_stats_loop_function, &gsi.ret, &gsi.osl_errno);
449         if (ret < 0)
450                 return ret;
451         printf("\n");
452         print_global_summary();
453         print_user_stats();
454         print_user_summary();
455         return 1;
456 }
457
458 static int read_uid_file(void)
459 {
460         size_t size;
461         uint32_t n;
462         char *filename = get_uid_list_name(), *map;
463         int ret = mmap_full_file(filename, O_RDONLY, (void **)&map, &size, NULL);
464         unsigned bits;
465
466         if (ret < 0) {
467                 INFO_LOG("failed to map %s\n", filename);
468                 free(filename);
469                 return ret;
470         }
471         num_uids = size / 4;
472         INFO_LOG("found %u uids in %s\n", (unsigned)num_uids, filename);
473         free(filename);
474         /*
475          * Compute number of hash table bits. The hash table size must be a
476          * power of two and larger than the number of uids.
477          */
478         bits = 2;
479         while (1 << bits < num_uids)
480                 bits++;
481         create_hash_table(bits);
482         for (n = 0; n < num_uids; n++) {
483                 uint32_t uid = read_u32(map + n * sizeof(uid));
484                 ret = search_uid(uid, OPEN_USER_TABLE, NULL);
485                 if (ret < 0)
486                         goto out;
487         }
488 out:
489         adu_munmap(map, size);
490         return ret;
491 }
492
493 int com_select(void)
494 {
495         int ret;
496
497         if (conf.count_unit_arg != count_unit_arg_h)
498                 count_unit_buf[1] = count_unit_abbrevs[conf.count_unit_arg];
499         else
500                 count_unit_buf[0] = '\0';
501         if (conf.size_unit_arg != size_unit_arg_h)
502                 size_unit_buf[1] = size_unit_abbrevs[conf.size_unit_arg];
503         else
504                 size_unit_buf[0] = '\0';
505
506         ret = open_dir_table(0);
507         if (ret < 0)
508                 return ret;
509         check_signals();
510         ret = read_uid_file();
511         if (ret < 0)
512                 return ret;
513         check_signals();
514         ret = print_statistics();
515         close_all_tables();
516         return ret;
517 }