]>
Commit | Line | Data |
---|---|---|
793bd4d9 | 1 | /* Create simple DB database from textual input. |
dff8da6b | 2 | Copyright (C) 1996-2024 Free Software Foundation, Inc. |
793bd4d9 | 3 | This file is part of the GNU C Library. |
793bd4d9 UD |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or | |
41bdb6e2 AJ |
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. | |
793bd4d9 UD |
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 | |
41bdb6e2 | 13 | Lesser General Public License for more details. |
793bd4d9 | 14 | |
41bdb6e2 | 15 | You should have received a copy of the GNU Lesser General Public |
59ba27a6 | 16 | License along with the GNU C Library; if not, see |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
793bd4d9 UD |
18 | |
19 | #include <argp.h> | |
9ee76b5a | 20 | #include <assert.h> |
793bd4d9 | 21 | #include <ctype.h> |
793bd4d9 UD |
22 | #include <errno.h> |
23 | #include <error.h> | |
24 | #include <fcntl.h> | |
9ee76b5a | 25 | #include <inttypes.h> |
793bd4d9 UD |
26 | #include <libintl.h> |
27 | #include <locale.h> | |
820948ed | 28 | #include <scratch_buffer.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 | 34 | #include <unistd.h> |
e054f494 | 35 | #include <stdint.h> |
9ee76b5a | 36 | #include <sys/mman.h> |
e468f8a3 | 37 | #include <sys/param.h> |
793bd4d9 | 38 | #include <sys/stat.h> |
e468f8a3 | 39 | #include <sys/uio.h> |
2666d441 | 40 | #include "nss_db/nss_db.h" |
04726be8 | 41 | #include <libc-diag.h> |
793bd4d9 UD |
42 | |
43 | /* Get libc version number. */ | |
44 | #include "../version.h" | |
45 | ||
9ee76b5a UD |
46 | /* The hashing function we use. */ |
47 | #include "../intl/hash-string.h" | |
48 | ||
49 | /* SELinux support. */ | |
50 | #ifdef HAVE_SELINUX | |
f278835f | 51 | # include <selinux/label.h> |
9ee76b5a UD |
52 | # include <selinux/selinux.h> |
53 | #endif | |
54 | ||
80694780 TS |
55 | #ifndef MAP_POPULATE |
56 | # define MAP_POPULATE 0 | |
57 | #endif | |
58 | ||
793bd4d9 UD |
59 | #define PACKAGE _libc_intl_domainname |
60 | ||
9ee76b5a UD |
61 | /* List of data bases. */ |
62 | struct database | |
63 | { | |
64 | char dbid; | |
9f2da732 | 65 | bool extra_string; |
9ee76b5a UD |
66 | struct database *next; |
67 | void *entries; | |
68 | size_t nentries; | |
9ee76b5a | 69 | size_t nhashentries; |
2666d441 UD |
70 | stridx_t *hashtable; |
71 | size_t keystrlen; | |
72 | stridx_t *keyidxtab; | |
9ee76b5a UD |
73 | char *keystrtab; |
74 | } *databases; | |
75 | static size_t ndatabases; | |
a9e836b0 | 76 | static size_t nhashentries_total; |
9ee76b5a UD |
77 | static size_t valstrlen; |
78 | static void *valstrtree; | |
79 | static char *valstrtab; | |
9f2da732 | 80 | static size_t extrastrlen; |
9ee76b5a UD |
81 | |
82 | /* Database entry. */ | |
83 | struct dbentry | |
84 | { | |
9ee76b5a UD |
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; | |
9f2da732 | 94 | bool extra_string; |
9ee76b5a UD |
95 | char str[0]; |
96 | }; | |
97 | ||
98 | ||
9ee76b5a UD |
99 | /* True if any entry has been added. */ |
100 | static bool any_dbentry; | |
101 | ||
793bd4d9 UD |
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 | ||
793bd4d9 UD |
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") }, | |
9f2da732 UD |
127 | { "generated", 'g', N_("CHAR"), 0, |
128 | N_("Generated line not part of iteration") }, | |
793bd4d9 UD |
129 | { NULL, 0, NULL, 0, NULL } |
130 | }; | |
131 | ||
132 | /* Short description of program. */ | |
9ee76b5a | 133 | static const char doc[] = N_("Create simple database from textual input."); |
793bd4d9 UD |
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. */ | |
cbc85992 | 140 | static error_t parse_opt (int key, char *arg, struct argp_state *state); |
793bd4d9 UD |
141 | |
142 | /* Function to print some extra text in the help message. */ | |
cbc85992 | 143 | static char *more_help (int key, const char *text, void *input); |
793bd4d9 UD |
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 | ||
9f2da732 UD |
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 | ||
793bd4d9 | 160 | /* Prototypes for local functions. */ |
9ee76b5a | 161 | static int process_input (FILE *input, const char *inname, |
cbc85992 | 162 | int to_lowercase, int be_quiet); |
9ee76b5a UD |
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. */ | |
6ff444c4 | 179 | #include <programs/xmalloc.h> |
793bd4d9 UD |
180 | |
181 | ||
182 | int | |
cbc85992 | 183 | main (int argc, char *argv[]) |
793bd4d9 UD |
184 | { |
185 | const char *input_name; | |
186 | FILE *input_file; | |
793bd4d9 | 187 | int remaining; |
2666d441 | 188 | int mode = 0644; |
793bd4d9 UD |
189 | |
190 | /* Set locale via LC_ALL. */ | |
191 | setlocale (LC_ALL, ""); | |
192 | ||
193 | /* Set the text message domain. */ | |
194 | textdomain (_libc_intl_domainname); | |
195 | ||
196 | /* Initialize local variables. */ | |
197 | input_name = NULL; | |
198 | ||
199 | /* Parse and process arguments. */ | |
200 | argp_parse (&argp, argc, argv, 0, &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 (0, 0, gettext ("wrong number of arguments")); | |
209 | argp_help (&argp, stdout, ARGP_HELP_SEE, | |
210 | program_invocation_short_name); | |
211 | exit (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 | { | |
9ee76b5a UD |
227 | int fd = open (input_name, O_RDONLY); |
228 | if (fd == -1) | |
229 | error (EXIT_FAILURE, errno, gettext ("cannot open database file `%s'"), | |
230 | input_name); | |
793bd4d9 | 231 | |
9ee76b5a | 232 | int status = print_database (fd); |
793bd4d9 | 233 | |
9ee76b5a | 234 | close (fd); |
793bd4d9 UD |
235 | |
236 | return status; | |
237 | } | |
238 | ||
239 | /* Open input file. */ | |
240 | if (strcmp (input_name, "-") == 0 || strcmp (input_name, "/dev/stdin") == 0) | |
241 | input_file = stdin; | |
242 | else | |
243 | { | |
9ee76b5a | 244 | struct stat64 st; |
793bd4d9 | 245 | |
9ee76b5a | 246 | input_file = fopen64 (input_name, "r"); |
793bd4d9 UD |
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. */ | |
9ee76b5a | 253 | if (fstat64 (fileno (input_file), &st) >= 0) |
793bd4d9 UD |
254 | mode = st.st_mode & ACCESSPERMS; |
255 | } | |
256 | ||
793bd4d9 | 257 | /* Start the real work. */ |
9ee76b5a | 258 | int status = process_input (input_file, input_name, to_lowercase, be_quiet); |
793bd4d9 UD |
259 | |
260 | /* Close files. */ | |
261 | if (input_file != stdin) | |
262 | fclose (input_file); | |
9ee76b5a UD |
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) | |
2666d441 UD |
270 | { |
271 | if (be_quiet) | |
272 | return EXIT_SUCCESS; | |
273 | else | |
274 | error (EXIT_SUCCESS, 0, gettext ("no entries to be processed")); | |
275 | } | |
9ee76b5a UD |
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 (&tmp_output_name, "%s.XXXXXX", output_name) == -1) | |
284 | error (EXIT_FAILURE, errno, gettext ("cannot create temporary file name")); | |
285 | ||
286 | set_file_creation_context (output_name, mode); | |
287 | int fd = mkstemp (tmp_output_name); | |
288 | reset_file_creation_context (); | |
289 | if (fd == -1) | |
290 | error (EXIT_FAILURE, errno, gettext ("cannot create temporary file")); | |
9ee76b5a UD |
291 | |
292 | status = write_output (fd); | |
293 | ||
294 | if (status == EXIT_SUCCESS) | |
295 | { | |
296 | struct stat64 st; | |
297 | ||
298 | if (fstat64 (fd, &st) == 0) | |
299 | { | |
300 | if ((st.st_mode & ACCESSPERMS) != mode) | |
301 | /* We ignore problems with changing the mode. */ | |
302 | fchmod (fd, mode); | |
303 | } | |
304 | else | |
305 | { | |
306 | error (0, errno, gettext ("cannot stat newly created file")); | |
307 | status = EXIT_FAILURE; | |
308 | } | |
309 | } | |
310 | ||
311 | close (fd); | |
312 | ||
313 | if (status == EXIT_SUCCESS) | |
314 | { | |
315 | if (rename (tmp_output_name, output_name) != 0) | |
316 | { | |
317 | error (0, errno, gettext ("cannot rename temporary file")); | |
318 | status = EXIT_FAILURE; | |
319 | goto do_unlink; | |
320 | } | |
321 | } | |
322 | else | |
323 | do_unlink: | |
324 | unlink (tmp_output_name); | |
793bd4d9 UD |
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 | { | |
9f2da732 UD |
334 | struct db_option *newp; |
335 | ||
793bd4d9 UD |
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; | |
9f2da732 UD |
350 | case 'g': |
351 | newp = xmalloc (sizeof (*newp)); | |
352 | newp->dbid = arg[0]; | |
353 | newp->next = db_options; | |
354 | db_options = newp; | |
355 | break; | |
793bd4d9 UD |
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 | { | |
8b748aed | 366 | char *tp = NULL; |
793bd4d9 UD |
367 | switch (key) |
368 | { | |
369 | case ARGP_KEY_HELP_EXTRA: | |
370 | /* We print some extra information. */ | |
8b748aed | 371 | if (asprintf (&tp, gettext ("\ |
d40eb37a | 372 | For bug reporting instructions, please see:\n\ |
8b748aed JM |
373 | %s.\n"), REPORT_BUGS_TO) < 0) |
374 | return NULL; | |
375 | return tp; | |
793bd4d9 UD |
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 | { | |
8b748aed | 386 | fprintf (stream, "makedb %s%s\n", PKGVERSION, VERSION); |
793bd4d9 UD |
387 | fprintf (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\ | |
1059defe | 391 | "), "2024"); |
793bd4d9 UD |
392 | fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); |
393 | } | |
394 | ||
395 | ||
396 | static int | |
9ee76b5a UD |
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 | ||
2666d441 | 405 | return strcmp (d1->str, d2->str); |
9ee76b5a UD |
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 (d1->str, d2->str); | |
416 | } | |
417 | ||
418 | ||
419 | static int | |
9dd346ff | 420 | process_input (FILE *input, const char *inname, int to_lowercase, int be_quiet) |
793bd4d9 UD |
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 | ||
9ee76b5a | 432 | struct database *last_database = NULL; |
793bd4d9 | 433 | |
9ee76b5a UD |
434 | while (!feof_unlocked (input)) |
435 | { | |
436 | ssize_t n = getline (&line, &linelen, input); | |
793bd4d9 UD |
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 | ||
9ee76b5a | 450 | char *cp = line; |
793bd4d9 UD |
451 | while (isspace (*cp)) |
452 | ++cp; | |
453 | ||
9ee76b5a UD |
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. */ | |
793bd4d9 UD |
457 | continue; |
458 | ||
9ee76b5a UD |
459 | /* Skip over the character indicating the database so that it is not |
460 | affected by TO_LOWERCASE. */ | |
461 | char *key = cp++; | |
793bd4d9 UD |
462 | while (*cp != '\0' && !isspace (*cp)) |
463 | { | |
464 | if (to_lowercase) | |
465 | *cp = tolower (*cp); | |
466 | ++cp; | |
467 | } | |
468 | ||
9ee76b5a UD |
469 | if (*cp == '\0') |
470 | /* It's a line without a value field. */ | |
793bd4d9 UD |
471 | continue; |
472 | ||
9ee76b5a UD |
473 | *cp++ = '\0'; |
474 | size_t keylen = cp - key; | |
793bd4d9 UD |
475 | |
476 | while (isspace (*cp)) | |
477 | ++cp; | |
478 | ||
9ee76b5a UD |
479 | char *data = cp; |
480 | size_t datalen = (&line[n] - cp) + 1; | |
793bd4d9 | 481 | |
9ee76b5a UD |
482 | /* Find the database. */ |
483 | if (last_database == NULL || last_database->dbid != key[0]) | |
793bd4d9 | 484 | { |
9ee76b5a UD |
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) | |
793bd4d9 | 490 | { |
9ee76b5a UD |
491 | last_database = xmalloc (sizeof (*last_database)); |
492 | last_database->dbid = key[0]; | |
9f2da732 | 493 | last_database->extra_string = false; |
9ee76b5a UD |
494 | last_database->next = databases; |
495 | last_database->entries = NULL; | |
496 | last_database->nentries = 0; | |
497 | last_database->keystrlen = 0; | |
498 | databases = last_database; | |
9f2da732 UD |
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; | |
793bd4d9 | 509 | } |
9ee76b5a | 510 | } |
793bd4d9 | 511 | |
9ee76b5a UD |
512 | /* Skip the database selector. */ |
513 | ++key; | |
514 | --keylen; | |
515 | ||
9ee76b5a UD |
516 | /* Store the data. */ |
517 | struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry) | |
518 | + datalen); | |
9f2da732 UD |
519 | if (last_database->extra_string) |
520 | nentry->idx = extrastrlen; | |
521 | else | |
522 | nentry->idx = valstrlen; | |
523 | nentry->extra_string = last_database->extra_string; | |
9ee76b5a | 524 | memcpy (nentry->str, data, datalen); |
793bd4d9 | 525 | |
9ee76b5a UD |
526 | struct valstrentry **fdata = tsearch (nentry, &valstrtree, |
527 | 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 (nentry); | |
535 | nentry = *fdata; | |
536 | } | |
537 | else | |
9f2da732 UD |
538 | if (last_database->extra_string) |
539 | extrastrlen += datalen; | |
540 | else | |
541 | valstrlen += datalen; | |
9ee76b5a UD |
542 | |
543 | /* Store the key. */ | |
2666d441 UD |
544 | struct dbentry *newp = xmalloc (sizeof (struct dbentry) + keylen); |
545 | newp->validx = nentry->idx; | |
546 | newp->hashval = __hash_string (key); | |
547 | memcpy (newp->str, key, keylen); | |
9ee76b5a UD |
548 | |
549 | struct dbentry **found = tsearch (newp, &last_database->entries, | |
550 | dbentry_compare); | |
551 | if (found == NULL) | |
552 | error (EXIT_FAILURE, errno, gettext ("cannot create search tree")); | |
553 | ||
554 | if (*found != newp) | |
555 | { | |
556 | free (newp); | |
557 | if (!be_quiet) | |
558 | error_at_line (0, 0, inname, linenr, gettext ("duplicate key")); | |
559 | continue; | |
560 | } | |
561 | ||
562 | ++last_database->nentries; | |
563 | last_database->keystrlen += keylen; | |
564 | ||
565 | any_dbentry = true; | |
793bd4d9 UD |
566 | } |
567 | ||
9ee76b5a | 568 | if (ferror_unlocked (input)) |
793bd4d9 UD |
569 | { |
570 | error (0, 0, gettext ("problems while reading `%s'"), inname); | |
571 | status = EXIT_FAILURE; | |
572 | } | |
573 | ||
574 | return status; | |
575 | } | |
576 | ||
577 | ||
9ee76b5a UD |
578 | static void |
579 | copy_valstr (const void *nodep, const VISIT which, const int depth) | |
793bd4d9 | 580 | { |
9ee76b5a UD |
581 | if (which != leaf && which != postorder) |
582 | return; | |
583 | ||
584 | const struct valstrentry *p = *(const struct valstrentry **) nodep; | |
585 | ||
9f2da732 | 586 | strcpy (valstrtab + (p->extra_string ? valstrlen : 0) + p->idx, p->str); |
9ee76b5a UD |
587 | } |
588 | ||
589 | ||
0817d63d SP |
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. */ | |
a9e836b0 UD |
592 | static int |
593 | is_prime (size_t candidate) | |
594 | { | |
a9e836b0 UD |
595 | size_t divn = 3; |
596 | size_t sq = divn * divn; | |
597 | ||
0817d63d SP |
598 | assert (candidate > 4 && candidate % 2 != 0); |
599 | ||
a9e836b0 UD |
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 | { | |
0817d63d SP |
614 | /* Make sure that we're always greater than 4. */ |
615 | seed = (seed + 4) | 1; | |
a9e836b0 UD |
616 | |
617 | while (!is_prime (seed)) | |
618 | seed += 2; | |
619 | ||
620 | return seed; | |
621 | } | |
622 | ||
53d19edf FS |
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 | } | |
a9e836b0 | 662 | |
9ee76b5a UD |
663 | static void |
664 | compute_tables (void) | |
665 | { | |
9f2da732 UD |
666 | valstrtab = xmalloc (roundup (valstrlen + extrastrlen, sizeof (stridx_t))); |
667 | while ((valstrlen + extrastrlen) % sizeof (stridx_t) != 0) | |
9ee76b5a UD |
668 | valstrtab[valstrlen++] = '\0'; |
669 | twalk (valstrtree, copy_valstr); | |
670 | ||
8de79a24 AS |
671 | static struct database *db; |
672 | for (db = databases; db != NULL; db = db->next) | |
9ee76b5a UD |
673 | if (db->nentries != 0) |
674 | { | |
675 | ++ndatabases; | |
676 | ||
9ee76b5a UD |
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. */ | |
a9e836b0 | 680 | #define TEST_RANGE 30 |
9f2da732 UD |
681 | size_t nhashentries_min = next_prime (db->nentries < TEST_RANGE |
682 | ? db->nentries | |
683 | : db->nentries * 2 - TEST_RANGE); | |
a9e836b0 UD |
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 (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 | ||
8de79a24 | 693 | copy_string = false; |
a9e836b0 UD |
694 | nhashentries = nhashentries_min; |
695 | for (size_t cnt = 0; cnt < TEST_RANGE; ++cnt) | |
696 | { | |
697 | memset (db->hashtable, '\xff', nhashentries * sizeof (stridx_t)); | |
698 | ||
699 | max_chainlength = 0; | |
700 | wp = db->keystrtab; | |
701 | ||
53d19edf | 702 | twalk_r (db->entries, add_key, db); |
a9e836b0 UD |
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 (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 (db->hashtable, '\xff', | |
725 | 2 * nhashentries_max * sizeof (stridx_t)); | |
726 | copy_string = true; | |
727 | wp = db->keystrtab; | |
9ee76b5a | 728 | |
53d19edf | 729 | twalk_r (db->entries, add_key, db); |
9ee76b5a | 730 | |
a9e836b0 UD |
731 | db->nhashentries = nhashentries_best; |
732 | nhashentries_total += nhashentries_best; | |
9ee76b5a UD |
733 | } |
734 | } | |
735 | ||
736 | ||
737 | static int | |
738 | write_output (int fd) | |
739 | { | |
740 | struct nss_db_header *header; | |
741 | uint64_t file_offset = (sizeof (struct nss_db_header) | |
742 | + (ndatabases * sizeof (header->dbs[0]))); | |
820948ed JST |
743 | struct scratch_buffer sbuf; |
744 | scratch_buffer_init (&sbuf); | |
745 | ||
746 | if (!scratch_buffer_set_array_size (&sbuf, 1, file_offset)) | |
747 | { | |
748 | error (0, errno, gettext ("failed to allocate memory")); | |
749 | return EXIT_FAILURE; | |
750 | } | |
751 | header = sbuf.data; | |
9ee76b5a UD |
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; | |
26492c0a MS |
759 | size_t iov_nelts = 2 + ndatabases * 3; |
760 | struct iovec iov[iov_nelts]; | |
9ee76b5a UD |
761 | iov[0].iov_base = header; |
762 | iov[0].iov_len = file_offset; | |
763 | ||
764 | iov[1].iov_base = valstrtab; | |
9f2da732 UD |
765 | iov[1].iov_len = valstrlen + extrastrlen; |
766 | file_offset += iov[1].iov_len; | |
9ee76b5a | 767 | |
a9e836b0 | 768 | size_t keydataoffset = file_offset + nhashentries_total * sizeof (stridx_t); |
9ee76b5a UD |
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; | |
9ee76b5a UD |
776 | memset (header->dbs[filled_dbs].pad, '\0', |
777 | sizeof (header->dbs[0].pad)); | |
778 | header->dbs[filled_dbs].hashsize = db->nhashentries; | |
779 | ||
2666d441 | 780 | iov[2 + filled_dbs].iov_base = db->hashtable; |
a9e836b0 | 781 | iov[2 + filled_dbs].iov_len = db->nhashentries * sizeof (stridx_t); |
9ee76b5a | 782 | header->dbs[filled_dbs].hashoffset = file_offset; |
2666d441 | 783 | file_offset += iov[2 + filled_dbs].iov_len; |
9ee76b5a | 784 | |
2666d441 UD |
785 | iov[2 + ndatabases + filled_dbs * 2].iov_base = db->keyidxtab; |
786 | iov[2 + ndatabases + filled_dbs * 2].iov_len | |
a9e836b0 | 787 | = db->nhashentries * sizeof (stridx_t); |
2666d441 UD |
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; | |
9ee76b5a UD |
795 | |
796 | ++filled_dbs; | |
797 | } | |
798 | ||
799 | assert (filled_dbs == ndatabases); | |
2666d441 | 800 | assert (file_offset == (iov[0].iov_len + iov[1].iov_len |
a9e836b0 | 801 | + nhashentries_total * sizeof (stridx_t))); |
2666d441 | 802 | header->allocate = file_offset; |
9ee76b5a | 803 | |
30685597 MS |
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 | ||
26492c0a MS |
811 | assert (iov_nelts <= INT_MAX); |
812 | if (writev (fd, iov, iov_nelts) != keydataoffset) | |
793bd4d9 | 813 | { |
9ee76b5a | 814 | error (0, errno, gettext ("failed to write new database file")); |
820948ed | 815 | scratch_buffer_free (&sbuf); |
793bd4d9 UD |
816 | return EXIT_FAILURE; |
817 | } | |
818 | ||
30685597 MS |
819 | #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0) |
820 | DIAG_POP_NEEDS_COMMENT; | |
821 | #endif | |
822 | ||
820948ed | 823 | scratch_buffer_free (&sbuf); |
9ee76b5a UD |
824 | return EXIT_SUCCESS; |
825 | } | |
826 | ||
827 | ||
828 | static int | |
829 | print_database (int fd) | |
830 | { | |
831 | struct stat64 st; | |
832 | if (fstat64 (fd, &st) != 0) | |
833 | error (EXIT_FAILURE, errno, gettext ("cannot stat database file")); | |
834 | ||
835 | const struct nss_db_header *header = mmap (NULL, st.st_size, PROT_READ, | |
836 | MAP_PRIVATE|MAP_POPULATE, fd, 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, 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) | |
793bd4d9 | 846 | { |
2666d441 UD |
847 | const stridx_t *stridxtab |
848 | = ((const stridx_t *) ((const char *) header | |
849 | + header->dbs[dbidx].keyidxoffset)); | |
9ee76b5a | 850 | const char *keystrtab |
2666d441 UD |
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 ("%c%s %s\n", | |
859 | header->dbs[dbidx].id, | |
860 | keystrtab + stridxtab[hidx], | |
861 | valstrtab + hashtab[hidx]); | |
793bd4d9 UD |
862 | } |
863 | ||
9ee76b5a UD |
864 | return EXIT_SUCCESS; |
865 | } | |
866 | ||
867 | ||
868 | #ifdef HAVE_SELINUX | |
04726be8 | 869 | |
9ee76b5a UD |
870 | static void |
871 | set_file_creation_context (const char *outname, mode_t mode) | |
872 | { | |
873 | static int enabled; | |
874 | static int enforcing; | |
f278835f AJ |
875 | struct selabel_handle *label_hnd = NULL; |
876 | char* ctx; | |
9ee76b5a UD |
877 | |
878 | /* Check if SELinux is enabled, and remember. */ | |
879 | if (enabled == 0) | |
3d7ba52b | 880 | enabled = is_selinux_enabled () ? 1 : -1; |
9ee76b5a UD |
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 | ||
f278835f AJ |
888 | /* Open the file contexts backend. */ |
889 | label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); | |
890 | if (!label_hnd) | |
891 | { | |
892 | error (enforcing > 0 ? EXIT_FAILURE : 0, 0, | |
893 | gettext ("cannot initialize SELinux context")); | |
894 | return; | |
895 | } | |
9ee76b5a UD |
896 | /* Determine the context which the file should have. */ |
897 | ctx = NULL; | |
f278835f | 898 | if (selabel_lookup(label_hnd, &ctx, outname, S_IFREG | mode) == 0) |
793bd4d9 | 899 | { |
9ee76b5a UD |
900 | if (setfscreatecon (ctx) != 0) |
901 | error (enforcing > 0 ? EXIT_FAILURE : 0, 0, | |
902 | gettext ("cannot set file creation context for `%s'"), | |
903 | outname); | |
904 | ||
905 | freecon (ctx); | |
793bd4d9 | 906 | } |
f278835f AJ |
907 | |
908 | /* Close the file contexts backend. */ | |
909 | selabel_close(label_hnd); | |
9ee76b5a | 910 | } |
793bd4d9 | 911 | |
9ee76b5a UD |
912 | static void |
913 | reset_file_creation_context (void) | |
914 | { | |
915 | setfscreatecon (NULL); | |
793bd4d9 | 916 | } |
9ee76b5a | 917 | #endif |