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