e09bce1052abc8f51c1e8cf58948d8440110ead5
[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 void print_id_stats(void)
361 {
362         struct user_info *ui;
363
364         printf("User summary "
365                 "(pw_name/uid/dirs%s/files%s/size%s):\n",
366                 count_unit_buf, count_unit_buf, size_unit_buf);
367         FOR_EACH_USER(ui) {
368                 char formated_dir_count[FORMATED_VALUE_SIZE],
369                         formated_file_count[FORMATED_VALUE_SIZE],
370                         formated_bytes[FORMATED_VALUE_SIZE ];
371                 if (!ui_used(ui) || !ui_admissible(ui))
372                         continue;
373                 format_count_value(conf.count_unit_arg, ui->dirs,
374                         conf.count_unit_arg == count_unit_arg_h,
375                         formated_dir_count);
376                 format_count_value(conf.count_unit_arg, ui->files,
377                         conf.count_unit_arg == count_unit_arg_h,
378                         formated_file_count);
379                 format_size_value(conf.size_unit_arg, ui->bytes,
380                         conf.size_unit_arg == size_unit_arg_h,
381                         formated_bytes);
382                 printf("\t%s\t%u\t%s\t%s\t%s\n",
383                         ui->pw_name? ui->pw_name : "?",
384                         (unsigned)ui->uid,
385                         formated_dir_count,
386                         formated_file_count,
387                         formated_bytes
388                 );
389         }
390 }
391
392 static int print_user_stats(void)
393 {
394         struct user_info *ui;
395         int ret;
396
397         FOR_EACH_USER(ui) {
398                 struct user_stats_info usi = {
399                         .count = conf.limit_arg,
400                         .ui = ui
401                 };
402                 if (!ui_used(ui) || !ui_admissible(ui))
403                         continue;
404                 usi.flags = USF_PRINT_DIRNAME | USF_PRINT_BYTES | USF_COMPUTE_SUMMARY;
405                 printf("%s (uid %u), by size%s:\n",
406                         ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid,
407                         size_unit_buf);
408                 ret = adu_loop_reverse(ui->table, UT_BYTES, &usi, user_stats_loop_function,
409                         &usi.ret, &usi.osl_errno);
410                 if (ret < 0)
411                         return ret;
412                 printf("\n%s (uid %u), by file count%s:\n",
413                         ui->pw_name? ui->pw_name : "?", (unsigned)ui->uid,
414                         count_unit_buf);
415                 usi.count = conf.limit_arg,
416                 usi.flags = USF_PRINT_DIRNAME | USF_PRINT_FILES;
417                 ret = adu_loop_reverse(ui->table, UT_FILES, &usi, user_stats_loop_function,
418                         &usi.ret, &usi.osl_errno);
419                 if (ret < 0)
420                         return ret;
421                 printf("\n");
422         }
423         return 1;
424 }
425
426 static int print_statistics(void)
427 {
428         int ret;
429         struct global_stats_info gsi = {
430                 .count = conf.limit_arg,
431                 .flags = GSF_PRINT_DIRNAME | GSF_PRINT_BYTES | GSF_COMPUTE_SUMMARY
432         };
433
434         printf("By size%s:\n",
435                 size_unit_buf);
436         ret = adu_loop_reverse(dir_table, DT_BYTES, &gsi,
437                 global_stats_loop_function, &gsi.ret, &gsi.osl_errno);
438         if (ret < 0)
439                 return ret;
440         printf("\n");
441
442         gsi.count = conf.limit_arg;
443         gsi.flags = GSF_PRINT_DIRNAME | GSF_PRINT_FILES;
444         printf("By file count%s:\n",
445                 count_unit_buf);
446         ret = adu_loop_reverse(dir_table, DT_FILES, &gsi,
447                 global_stats_loop_function, &gsi.ret, &gsi.osl_errno);
448         if (ret < 0)
449                 return ret;
450         printf("\n");
451         print_global_summary();
452         print_user_stats();
453         print_id_stats();
454         return 1;
455 }
456
457 static int read_uid_file(void)
458 {
459         size_t size;
460         uint32_t n;
461         char *filename = get_uid_list_name(), *map;
462         int ret = mmap_full_file(filename, O_RDONLY, (void **)&map, &size, NULL);
463
464         if (ret < 0) {
465                 INFO_LOG("failed to map %s\n", filename);
466                 free(filename);
467                 return ret;
468         }
469         num_uids = size / 4;
470         INFO_LOG("found %u uids in %s\n", (unsigned)num_uids, filename);
471         free(filename);
472         /* hash table size should be a power of two and larger than the number of uids */
473         uid_hash_table_size = 4;
474         while (uid_hash_table_size < num_uids)
475                 uid_hash_table_size *= 2;
476         create_hash_table();
477         for (n = 0; n < num_uids; n++) {
478                 uint32_t uid = read_u32(map + n * sizeof(uid));
479                 ret = search_uid(uid, OPEN_USER_TABLE, NULL);
480                 if (ret < 0)
481                         goto out;
482         }
483 out:
484         adu_munmap(map, size);
485         return ret;
486 }
487
488 int com_select(void)
489 {
490         int ret;
491
492         if (conf.count_unit_arg != count_unit_arg_h)
493                 count_unit_buf[1] = count_unit_abbrevs[conf.count_unit_arg];
494         else
495                 count_unit_buf[0] = '\0';
496         if (conf.size_unit_arg != size_unit_arg_h)
497                 size_unit_buf[1] = size_unit_abbrevs[conf.size_unit_arg];
498         else
499                 size_unit_buf[0] = '\0';
500
501         ret = open_dir_table(0);
502         if (ret < 0)
503                 return ret;
504         check_signals();
505         ret = read_uid_file();
506         if (ret < 0)
507                 return ret;
508         check_signals();
509         ret = print_statistics();
510         close_all_tables();
511         return ret;
512 }