]>
Commit | Line | Data |
---|---|---|
793bd4d9 | 1 | /* Create simple DB database from textual input. |
0549fbba | 2 | Copyright (C) 1996-2013 Free Software Foundation, Inc. |
793bd4d9 UD |
3 | This file is part of the GNU C Library. |
4 | Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. | |
5 | ||
6 | The GNU C Library is free software; you can redistribute it and/or | |
41bdb6e2 AJ |
7 | modify it under the terms of the GNU Lesser General Public |
8 | License as published by the Free Software Foundation; either | |
9 | version 2.1 of the License, or (at your option) any later version. | |
793bd4d9 UD |
10 | |
11 | The GNU C Library is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
41bdb6e2 | 14 | Lesser General Public License for more details. |
793bd4d9 | 15 | |
41bdb6e2 | 16 | You should have received a copy of the GNU Lesser General Public |
59ba27a6 PE |
17 | License along with the GNU C Library; if not, see |
18 | <http://www.gnu.org/licenses/>. */ | |
793bd4d9 UD |
19 | |
20 | #include <argp.h> | |
9ee76b5a | 21 | #include <assert.h> |
793bd4d9 | 22 | #include <ctype.h> |
793bd4d9 UD |
23 | #include <errno.h> |
24 | #include <error.h> | |
25 | #include <fcntl.h> | |
9ee76b5a | 26 | #include <inttypes.h> |
793bd4d9 UD |
27 | #include <libintl.h> |
28 | #include <locale.h> | |
9ee76b5a | 29 | #include <search.h> |
bea9b193 | 30 | #include <stdbool.h> |
793bd4d9 | 31 | #include <stdio.h> |
793bd4d9 UD |
32 | #include <stdlib.h> |
33 | #include <string.h> | |
9ee76b5a UD |
34 | #include <unistd.h> |
35 | #include <sys/mman.h> | |
e468f8a3 | 36 | #include <sys/param.h> |
793bd4d9 | 37 | #include <sys/stat.h> |
e468f8a3 | 38 | #include <sys/uio.h> |
2666d441 | 39 | #include "nss_db/nss_db.h" |
793bd4d9 UD |
40 | |
41 | /* Get libc version number. */ | |
42 | #include "../version.h" | |
43 | ||
9ee76b5a UD |
44 | /* The hashing function we use. */ |
45 | #include "../intl/hash-string.h" | |
46 | ||
47 | /* SELinux support. */ | |
48 | #ifdef HAVE_SELINUX | |
49 | # include <selinux/selinux.h> | |
50 | #endif | |
51 | ||
80694780 TS |
52 | #ifndef MAP_POPULATE |
53 | # define MAP_POPULATE 0 | |
54 | #endif | |
55 | ||
793bd4d9 UD |
56 | #define PACKAGE _libc_intl_domainname |
57 | ||
9ee76b5a UD |
58 | /* List of data bases. */ |
59 | struct database | |
60 | { | |
61 | char dbid; | |
9f2da732 | 62 | bool extra_string; |
9ee76b5a UD |
63 | struct database *next; |
64 | void *entries; | |
65 | size_t nentries; | |
9ee76b5a | 66 | size_t nhashentries; |
2666d441 UD |
67 | stridx_t *hashtable; |
68 | size_t keystrlen; | |
69 | stridx_t *keyidxtab; | |
9ee76b5a UD |
70 | char *keystrtab; |
71 | } *databases; | |
72 | static size_t ndatabases; | |
a9e836b0 | 73 | static size_t nhashentries_total; |
9ee76b5a UD |
74 | static size_t valstrlen; |
75 | static void *valstrtree; | |
76 | static char *valstrtab; | |
9f2da732 | 77 | static size_t extrastrlen; |
9ee76b5a UD |
78 | |
79 | /* Database entry. */ | |
80 | struct dbentry | |
81 | { | |
9ee76b5a UD |
82 | stridx_t validx; |
83 | uint32_t hashval; | |
84 | char str[0]; | |
85 | }; | |
86 | ||
87 | /* Stored string entry. */ | |
88 | struct valstrentry | |
89 | { | |
90 | stridx_t idx; | |
9f2da732 | 91 | bool extra_string; |
9ee76b5a UD |
92 | char str[0]; |
93 | }; | |
94 | ||
95 | ||
9ee76b5a UD |
96 | /* True if any entry has been added. */ |
97 | static bool any_dbentry; | |
98 | ||
793bd4d9 UD |
99 | /* If non-zero convert key to lower case. */ |
100 | static int to_lowercase; | |
101 | ||
102 | /* If non-zero print content of input file, one entry per line. */ | |
103 | static int do_undo; | |
104 | ||
105 | /* If non-zero do not print informational messages. */ | |
106 | static int be_quiet; | |
107 | ||
108 | /* Name of output file. */ | |
109 | static const char *output_name; | |
110 | ||
793bd4d9 UD |
111 | /* Name and version of program. */ |
112 | static void print_version (FILE *stream, struct argp_state *state); | |
113 | void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; | |
114 | ||
115 | /* Definitions of arguments for argp functions. */ | |
116 | static const struct argp_option options[] = | |
117 | { | |
118 | { "fold-case", 'f', NULL, 0, N_("Convert key to lower case") }, | |
119 | { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") }, | |
120 | { "quiet", 'q', NULL, 0, | |
121 | N_("Do not print messages while building database") }, | |
122 | { "undo", 'u', NULL, 0, | |
123 | N_("Print content of database file, one entry a line") }, | |
9f2da732 UD |
124 | { "generated", 'g', N_("CHAR"), 0, |
125 | N_("Generated line not part of iteration") }, | |
793bd4d9 UD |
126 | { NULL, 0, NULL, 0, NULL } |
127 | }; | |
128 | ||
129 | /* Short description of program. */ | |
9ee76b5a | 130 | static const char doc[] = N_("Create simple database from textual input."); |
793bd4d9 UD |
131 | |
132 | /* Strings for arguments in help texts. */ | |
133 | static const char args_doc[] = N_("\ | |
134 | INPUT-FILE OUTPUT-FILE\n-o OUTPUT-FILE INPUT-FILE\n-u INPUT-FILE"); | |
135 | ||
136 | /* Prototype for option handler. */ | |
cbc85992 | 137 | static error_t parse_opt (int key, char *arg, struct argp_state *state); |
793bd4d9 UD |
138 | |
139 | /* Function to print some extra text in the help message. */ | |
cbc85992 | 140 | static char *more_help (int key, const char *text, void *input); |
793bd4d9 UD |
141 | |
142 | /* Data structure to communicate with argp functions. */ | |
143 | static struct argp argp = | |
144 | { | |
145 | options, parse_opt, args_doc, doc, NULL, more_help | |
146 | }; | |
147 | ||
148 | ||
9f2da732 UD |
149 | /* List of databases which are not part of the iteration table. */ |
150 | static struct db_option | |
151 | { | |
152 | char dbid; | |
153 | struct db_option *next; | |
154 | } *db_options; | |
155 | ||
156 | ||
793bd4d9 | 157 | /* Prototypes for local functions. */ |
9ee76b5a | 158 | static int process_input (FILE *input, const char *inname, |
cbc85992 | 159 | int to_lowercase, int be_quiet); |
9ee76b5a UD |
160 | static int print_database (int fd); |
161 | static void compute_tables (void); | |
162 | static int write_output (int fd); | |
163 | ||
164 | /* SELinux support. */ | |
165 | #ifdef HAVE_SELINUX | |
166 | /* Set the SELinux file creation context for the given file. */ | |
167 | static void set_file_creation_context (const char *outname, mode_t mode); | |
168 | static void reset_file_creation_context (void); | |
169 | #else | |
170 | # define set_file_creation_context(_outname,_mode) | |
171 | # define reset_file_creation_context() | |
172 | #endif | |
173 | ||
174 | ||
175 | /* External functions. */ | |
6ff444c4 | 176 | #include <programs/xmalloc.h> |
793bd4d9 UD |
177 | |
178 | ||
179 | int | |
cbc85992 | 180 | main (int argc, char *argv[]) |
793bd4d9 UD |
181 | { |
182 | const char *input_name; | |
183 | FILE *input_file; | |
793bd4d9 | 184 | int remaining; |
2666d441 | 185 | int mode = 0644; |
793bd4d9 UD |
186 | |
187 | /* Set locale via LC_ALL. */ | |
188 | setlocale (LC_ALL, ""); | |
189 | ||
190 | /* Set the text message domain. */ | |
191 | textdomain (_libc_intl_domainname); | |
192 | ||
193 | /* Initialize local variables. */ | |
194 | input_name = NULL; | |
195 | ||
196 | /* Parse and process arguments. */ | |
197 | argp_parse (&argp, argc, argv, 0, &remaining, NULL); | |
198 | ||
199 | /* Determine file names. */ | |
200 | if (do_undo || output_name != NULL) | |
201 | { | |
202 | if (remaining + 1 != argc) | |
203 | { | |
204 | wrong_arguments: | |
205 | error (0, 0, gettext ("wrong number of arguments")); | |
206 | argp_help (&argp, stdout, ARGP_HELP_SEE, | |
207 | program_invocation_short_name); | |
208 | exit (1); | |
209 | } | |
210 | input_name = argv[remaining]; | |
211 | } | |
212 | else | |
213 | { | |
214 | if (remaining + 2 != argc) | |
215 | goto wrong_arguments; | |
216 | ||
217 | input_name = argv[remaining++]; | |
218 | output_name = argv[remaining]; | |
219 | } | |
220 | ||
221 | /* Special handling if we are asked to print the database. */ | |
222 | if (do_undo) | |
223 | { | |
9ee76b5a UD |
224 | int fd = open (input_name, O_RDONLY); |
225 | if (fd == -1) | |
226 | error (EXIT_FAILURE, errno, gettext ("cannot open database file `%s'"), | |
227 | input_name); | |
793bd4d9 | 228 | |
9ee76b5a | 229 | int status = print_database (fd); |
793bd4d9 | 230 | |
9ee76b5a | 231 | close (fd); |
793bd4d9 UD |
232 | |
233 | return status; | |
234 | } | |
235 | ||
236 | /* Open input file. */ | |
237 | if (strcmp (input_name, "-") == 0 || strcmp (input_name, "/dev/stdin") == 0) | |
238 | input_file = stdin; | |
239 | else | |
240 | { | |
9ee76b5a | 241 | struct stat64 st; |
793bd4d9 | 242 | |
9ee76b5a | 243 | input_file = fopen64 (input_name, "r"); |
793bd4d9 UD |
244 | if (input_file == NULL) |
245 | error (EXIT_FAILURE, errno, gettext ("cannot open input file `%s'"), | |
246 | input_name); | |
247 | ||
248 | /* Get the access rights from the source file. The output file should | |
249 | have the same. */ | |
9ee76b5a | 250 | if (fstat64 (fileno (input_file), &st) >= 0) |
793bd4d9 UD |
251 | mode = st.st_mode & ACCESSPERMS; |
252 | } | |
253 | ||
793bd4d9 | 254 | /* Start the real work. */ |
9ee76b5a | 255 | int status = process_input (input_file, input_name, to_lowercase, be_quiet); |
793bd4d9 UD |
256 | |
257 | /* Close files. */ | |
258 | if (input_file != stdin) | |
259 | fclose (input_file); | |
9ee76b5a UD |
260 | |
261 | /* No need to continue when we did not read the file successfully. */ | |
262 | if (status != EXIT_SUCCESS) | |
263 | return status; | |
264 | ||
265 | /* Bail out if nothing is to be done. */ | |
266 | if (!any_dbentry) | |
2666d441 UD |
267 | { |
268 | if (be_quiet) | |
269 | return EXIT_SUCCESS; | |
270 | else | |
271 | error (EXIT_SUCCESS, 0, gettext ("no entries to be processed")); | |
272 | } | |
9ee76b5a UD |
273 | |
274 | /* Compute hash and string tables. */ | |
275 | compute_tables (); | |
276 | ||
277 | /* Open output file. This must not be standard output so we don't | |
278 | handle "-" and "/dev/stdout" special. */ | |
279 | char *tmp_output_name; | |
280 | if (asprintf (&tmp_output_name, "%s.XXXXXX", output_name) == -1) | |
281 | error (EXIT_FAILURE, errno, gettext ("cannot create temporary file name")); | |
282 | ||
283 | set_file_creation_context (output_name, mode); | |
284 | int fd = mkstemp (tmp_output_name); | |
285 | reset_file_creation_context (); | |
286 | if (fd == -1) | |
287 | error (EXIT_FAILURE, errno, gettext ("cannot create temporary file")); | |
9ee76b5a UD |
288 | |
289 | status = write_output (fd); | |
290 | ||
291 | if (status == EXIT_SUCCESS) | |
292 | { | |
293 | struct stat64 st; | |
294 | ||
295 | if (fstat64 (fd, &st) == 0) | |
296 | { | |
297 | if ((st.st_mode & ACCESSPERMS) != mode) | |
298 | /* We ignore problems with changing the mode. */ | |
299 | fchmod (fd, mode); | |
300 | } | |
301 | else | |
302 | { | |
303 | error (0, errno, gettext ("cannot stat newly created file")); | |
304 | status = EXIT_FAILURE; | |
305 | } | |
306 | } | |
307 | ||
308 | close (fd); | |
309 | ||
310 | if (status == EXIT_SUCCESS) | |
311 | { | |
312 | if (rename (tmp_output_name, output_name) != 0) | |
313 | { | |
314 | error (0, errno, gettext ("cannot rename temporary file")); | |
315 | status = EXIT_FAILURE; | |
316 | goto do_unlink; | |
317 | } | |
318 | } | |
319 | else | |
320 | do_unlink: | |
321 | unlink (tmp_output_name); | |
793bd4d9 UD |
322 | |
323 | return status; | |
324 | } | |
325 | ||
326 | ||
327 | /* Handle program arguments. */ | |
328 | static error_t | |
329 | parse_opt (int key, char *arg, struct argp_state *state) | |
330 | { | |
9f2da732 UD |
331 | struct db_option *newp; |
332 | ||
793bd4d9 UD |
333 | switch (key) |
334 | { | |
335 | case 'f': | |
336 | to_lowercase = 1; | |
337 | break; | |
338 | case 'o': | |
339 | output_name = arg; | |
340 | break; | |
341 | case 'q': | |
342 | be_quiet = 1; | |
343 | break; | |
344 | case 'u': | |
345 | do_undo = 1; | |
346 | break; | |
9f2da732 UD |
347 | case 'g': |
348 | newp = xmalloc (sizeof (*newp)); | |
349 | newp->dbid = arg[0]; | |
350 | newp->next = db_options; | |
351 | db_options = newp; | |
352 | break; | |
793bd4d9 UD |
353 | default: |
354 | return ARGP_ERR_UNKNOWN; | |
355 | } | |
356 | return 0; | |
357 | } | |
358 | ||
359 | ||
360 | static char * | |
361 | more_help (int key, const char *text, void *input) | |
362 | { | |
8b748aed | 363 | char *tp = NULL; |
793bd4d9 UD |
364 | switch (key) |
365 | { | |
366 | case ARGP_KEY_HELP_EXTRA: | |
367 | /* We print some extra information. */ | |
8b748aed | 368 | if (asprintf (&tp, gettext ("\ |
d40eb37a | 369 | For bug reporting instructions, please see:\n\ |
8b748aed JM |
370 | %s.\n"), REPORT_BUGS_TO) < 0) |
371 | return NULL; | |
372 | return tp; | |
793bd4d9 UD |
373 | default: |
374 | break; | |
375 | } | |
376 | return (char *) text; | |
377 | } | |
378 | ||
379 | /* Print the version information. */ | |
380 | static void | |
381 | print_version (FILE *stream, struct argp_state *state) | |
382 | { | |
8b748aed | 383 | fprintf (stream, "makedb %s%s\n", PKGVERSION, VERSION); |
793bd4d9 UD |
384 | fprintf (stream, gettext ("\ |
385 | Copyright (C) %s Free Software Foundation, Inc.\n\ | |
386 | This is free software; see the source for copying conditions. There is NO\n\ | |
387 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ | |
0549fbba | 388 | "), "2013"); |
793bd4d9 UD |
389 | fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); |
390 | } | |
391 | ||
392 | ||
393 | static int | |
9ee76b5a UD |
394 | dbentry_compare (const void *p1, const void *p2) |
395 | { | |
396 | const struct dbentry *d1 = (const struct dbentry *) p1; | |
397 | const struct dbentry *d2 = (const struct dbentry *) p2; | |
398 | ||
399 | if (d1->hashval != d2->hashval) | |
400 | return d1->hashval < d2->hashval ? -1 : 1; | |
401 | ||
2666d441 | 402 | return strcmp (d1->str, d2->str); |
9ee76b5a UD |
403 | } |
404 | ||
405 | ||
406 | static int | |
407 | valstr_compare (const void *p1, const void *p2) | |
408 | { | |
409 | const struct valstrentry *d1 = (const struct valstrentry *) p1; | |
410 | const struct valstrentry *d2 = (const struct valstrentry *) p2; | |
411 | ||
412 | return strcmp (d1->str, d2->str); | |
413 | } | |
414 | ||
415 | ||
416 | static int | |
417 | process_input (input, inname, to_lowercase, be_quiet) | |
793bd4d9 UD |
418 | FILE *input; |
419 | const char *inname; | |
793bd4d9 UD |
420 | int to_lowercase; |
421 | int be_quiet; | |
422 | { | |
423 | char *line; | |
424 | size_t linelen; | |
425 | int status; | |
426 | size_t linenr; | |
427 | ||
428 | line = NULL; | |
429 | linelen = 0; | |
430 | status = EXIT_SUCCESS; | |
431 | linenr = 0; | |
432 | ||
9ee76b5a | 433 | struct database *last_database = NULL; |
793bd4d9 | 434 | |
9ee76b5a UD |
435 | while (!feof_unlocked (input)) |
436 | { | |
437 | ssize_t n = getline (&line, &linelen, input); | |
793bd4d9 UD |
438 | if (n < 0) |
439 | /* This means end of file or some bug. */ | |
440 | break; | |
441 | if (n == 0) | |
442 | /* Short read. Probably interrupted system call. */ | |
443 | continue; | |
444 | ||
445 | ++linenr; | |
446 | ||
447 | if (line[n - 1] == '\n') | |
448 | /* Remove trailing newline. */ | |
449 | line[--n] = '\0'; | |
450 | ||
9ee76b5a | 451 | char *cp = line; |
793bd4d9 UD |
452 | while (isspace (*cp)) |
453 | ++cp; | |
454 | ||
9ee76b5a UD |
455 | if (*cp == '#' || *cp == '\0') |
456 | /* First non-space character in line '#': it's a comment. | |
457 | Also go to the next line if it is empty except for whitespaces. */ | |
793bd4d9 UD |
458 | continue; |
459 | ||
9ee76b5a UD |
460 | /* Skip over the character indicating the database so that it is not |
461 | affected by TO_LOWERCASE. */ | |
462 | char *key = cp++; | |
793bd4d9 UD |
463 | while (*cp != '\0' && !isspace (*cp)) |
464 | { | |
465 | if (to_lowercase) | |
466 | *cp = tolower (*cp); | |
467 | ++cp; | |
468 | } | |
469 | ||
9ee76b5a UD |
470 | if (*cp == '\0') |
471 | /* It's a line without a value field. */ | |
793bd4d9 UD |
472 | continue; |
473 | ||
9ee76b5a UD |
474 | *cp++ = '\0'; |
475 | size_t keylen = cp - key; | |
793bd4d9 UD |
476 | |
477 | while (isspace (*cp)) | |
478 | ++cp; | |
479 | ||
9ee76b5a UD |
480 | char *data = cp; |
481 | size_t datalen = (&line[n] - cp) + 1; | |
793bd4d9 | 482 | |
9ee76b5a UD |
483 | /* Find the database. */ |
484 | if (last_database == NULL || last_database->dbid != key[0]) | |
793bd4d9 | 485 | { |
9ee76b5a UD |
486 | last_database = databases; |
487 | while (last_database != NULL && last_database->dbid != key[0]) | |
488 | last_database = last_database->next; | |
489 | ||
490 | if (last_database == NULL) | |
793bd4d9 | 491 | { |
9ee76b5a UD |
492 | last_database = xmalloc (sizeof (*last_database)); |
493 | last_database->dbid = key[0]; | |
9f2da732 | 494 | last_database->extra_string = false; |
9ee76b5a UD |
495 | last_database->next = databases; |
496 | last_database->entries = NULL; | |
497 | last_database->nentries = 0; | |
498 | last_database->keystrlen = 0; | |
499 | databases = last_database; | |
9f2da732 UD |
500 | |
501 | struct db_option *runp = db_options; | |
502 | while (runp != NULL) | |
503 | if (runp->dbid == key[0]) | |
504 | { | |
505 | last_database->extra_string = true; | |
506 | break; | |
507 | } | |
508 | else | |
509 | runp = runp->next; | |
793bd4d9 | 510 | } |
9ee76b5a | 511 | } |
793bd4d9 | 512 | |
9ee76b5a UD |
513 | /* Skip the database selector. */ |
514 | ++key; | |
515 | --keylen; | |
516 | ||
9ee76b5a UD |
517 | /* Store the data. */ |
518 | struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry) | |
519 | + datalen); | |
9f2da732 UD |
520 | if (last_database->extra_string) |
521 | nentry->idx = extrastrlen; | |
522 | else | |
523 | nentry->idx = valstrlen; | |
524 | nentry->extra_string = last_database->extra_string; | |
9ee76b5a | 525 | memcpy (nentry->str, data, datalen); |
793bd4d9 | 526 | |
9ee76b5a UD |
527 | struct valstrentry **fdata = tsearch (nentry, &valstrtree, |
528 | valstr_compare); | |
529 | if (fdata == NULL) | |
530 | error (EXIT_FAILURE, errno, gettext ("cannot create search tree")); | |
531 | ||
532 | if (*fdata != nentry) | |
533 | { | |
534 | /* We can reuse a string. */ | |
535 | free (nentry); | |
536 | nentry = *fdata; | |
537 | } | |
538 | else | |
9f2da732 UD |
539 | if (last_database->extra_string) |
540 | extrastrlen += datalen; | |
541 | else | |
542 | valstrlen += datalen; | |
9ee76b5a UD |
543 | |
544 | /* Store the key. */ | |
2666d441 UD |
545 | struct dbentry *newp = xmalloc (sizeof (struct dbentry) + keylen); |
546 | newp->validx = nentry->idx; | |
547 | newp->hashval = __hash_string (key); | |
548 | memcpy (newp->str, key, keylen); | |
9ee76b5a UD |
549 | |
550 | struct dbentry **found = tsearch (newp, &last_database->entries, | |
551 | dbentry_compare); | |
552 | if (found == NULL) | |
553 | error (EXIT_FAILURE, errno, gettext ("cannot create search tree")); | |
554 | ||
555 | if (*found != newp) | |
556 | { | |
557 | free (newp); | |
558 | if (!be_quiet) | |
559 | error_at_line (0, 0, inname, linenr, gettext ("duplicate key")); | |
560 | continue; | |
561 | } | |
562 | ||
563 | ++last_database->nentries; | |
564 | last_database->keystrlen += keylen; | |
565 | ||
566 | any_dbentry = true; | |
793bd4d9 UD |
567 | } |
568 | ||
9ee76b5a | 569 | if (ferror_unlocked (input)) |
793bd4d9 UD |
570 | { |
571 | error (0, 0, gettext ("problems while reading `%s'"), inname); | |
572 | status = EXIT_FAILURE; | |
573 | } | |
574 | ||
575 | return status; | |
576 | } | |
577 | ||
578 | ||
9ee76b5a UD |
579 | static void |
580 | copy_valstr (const void *nodep, const VISIT which, const int depth) | |
793bd4d9 | 581 | { |
9ee76b5a UD |
582 | if (which != leaf && which != postorder) |
583 | return; | |
584 | ||
585 | const struct valstrentry *p = *(const struct valstrentry **) nodep; | |
586 | ||
9f2da732 | 587 | strcpy (valstrtab + (p->extra_string ? valstrlen : 0) + p->idx, p->str); |
9ee76b5a UD |
588 | } |
589 | ||
590 | ||
0817d63d SP |
591 | /* Determine if the candidate is prime by using a modified trial division |
592 | algorithm. The candidate must be both odd and greater than 4. */ | |
a9e836b0 UD |
593 | static int |
594 | is_prime (size_t candidate) | |
595 | { | |
a9e836b0 UD |
596 | size_t divn = 3; |
597 | size_t sq = divn * divn; | |
598 | ||
0817d63d SP |
599 | assert (candidate > 4 && candidate % 2 != 0); |
600 | ||
a9e836b0 UD |
601 | while (sq < candidate && candidate % divn != 0) |
602 | { | |
603 | ++divn; | |
604 | sq += 4 * divn; | |
605 | ++divn; | |
606 | } | |
607 | ||
608 | return candidate % divn != 0; | |
609 | } | |
610 | ||
611 | ||
612 | static size_t | |
613 | next_prime (size_t seed) | |
614 | { | |
0817d63d SP |
615 | /* Make sure that we're always greater than 4. */ |
616 | seed = (seed + 4) | 1; | |
a9e836b0 UD |
617 | |
618 | while (!is_prime (seed)) | |
619 | seed += 2; | |
620 | ||
621 | return seed; | |
622 | } | |
623 | ||
624 | ||
9ee76b5a UD |
625 | static void |
626 | compute_tables (void) | |
627 | { | |
9f2da732 UD |
628 | valstrtab = xmalloc (roundup (valstrlen + extrastrlen, sizeof (stridx_t))); |
629 | while ((valstrlen + extrastrlen) % sizeof (stridx_t) != 0) | |
9ee76b5a UD |
630 | valstrtab[valstrlen++] = '\0'; |
631 | twalk (valstrtree, copy_valstr); | |
632 | ||
8de79a24 AS |
633 | static struct database *db; |
634 | for (db = databases; db != NULL; db = db->next) | |
9ee76b5a UD |
635 | if (db->nentries != 0) |
636 | { | |
637 | ++ndatabases; | |
638 | ||
9ee76b5a UD |
639 | /* We simply use an odd number large than twice the number of |
640 | elements to store in the hash table for the size. This gives | |
641 | enough efficiency. */ | |
a9e836b0 | 642 | #define TEST_RANGE 30 |
9f2da732 UD |
643 | size_t nhashentries_min = next_prime (db->nentries < TEST_RANGE |
644 | ? db->nentries | |
645 | : db->nentries * 2 - TEST_RANGE); | |
a9e836b0 UD |
646 | size_t nhashentries_max = MAX (nhashentries_min, db->nentries * 4); |
647 | size_t nhashentries_best = nhashentries_min; | |
648 | size_t chainlength_best = db->nentries; | |
649 | ||
650 | db->hashtable = xmalloc (2 * nhashentries_max * sizeof (stridx_t) | |
651 | + db->keystrlen); | |
652 | db->keyidxtab = db->hashtable + nhashentries_max; | |
653 | db->keystrtab = (char *) (db->keyidxtab + nhashentries_max); | |
654 | ||
8de79a24 AS |
655 | static size_t max_chainlength; |
656 | static char *wp; | |
657 | static size_t nhashentries; | |
658 | static bool copy_string; | |
9ee76b5a UD |
659 | |
660 | void add_key(const void *nodep, const VISIT which, const int depth) | |
661 | { | |
662 | if (which != leaf && which != postorder) | |
663 | return; | |
664 | ||
665 | const struct dbentry *dbe = *(const struct dbentry **) nodep; | |
666 | ||
a9e836b0 UD |
667 | ptrdiff_t stridx; |
668 | if (copy_string) | |
669 | { | |
670 | stridx = wp - db->keystrtab; | |
671 | wp = stpcpy (wp, dbe->str) + 1; | |
672 | } | |
673 | else | |
674 | stridx = 0; | |
9ee76b5a | 675 | |
a9e836b0 UD |
676 | size_t hidx = dbe->hashval % nhashentries; |
677 | size_t hval2 = 1 + dbe->hashval % (nhashentries - 2); | |
9ee76b5a UD |
678 | size_t chainlength = 0; |
679 | ||
2666d441 | 680 | while (db->hashtable[hidx] != ~((stridx_t) 0)) |
9ee76b5a UD |
681 | { |
682 | ++chainlength; | |
a9e836b0 UD |
683 | if ((hidx += hval2) >= nhashentries) |
684 | hidx -= nhashentries; | |
9ee76b5a UD |
685 | } |
686 | ||
9f2da732 UD |
687 | db->hashtable[hidx] = ((db->extra_string ? valstrlen : 0) |
688 | + dbe->validx); | |
2666d441 | 689 | db->keyidxtab[hidx] = stridx; |
9ee76b5a UD |
690 | |
691 | max_chainlength = MAX (max_chainlength, chainlength); | |
692 | } | |
793bd4d9 | 693 | |
8de79a24 | 694 | copy_string = false; |
a9e836b0 UD |
695 | nhashentries = nhashentries_min; |
696 | for (size_t cnt = 0; cnt < TEST_RANGE; ++cnt) | |
697 | { | |
698 | memset (db->hashtable, '\xff', nhashentries * sizeof (stridx_t)); | |
699 | ||
700 | max_chainlength = 0; | |
701 | wp = db->keystrtab; | |
702 | ||
703 | twalk (db->entries, add_key); | |
704 | ||
705 | if (max_chainlength == 0) | |
706 | { | |
707 | /* No need to look further, this is as good as it gets. */ | |
708 | nhashentries_best = nhashentries; | |
709 | break; | |
710 | } | |
711 | ||
712 | if (max_chainlength < chainlength_best) | |
713 | { | |
714 | chainlength_best = max_chainlength; | |
715 | nhashentries_best = nhashentries; | |
716 | } | |
717 | ||
718 | nhashentries = next_prime (nhashentries + 1); | |
719 | if (nhashentries > nhashentries_max) | |
720 | break; | |
721 | } | |
722 | ||
723 | /* Recompute the best table again, this time fill in the strings. */ | |
724 | nhashentries = nhashentries_best; | |
725 | memset (db->hashtable, '\xff', | |
726 | 2 * nhashentries_max * sizeof (stridx_t)); | |
727 | copy_string = true; | |
728 | wp = db->keystrtab; | |
9ee76b5a | 729 | |
a9e836b0 | 730 | twalk (db->entries, add_key); |
9ee76b5a | 731 | |
a9e836b0 UD |
732 | db->nhashentries = nhashentries_best; |
733 | nhashentries_total += nhashentries_best; | |
9ee76b5a UD |
734 | } |
735 | } | |
736 | ||
737 | ||
738 | static int | |
739 | write_output (int fd) | |
740 | { | |
741 | struct nss_db_header *header; | |
742 | uint64_t file_offset = (sizeof (struct nss_db_header) | |
743 | + (ndatabases * sizeof (header->dbs[0]))); | |
744 | header = alloca (file_offset); | |
745 | ||
746 | header->magic = NSS_DB_MAGIC; | |
747 | header->ndbs = ndatabases; | |
748 | header->valstroffset = file_offset; | |
749 | header->valstrlen = valstrlen; | |
750 | ||
751 | size_t filled_dbs = 0; | |
2666d441 | 752 | struct iovec iov[2 + ndatabases * 3]; |
9ee76b5a UD |
753 | iov[0].iov_base = header; |
754 | iov[0].iov_len = file_offset; | |
755 | ||
756 | iov[1].iov_base = valstrtab; | |
9f2da732 UD |
757 | iov[1].iov_len = valstrlen + extrastrlen; |
758 | file_offset += iov[1].iov_len; | |
9ee76b5a | 759 | |
a9e836b0 | 760 | size_t keydataoffset = file_offset + nhashentries_total * sizeof (stridx_t); |
9ee76b5a UD |
761 | for (struct database *db = databases; db != NULL; db = db->next) |
762 | if (db->entries != NULL) | |
763 | { | |
764 | assert (file_offset % sizeof (stridx_t) == 0); | |
765 | assert (filled_dbs < ndatabases); | |
766 | ||
767 | header->dbs[filled_dbs].id = db->dbid; | |
9ee76b5a UD |
768 | memset (header->dbs[filled_dbs].pad, '\0', |
769 | sizeof (header->dbs[0].pad)); | |
770 | header->dbs[filled_dbs].hashsize = db->nhashentries; | |
771 | ||
2666d441 | 772 | iov[2 + filled_dbs].iov_base = db->hashtable; |
a9e836b0 | 773 | iov[2 + filled_dbs].iov_len = db->nhashentries * sizeof (stridx_t); |
9ee76b5a | 774 | header->dbs[filled_dbs].hashoffset = file_offset; |
2666d441 | 775 | file_offset += iov[2 + filled_dbs].iov_len; |
9ee76b5a | 776 | |
2666d441 UD |
777 | iov[2 + ndatabases + filled_dbs * 2].iov_base = db->keyidxtab; |
778 | iov[2 + ndatabases + filled_dbs * 2].iov_len | |
a9e836b0 | 779 | = db->nhashentries * sizeof (stridx_t); |
2666d441 UD |
780 | header->dbs[filled_dbs].keyidxoffset = keydataoffset; |
781 | keydataoffset += iov[2 + ndatabases + filled_dbs * 2].iov_len; | |
782 | ||
783 | iov[3 + ndatabases + filled_dbs * 2].iov_base = db->keystrtab; | |
784 | iov[3 + ndatabases + filled_dbs * 2].iov_len = db->keystrlen; | |
785 | header->dbs[filled_dbs].keystroffset = keydataoffset; | |
786 | keydataoffset += iov[3 + ndatabases + filled_dbs * 2].iov_len; | |
9ee76b5a UD |
787 | |
788 | ++filled_dbs; | |
789 | } | |
790 | ||
791 | assert (filled_dbs == ndatabases); | |
2666d441 | 792 | assert (file_offset == (iov[0].iov_len + iov[1].iov_len |
a9e836b0 | 793 | + nhashentries_total * sizeof (stridx_t))); |
2666d441 | 794 | header->allocate = file_offset; |
9ee76b5a | 795 | |
2666d441 | 796 | if (writev (fd, iov, 2 + ndatabases * 3) != keydataoffset) |
793bd4d9 | 797 | { |
9ee76b5a | 798 | error (0, errno, gettext ("failed to write new database file")); |
793bd4d9 UD |
799 | return EXIT_FAILURE; |
800 | } | |
801 | ||
9ee76b5a UD |
802 | return EXIT_SUCCESS; |
803 | } | |
804 | ||
805 | ||
806 | static int | |
807 | print_database (int fd) | |
808 | { | |
809 | struct stat64 st; | |
810 | if (fstat64 (fd, &st) != 0) | |
811 | error (EXIT_FAILURE, errno, gettext ("cannot stat database file")); | |
812 | ||
813 | const struct nss_db_header *header = mmap (NULL, st.st_size, PROT_READ, | |
814 | MAP_PRIVATE|MAP_POPULATE, fd, 0); | |
815 | if (header == MAP_FAILED) | |
816 | error (EXIT_FAILURE, errno, gettext ("cannot map database file")); | |
817 | ||
818 | if (header->magic != NSS_DB_MAGIC) | |
819 | error (EXIT_FAILURE, 0, gettext ("file not a database file")); | |
820 | ||
821 | const char *valstrtab = (const char *) header + header->valstroffset; | |
822 | ||
823 | for (unsigned int dbidx = 0; dbidx < header->ndbs; ++dbidx) | |
793bd4d9 | 824 | { |
2666d441 UD |
825 | const stridx_t *stridxtab |
826 | = ((const stridx_t *) ((const char *) header | |
827 | + header->dbs[dbidx].keyidxoffset)); | |
9ee76b5a | 828 | const char *keystrtab |
2666d441 UD |
829 | = (const char *) header + header->dbs[dbidx].keystroffset; |
830 | const stridx_t *hashtab | |
831 | = (const stridx_t *) ((const char *) header | |
832 | + header->dbs[dbidx].hashoffset); | |
833 | ||
834 | for (uint32_t hidx = 0; hidx < header->dbs[dbidx].hashsize; ++hidx) | |
835 | if (hashtab[hidx] != ~((stridx_t) 0)) | |
836 | printf ("%c%s %s\n", | |
837 | header->dbs[dbidx].id, | |
838 | keystrtab + stridxtab[hidx], | |
839 | valstrtab + hashtab[hidx]); | |
793bd4d9 UD |
840 | } |
841 | ||
9ee76b5a UD |
842 | return EXIT_SUCCESS; |
843 | } | |
844 | ||
845 | ||
846 | #ifdef HAVE_SELINUX | |
847 | static void | |
848 | set_file_creation_context (const char *outname, mode_t mode) | |
849 | { | |
850 | static int enabled; | |
851 | static int enforcing; | |
852 | security_context_t ctx; | |
853 | ||
854 | /* Check if SELinux is enabled, and remember. */ | |
855 | if (enabled == 0) | |
3d7ba52b | 856 | enabled = is_selinux_enabled () ? 1 : -1; |
9ee76b5a UD |
857 | if (enabled < 0) |
858 | return; | |
859 | ||
860 | /* Check if SELinux is enforcing, and remember. */ | |
861 | if (enforcing == 0) | |
862 | enforcing = security_getenforce () ? 1 : -1; | |
863 | ||
864 | /* Determine the context which the file should have. */ | |
865 | ctx = NULL; | |
866 | if (matchpathcon (outname, S_IFREG | mode, &ctx) == 0 && ctx != NULL) | |
793bd4d9 | 867 | { |
9ee76b5a UD |
868 | if (setfscreatecon (ctx) != 0) |
869 | error (enforcing > 0 ? EXIT_FAILURE : 0, 0, | |
870 | gettext ("cannot set file creation context for `%s'"), | |
871 | outname); | |
872 | ||
873 | freecon (ctx); | |
793bd4d9 | 874 | } |
9ee76b5a | 875 | } |
793bd4d9 | 876 | |
9ee76b5a UD |
877 | static void |
878 | reset_file_creation_context (void) | |
879 | { | |
880 | setfscreatecon (NULL); | |
793bd4d9 | 881 | } |
9ee76b5a | 882 | #endif |