Merge commit 'fml/master'
[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 }