Move the code for the create command to its own file.
[adu.git] / adu.c
1 #include "adu.h"
2 #include <dirent.h> /* readdir() */
3 #include <pwd.h>
4
5 #include "gcc-compat.h"
6 #include "cmdline.h"
7 #include "fd.h"
8 #include "string.h"
9 #include "error.h"
10 #include "portable_io.h"
11
12 DEFINE_ERRLIST;
13 int osl_errno;
14
15 /** In case a signal is received, its number is stored here. */
16 static int signum;
17
18 /** Command line and config file options. */
19 struct gengetopt_args_info conf;
20
21 /** Global dir count. */
22 uint64_t num_dirs = 0;
23 /** Global files count. */
24 uint64_t num_files = 0;
25 /** Global bytes count. */
26 uint64_t num_bytes = 0;
27
28 /** The number of different uids found so far. */
29 uint32_t num_uids = 0;
30
31 /**
32  * Contains info for each user that owns at least one regular file.
33  *
34  * Even users that are not taken into account because of the --uid
35  * option occupy a slot in this hash table. This allows to find out
36  * quicky whether a uid is admissible. And yes, this has to be fast.
37  */
38 struct user_info *uid_hash_table = NULL;
39
40 /**
41  * The table containing the directory names and statistics.
42  */
43 struct osl_table *dir_table = NULL;
44
45 /**
46  * The array of all uid ranges that were given at the command line.
47  */
48 struct uid_range *admissible_uids;
49
50 /** Evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y. */
51 #define NUM_COMPARE(x, y) ((int)((x) < (y)) - (int)((x) > (y)))
52
53 /**
54  * Compare the size of two directories
55  *
56  * \param obj1 Pointer to the first object.
57  * \param obj2 Pointer to the second object.
58  *
59  * This function first compares the size values as usual integers. If they compare as
60  * equal, the address of \a obj1 and \a obj2 are compared. So this compare function
61  * returns zero if and only if \a obj1 and \a obj2 point to the same memory area.
62  */
63 static int size_compare(const struct osl_object *obj1, const struct osl_object *obj2)
64 {
65         uint64_t d1 = *(uint64_t *)obj1->data;
66         uint64_t d2 = *(uint64_t *)obj2->data;
67         int ret = NUM_COMPARE(d2, d1);
68
69         if (ret)
70                 return ret;
71         //INFO_LOG("addresses: %p, %p\n", obj1->data, obj2->data);
72         return NUM_COMPARE(obj2->data, obj1->data);
73 }
74
75 /**
76  * Compare two osl objects pointing to unsigned integers of 64 bit size.
77  *
78  * \param obj1 Pointer to the first integer.
79  * \param obj2 Pointer to the second integer.
80  *
81  * \return The values required for an osl compare function.
82  *
83  * \sa osl_compare_func, osl_hash_compare().
84  */
85 static int uint64_compare(const struct osl_object *obj1,
86                 const struct osl_object *obj2)
87 {
88         uint64_t d1 = read_u64((const char *)obj1->data);
89         uint64_t d2 = read_u64((const char *)obj2->data);
90
91         if (d1 < d2)
92                 return 1;
93         if (d1 > d2)
94                 return -1;
95         return 0;
96 }
97
98 static struct osl_column_description dir_table_cols[] = {
99         [DT_NAME] = {
100                 .storage_type = OSL_MAPPED_STORAGE,
101                 .storage_flags = 0,
102                 .name = "dir",
103         },
104         [DT_NUM] = {
105                 .storage_type = OSL_MAPPED_STORAGE,
106                 .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
107                 .name = "num",
108                 .compare_function = uint64_compare,
109                 .data_size = sizeof(uint64_t)
110         },
111         [DT_PARENT_NUM] = {
112                 .storage_type = OSL_MAPPED_STORAGE,
113                 .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
114                 .name = "parent_num",
115                 .compare_function = size_compare,
116                 .data_size = sizeof(uint64_t)
117         },
118         [DT_BYTES] = {
119                 .storage_type = OSL_MAPPED_STORAGE,
120                 .storage_flags =  OSL_RBTREE | OSL_FIXED_SIZE,
121                 .compare_function = size_compare,
122                 .name = "num_bytes",
123                 .data_size = sizeof(uint64_t)
124         },
125         [DT_FILES] = {
126                 .storage_type = OSL_MAPPED_STORAGE,
127                 .storage_flags =  OSL_RBTREE | OSL_FIXED_SIZE,
128                 .compare_function = size_compare,
129                 .name = "num_files",
130                 .data_size = sizeof(uint64_t)
131         }
132 };
133
134 static struct osl_table_description dir_table_desc = {
135         .name = "dir_table",
136         .num_columns = NUM_DT_COLUMNS,
137         .flags = 0,
138         .column_descriptions = dir_table_cols,
139 };
140
141 static struct osl_column_description user_table_cols[] = {
142         [UT_DIR_NUM] = {
143                 .storage_type = OSL_MAPPED_STORAGE,
144                 .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE,
145                 .name = "dir_num",
146                 .compare_function = uint64_compare,
147                 .data_size = sizeof(uint64_t)
148         },
149         [UT_BYTES] = {
150                 .storage_type = OSL_MAPPED_STORAGE,
151                 .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE,
152                 .compare_function = size_compare,
153                 .name = "num_bytes",
154                 .data_size = sizeof(uint64_t)
155         },
156         [UT_FILES] = {
157                 .storage_type = OSL_MAPPED_STORAGE,
158                 .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE,
159                 .compare_function = size_compare,
160                 .name = "num_files",
161                 .data_size = sizeof(uint64_t)
162         },
163 };
164
165 static int check_uid_arg(const char *arg, uint32_t *uid)
166 {
167         const uint32_t max = ~0U;
168         /*
169          * we need an 64-bit int for string -> uid conversion because strtoll()
170          * returns a signed value.
171          */
172         int64_t val;
173         int ret = atoi64(arg, &val);
174
175         if (ret < 0)
176                 return ret;
177         if (val < 0 || val > max)
178                 return -ERRNO_TO_ERROR(EINVAL);
179         *uid = val;
180         return 1;
181 }
182
183 static int parse_uid_range(const char *orig_arg, struct uid_range *ur)
184 {
185         int ret;
186         char *arg = adu_strdup(orig_arg), *p = strchr(arg, '-');
187
188         if (!p || p == arg) { /* -42 or 42 */
189                 ret = check_uid_arg(p? p + 1 : arg, &ur->high);
190                 if (ret < 0)
191                         goto out;
192                 ur->low = p? 0 : ur->high;
193                 ret = 1;
194                 goto out;
195         }
196         /* 42- or 42-4711 */
197         *p = '\0';
198         p++;
199         ret = check_uid_arg(arg, &ur->low);
200         if (ret < 0)
201                 goto out;
202         ur->high = ~0U;
203         if (*p) { /* 42-4711 */
204                 ret = check_uid_arg(p, &ur->high);
205                 if (ret < 0)
206                         goto out;
207         }
208         if (ur->low > ur->high)
209                 ret = -ERRNO_TO_ERROR(EINVAL);
210 out:
211         if (ret < 0)
212                 ERROR_LOG("bad uid option: %s\n", orig_arg);
213         else
214                 INFO_LOG("admissible uid range: %u - %u\n", ur->low,
215                         ur->high);
216         free(arg);
217         return ret;
218 }
219
220 /**
221  * The log function.
222  *
223  * \param ll Loglevel.
224  * \param fml Usual format string.
225  *
226  * All XXX_LOG() macros use this function.
227  */
228 __printf_2_3 void __log(int ll, const char* fmt,...)
229 {
230         va_list argp;
231         FILE *outfd;
232         struct tm *tm;
233         time_t t1;
234         char str[255] = "";
235
236         if (ll < conf.loglevel_arg)
237                 return;
238         outfd = stderr;
239         time(&t1);
240         tm = localtime(&t1);
241         strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
242         fprintf(outfd, "%s ", str);
243         va_start(argp, fmt);
244         vfprintf(outfd, fmt, argp);
245         va_end(argp);
246 }
247
248 static int open_user_table(struct user_info *ui, int create)
249 {
250         int ret;
251         struct passwd *pw;
252
253         ui->desc = adu_malloc(sizeof(*ui->desc));
254         ui->desc->num_columns = NUM_UT_COLUMNS;
255         ui->desc->flags = 0;
256         ui->desc->column_descriptions = user_table_cols;
257         ui->desc->dir = adu_strdup(conf.database_dir_arg);
258         ui->desc->name = make_message("%u", (unsigned)ui->uid);
259         pw = getpwuid(ui->uid);
260         if (pw && pw->pw_name)
261                 ui->pw_name = adu_strdup(pw->pw_name);
262
263         INFO_LOG(".............................uid #%u: %u\n",
264                 (unsigned)num_uids, (unsigned)ui->uid);
265         if (create) {
266                 ret = osl(osl_create_table(ui->desc));
267                 if (ret < 0)
268                         goto err;
269                 num_uids++;
270         }
271         ret = osl(osl_open_table(ui->desc, &ui->table));
272         if (ret < 0)
273                 goto err;
274         return 1;
275 err:
276         free((char *)ui->desc->name);
277         free((char *)ui->desc->dir);
278         free(ui->pw_name);
279         free(ui->desc);
280         ui->desc->name = NULL;
281         ui->desc->dir = NULL;
282         ui->desc = NULL;
283         ui->table = NULL;
284         ui->flags = 0;
285         return ret;
286 }
287
288 #define uid_hash_bits 8
289 uint32_t uid_hash_table_size = 1 << uid_hash_bits;
290 #define PRIME1 0x811c9dc5
291 #define PRIME2 0x01000193
292
293 void create_hash_table(void)
294 {
295         uid_hash_table = adu_calloc(uid_hash_table_size
296                 * sizeof(struct user_info));
297 }
298
299 static void free_hash_table(void)
300 {
301         free(uid_hash_table);
302         uid_hash_table = NULL;
303 }
304
305 static void close_dir_table(void)
306 {
307         int ret;
308
309         if (!dir_table)
310                 return;
311         ret = osl(osl_close_table(dir_table, OSL_MARK_CLEAN));
312         if (ret < 0)
313                 ERROR_LOG("failed to close dir table: %s\n", adu_strerror(-ret));
314         free((char *)dir_table_desc.dir);
315         dir_table = NULL;
316 }
317
318 static void close_user_table(struct user_info *ui)
319 {
320         int ret;
321
322         if (!ui || !ui_used(ui) || !ui_admissible(ui))
323                 return;
324         ret = osl(osl_close_table(ui->table, OSL_MARK_CLEAN));
325         if (ret < 0)
326                 ERROR_LOG("failed to close user table %u: %s\n",
327                         (unsigned) ui->uid, adu_strerror(-ret));
328         free((char *)ui->desc->name);
329         ui->desc->name = NULL;
330         free((char *)ui->desc->dir);
331         ui->desc->dir = NULL;
332         free(ui->pw_name);
333         ui->pw_name = NULL;
334         free(ui->desc);
335         ui->desc = NULL;
336         ui->table = NULL;
337         ui->flags = 0;
338 }
339
340 static void close_user_tables(void)
341 {
342         struct user_info *ui;
343
344         FOR_EACH_USER(ui)
345                 close_user_table(ui);
346 }
347
348 void close_all_tables(void)
349 {
350         close_dir_table();
351         close_user_tables();
352         free_hash_table();
353 }
354
355 static void signal_handler(int s)
356 {
357         signum = s;
358 }
359
360 void check_signals(void)
361 {
362         if (likely(!signum))
363                 return;
364         EMERG_LOG("caught signal %d\n", signum);
365         close_all_tables();
366         exit(EXIT_FAILURE);
367 }
368
369 static int init_signals(void)
370 {
371         if (signal(SIGINT, &signal_handler) == SIG_ERR)
372                 return -E_SIGNAL_SIG_ERR;
373         if (signal(SIGTERM, &signal_handler) == SIG_ERR)
374                 return -E_SIGNAL_SIG_ERR;
375         return 1;
376 }
377
378 /*
379  * We use a hash table of size s=2^uid_hash_bits to map the uids into the
380  * interval [0..s]. Hash collisions are treated by open addressing, i.e.
381  * unused slots in the table are used to store different uids that hash to the
382  * same slot.
383  *
384  * If a hash collision occurs, different slots are successively probed in order
385  * to find an unused slot for the new uid. Probing is implemented via a second
386  * hash function that maps the uid to h=(uid * PRIME2) | 1, which is always an
387  * odd number.
388  *
389  * An odd number is sufficient to make sure each entry of the hash table gets
390  * probed for probe_num between 0 and s-1 because s is a power of two, hence
391  * the second hash value has never a common divisor with the hash table size.
392  * IOW: h is invertible in the ring [0..s].
393  */
394 static uint32_t double_hash(uint32_t uid, uint32_t probe_num)
395 {
396         return (uid * PRIME1 + ((uid * PRIME2) | 1) * probe_num)
397                 % uid_hash_table_size;
398 }
399
400 static int uid_is_admissible(uint32_t uid)
401 {
402         int i;
403
404         for (i = 0; i < conf.uid_given; i++) {
405                 struct uid_range *ur = admissible_uids + i;
406
407                 if (ur->low <= uid && ur->high >= uid)
408                         break;
409         }
410         i = !conf.uid_given || i < conf.uid_given;
411         DEBUG_LOG("uid %u is %sadmissible\n", (unsigned)uid,
412                 i? "" : "not ");
413         return i;
414 }
415
416 int search_uid(uint32_t uid, enum search_uid_flags flags,
417                 struct user_info **ui_ptr)
418 {
419         uint32_t p;
420
421         for (p = 0; p < uid_hash_table_size; p++) {
422                 struct user_info *ui = uid_hash_table + double_hash(uid, p);
423
424                 if (!ui_used(ui)) {
425                         int ret;
426                         if (!flags)
427                                 return -E_BAD_UID;
428                         ui->uid = uid;
429                         ui->flags |= UI_FL_SLOT_USED;
430                         if (!uid_is_admissible(uid))
431                                 return 0;
432                         ui->flags |= UI_FL_ADMISSIBLE;
433                         ret = open_user_table(ui, flags & CREATE_USER_TABLE);
434                         if (ret < 0)
435                                 return ret;
436
437                         if (ui_ptr)
438                                 *ui_ptr = ui;
439                         return 1;
440                 }
441                 if (ui->uid != uid)
442                         continue;
443                 if (ui_ptr)
444                         *ui_ptr = ui;
445                 return 0;
446         }
447         return flags? -E_HASH_TABLE_OVERFLOW : -E_BAD_UID;
448 }
449
450 char *get_uid_list_name(void)
451 {
452         return make_message("%s/uid_list", conf.database_dir_arg);
453 }
454
455 int open_dir_table(int create)
456 {
457         dir_table_desc.dir = adu_strdup(conf.database_dir_arg);
458
459         if (create) {
460                 int ret = osl(osl_create_table(&dir_table_desc));
461                 if (ret < 0) {
462                         free((char *)dir_table_desc.dir);
463                         return ret;
464                 }
465         }
466         return osl(osl_open_table(&dir_table_desc, &dir_table));
467 }
468
469 static int check_args(void)
470 {
471         int i, ret;
472
473         /* remove trailing slashes from base-dir arg */
474         if (conf.base_dir_given) {
475                 size_t len = strlen(conf.base_dir_arg);
476                 for (;;) {
477                         if (!len) /* empty string */
478                                 return -ERRNO_TO_ERROR(EINVAL);
479                         if (!--len) /* length 1 is always OK */
480                                 break;
481                         if (conf.base_dir_arg[len] != '/')
482                                 break; /* no trailing slash, also OK */
483                         conf.base_dir_arg[len] = '\0';
484                 }
485         }
486         if (!conf.uid_given)
487                 return 0;
488         admissible_uids = adu_malloc(conf.uid_given * sizeof(*admissible_uids));
489         for (i = 0; i < conf.uid_given; i++) {
490                 ret = parse_uid_range(conf.uid_arg[i], admissible_uids + i);
491                 if (ret < 0)
492                         goto err;
493         }
494         return 1;
495 err:
496         free(admissible_uids);
497         admissible_uids = NULL;
498         return ret;
499 }
500
501 int main(int argc, char **argv)
502 {
503         int ret;
504         struct cmdline_parser_params params = {
505                 .override = 0,
506                 .initialize = 1,
507                 .check_required = 0,
508                 .check_ambiguity = 0,
509                 .print_errors = 1
510         };
511
512         cmdline_parser_ext(argc, argv, &conf, &params); /* aborts on errors */
513         ret = init_signals();
514         if (ret < 0)
515                 goto out;
516         ret = check_args();
517         if (ret < 0)
518                 goto out;
519         ret = -E_SYNTAX;
520         if (conf.select_given)
521                 ret = com_select();
522         else
523                 ret = com_create();
524         if (ret < 0)
525                 goto out;
526 out:
527         free(admissible_uids);
528         if (ret < 0) {
529                 ERROR_LOG("%s\n", adu_strerror(-ret));
530                 return -EXIT_FAILURE;
531         }
532         return EXIT_SUCCESS;
533 }