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