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