Move the code for the select command to its own file.
[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();
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 }