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