]>
Commit | Line | Data |
---|---|---|
793bd4d9 | 1 | /* Create simple DB database from textual input. |
6d7e8eda | 2 | Copyright (C) 1996-2023 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> | |
9ee76b5a | 28 | #include <search.h> |
bea9b193 | 29 | #include <stdbool.h> |
793bd4d9 | 30 | #include <stdio.h> |
793bd4d9 UD |
31 | #include <stdlib.h> |
32 | #include <string.h> | |
9ee76b5a | 33 | #include <unistd.h> |
e054f494 | 34 | #include <stdint.h> |
9ee76b5a | 35 | #include <sys/mman.h> |
e468f8a3 | 36 | #include <sys/param.h> |
793bd4d9 | 37 | #include <sys/stat.h> |
e468f8a3 | 38 | #include <sys/uio.h> |
2666d441 | 39 | #include "nss_db/nss_db.h" |
04726be8 | 40 | #include <libc-diag.h> |
793bd4d9 UD |
41 | |
42 | /* Get libc version number. */ | |
43 | #include "../version.h" | |
44 | ||
9ee76b5a UD |
45 | /* The hashing function we use. */ |
46 | #include "../intl/hash-string.h" | |
47 | ||
48 | /* SELinux support. */ | |
49 | #ifdef HAVE_SELINUX | |
f278835f | 50 | # include <selinux/label.h> |
9ee76b5a UD |
51 | # include <selinux/selinux.h> |
52 | #endif | |
53 | ||
80694780 TS |
54 | #ifndef MAP_POPULATE |
55 | # define MAP_POPULATE 0 | |
56 | #endif | |
57 | ||
793bd4d9 UD |
58 | #define PACKAGE _libc_intl_domainname |
59 | ||
9ee76b5a UD |
60 | /* List of data bases. */ |
61 | struct database | |
62 | { | |
63 | char dbid; | |
9f2da732 | 64 | bool extra_string; |
9ee76b5a UD |
65 | struct database *next; |
66 | void *entries; | |
67 | size_t nentries; | |
9ee76b5a | 68 | size_t nhashentries; |
2666d441 UD |
69 | stridx_t *hashtable; |
70 | size_t keystrlen; | |
71 | stridx_t *keyidxtab; | |
9ee76b5a UD |
72 | char *keystrtab; |
73 | } *databases; | |
74 | static size_t ndatabases; | |
a9e836b0 | 75 | static size_t nhashentries_total; |
9ee76b5a UD |
76 | static size_t valstrlen; |
77 | static void *valstrtree; | |
78 | static char *valstrtab; | |
9f2da732 | 79 | static size_t extrastrlen; |
9ee76b5a UD |
80 | |
81 | /* Database entry. */ | |
82 | struct dbentry | |
83 | { | |
9ee76b5a UD |
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; | |
9f2da732 | 93 | bool extra_string; |
9ee76b5a UD |
94 | char str[0]; |
95 | }; | |
96 | ||
97 | ||
9ee76b5a UD |
98 | /* True if any entry has been added. */ |
99 | static bool any_dbentry; | |
100 | ||
793bd4d9 UD |
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 | ||
793bd4d9 UD |
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") }, | |
9f2da732 UD |
126 | { "generated", 'g', N_("CHAR"), 0, |
127 | N_("Generated line not part of iteration") }, | |
793bd4d9 UD |
128 | { NULL, 0, NULL, 0, NULL } |
129 | }; | |
130 | ||
131 | /* Short description of program. */ | |
9ee76b5a | 132 | static const char doc[] = N_("Create simple database from textual input."); |
793bd4d9 UD |
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. */ | |
cbc85992 | 139 | static error_t parse_opt (int key, char *arg, struct argp_state *state); |
793bd4d9 UD |
140 | |
141 | /* Function to print some extra text in the help message. */ | |
cbc85992 | 142 | static char *more_help (int key, const char *text, void *input); |
793bd4d9 UD |
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 | ||
9f2da732 UD |
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 | ||
793bd4d9 | 159 | /* Prototypes for local functions. */ |
9ee76b5a | 160 | static int process_input (FILE *input, const char *inname, |
cbc85992 | 161 | int to_lowercase, int be_quiet); |
9ee76b5a UD |
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. */ | |
6ff444c4 | 178 | #include <programs/xmalloc.h> |
793bd4d9 UD |
179 | |
180 | ||
181 | int | |
cbc85992 | 182 | main (int argc, char *argv[]) |
793bd4d9 UD |
183 | { |
184 | const char *input_name; | |
185 | FILE *input_file; | |
793bd4d9 | 186 | int remaining; |
2666d441 | 187 | int mode = 0644; |
793bd4d9 UD |
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 | { | |
9ee76b5a UD |
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); | |
793bd4d9 | 230 | |
9ee76b5a | 231 | int status = print_database (fd); |
793bd4d9 | 232 | |
9ee76b5a | 233 | close (fd); |
793bd4d9 UD |
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 | { | |
9ee76b5a | 243 | struct stat64 st; |
793bd4d9 | 244 | |
9ee76b5a | 245 | input_file = fopen64 (input_name, "r"); |
793bd4d9 UD |
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. */ | |
9ee76b5a | 252 | if (fstat64 (fileno (input_file), &st) >= 0) |
793bd4d9 UD |
253 | mode = st.st_mode & ACCESSPERMS; |
254 | } | |
255 | ||
793bd4d9 | 256 | /* Start the real work. */ |
9ee76b5a | 257 | int status = process_input (input_file, input_name, to_lowercase, be_quiet); |
793bd4d9 UD |
258 | |
259 | /* Close files. */ | |
260 | if (input_file != stdin) | |
261 | fclose (input_file); | |
9ee76b5a UD |
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) | |
2666d441 UD |
269 | { |
270 | if (be_quiet) | |
271 | return EXIT_SUCCESS; | |
272 | else | |
273 | error (EXIT_SUCCESS, 0, gettext ("no entries to be processed")); | |
274 | } | |
9ee76b5a UD |
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")); | |
9ee76b5a UD |
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); | |
793bd4d9 UD |
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 | { | |
9f2da732 UD |
333 | struct db_option *newp; |
334 | ||
793bd4d9 UD |
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; | |
9f2da732 UD |
349 | case 'g': |
350 | newp = xmalloc (sizeof (*newp)); | |
351 | newp->dbid = arg[0]; | |
352 | newp->next = db_options; | |
353 | db_options = newp; | |
354 | break; | |
793bd4d9 UD |
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 | { | |
8b748aed | 365 | char *tp = NULL; |
793bd4d9 UD |
366 | switch (key) |
367 | { | |
368 | case ARGP_KEY_HELP_EXTRA: | |
369 | /* We print some extra information. */ | |
8b748aed | 370 | if (asprintf (&tp, gettext ("\ |
d40eb37a | 371 | For bug reporting instructions, please see:\n\ |
8b748aed JM |
372 | %s.\n"), REPORT_BUGS_TO) < 0) |
373 | return NULL; | |
374 | return tp; | |
793bd4d9 UD |
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 | { | |
8b748aed | 385 | fprintf (stream, "makedb %s%s\n", PKGVERSION, VERSION); |
793bd4d9 UD |
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\ | |
ae612c45 | 390 | "), "2023"); |
793bd4d9 UD |
391 | fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); |
392 | } | |
393 | ||
394 | ||
395 | static int | |
9ee76b5a UD |
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 | ||
2666d441 | 404 | return strcmp (d1->str, d2->str); |
9ee76b5a UD |
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 | |
9dd346ff | 419 | process_input (FILE *input, const char *inname, int to_lowercase, int be_quiet) |
793bd4d9 UD |
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 | ||
9ee76b5a | 431 | struct database *last_database = NULL; |
793bd4d9 | 432 | |
9ee76b5a UD |
433 | while (!feof_unlocked (input)) |
434 | { | |
435 | ssize_t n = getline (&line, &linelen, input); | |
793bd4d9 UD |
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 | ||
9ee76b5a | 449 | char *cp = line; |
793bd4d9 UD |
450 | while (isspace (*cp)) |
451 | ++cp; | |
452 | ||
9ee76b5a UD |
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. */ | |
793bd4d9 UD |
456 | continue; |
457 | ||
9ee76b5a UD |
458 | /* Skip over the character indicating the database so that it is not |
459 | affected by TO_LOWERCASE. */ | |
460 | char *key = cp++; | |
793bd4d9 UD |
461 | while (*cp != '\0' && !isspace (*cp)) |
462 | { | |
463 | if (to_lowercase) | |
464 | *cp = tolower (*cp); | |
465 | ++cp; | |
466 | } | |
467 | ||
9ee76b5a UD |
468 | if (*cp == '\0') |
469 | /* It's a line without a value field. */ | |
793bd4d9 UD |
470 | continue; |
471 | ||
9ee76b5a UD |
472 | *cp++ = '\0'; |
473 | size_t keylen = cp - key; | |
793bd4d9 UD |
474 | |
475 | while (isspace (*cp)) | |
476 | ++cp; | |
477 | ||
9ee76b5a UD |
478 | char *data = cp; |
479 | size_t datalen = (&line[n] - cp) + 1; | |
793bd4d9 | 480 | |
9ee76b5a UD |
481 | /* Find the database. */ |
482 | if (last_database == NULL || last_database->dbid != key[0]) | |
793bd4d9 | 483 | { |
9ee76b5a UD |
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) | |
793bd4d9 | 489 | { |
9ee76b5a UD |
490 | last_database = xmalloc (sizeof (*last_database)); |
491 | last_database->dbid = key[0]; | |
9f2da732 | 492 | last_database->extra_string = false; |
9ee76b5a UD |
493 | last_database->next = databases; |
494 | last_database->entries = NULL; | |
495 | last_database->nentries = 0; | |
496 | last_database->keystrlen = 0; | |
497 | databases = last_database; | |
9f2da732 UD |
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; | |
793bd4d9 | 508 | } |
9ee76b5a | 509 | } |
793bd4d9 | 510 | |
9ee76b5a UD |
511 | /* Skip the database selector. */ |
512 | ++key; | |
513 | --keylen; | |
514 | ||
9ee76b5a UD |
515 | /* Store the data. */ |
516 | struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry) | |
517 | + datalen); | |
9f2da732 UD |
518 | if (last_database->extra_string) |
519 | nentry->idx = extrastrlen; | |
520 | else | |
521 | nentry->idx = valstrlen; | |
522 | nentry->extra_string = last_database->extra_string; | |
9ee76b5a | 523 | memcpy (nentry->str, data, datalen); |
793bd4d9 | 524 | |
9ee76b5a UD |
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 | |
9f2da732 UD |
537 | if (last_database->extra_string) |
538 | extrastrlen += datalen; | |
539 | else | |
540 | valstrlen += datalen; | |
9ee76b5a UD |
541 | |
542 | /* Store the key. */ | |
2666d441 UD |
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); | |
9ee76b5a UD |
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; | |
793bd4d9 UD |
565 | } |
566 | ||
9ee76b5a | 567 | if (ferror_unlocked (input)) |
793bd4d9 UD |
568 | { |
569 | error (0, 0, gettext ("problems while reading `%s'"), inname); | |
570 | status = EXIT_FAILURE; | |
571 | } | |
572 | ||
573 | return status; | |
574 | } | |
575 | ||
576 | ||
9ee76b5a UD |
577 | static void |
578 | copy_valstr (const void *nodep, const VISIT which, const int depth) | |
793bd4d9 | 579 | { |
9ee76b5a UD |
580 | if (which != leaf && which != postorder) |
581 | return; | |
582 | ||
583 | const struct valstrentry *p = *(const struct valstrentry **) nodep; | |
584 | ||
9f2da732 | 585 | strcpy (valstrtab + (p->extra_string ? valstrlen : 0) + p->idx, p->str); |
9ee76b5a UD |
586 | } |
587 | ||
588 | ||
0817d63d SP |
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. */ | |
a9e836b0 UD |
591 | static int |
592 | is_prime (size_t candidate) | |
593 | { | |
a9e836b0 UD |
594 | size_t divn = 3; |
595 | size_t sq = divn * divn; | |
596 | ||
0817d63d SP |
597 | assert (candidate > 4 && candidate % 2 != 0); |
598 | ||
a9e836b0 UD |
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 | { | |
0817d63d SP |
613 | /* Make sure that we're always greater than 4. */ |
614 | seed = (seed + 4) | 1; | |
a9e836b0 UD |
615 | |
616 | while (!is_prime (seed)) | |
617 | seed += 2; | |
618 | ||
619 | return seed; | |
620 | } | |
621 | ||
53d19edf FS |
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 | } | |
a9e836b0 | 661 | |
9ee76b5a UD |
662 | static void |
663 | compute_tables (void) | |
664 | { | |
9f2da732 UD |
665 | valstrtab = xmalloc (roundup (valstrlen + extrastrlen, sizeof (stridx_t))); |
666 | while ((valstrlen + extrastrlen) % sizeof (stridx_t) != 0) | |
9ee76b5a UD |
667 | valstrtab[valstrlen++] = '\0'; |
668 | twalk (valstrtree, copy_valstr); | |
669 | ||
8de79a24 AS |
670 | static struct database *db; |
671 | for (db = databases; db != NULL; db = db->next) | |
9ee76b5a UD |
672 | if (db->nentries != 0) |
673 | { | |
674 | ++ndatabases; | |
675 | ||
9ee76b5a UD |
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. */ | |
a9e836b0 | 679 | #define TEST_RANGE 30 |
9f2da732 UD |
680 | size_t nhashentries_min = next_prime (db->nentries < TEST_RANGE |
681 | ? db->nentries | |
682 | : db->nentries * 2 - TEST_RANGE); | |
a9e836b0 UD |
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 | ||
8de79a24 | 692 | copy_string = false; |
a9e836b0 UD |
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 | ||
53d19edf | 701 | twalk_r (db->entries, add_key, db); |
a9e836b0 UD |
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; | |
9ee76b5a | 727 | |
53d19edf | 728 | twalk_r (db->entries, add_key, db); |
9ee76b5a | 729 | |
a9e836b0 UD |
730 | db->nhashentries = nhashentries_best; |
731 | nhashentries_total += nhashentries_best; | |
9ee76b5a UD |
732 | } |
733 | } | |
734 | ||
735 | ||
736 | static int | |
737 | write_output (int fd) | |
738 | { | |
739 | struct nss_db_header *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; | |
26492c0a MS |
750 | size_t iov_nelts = 2 + ndatabases * 3; |
751 | struct iovec iov[iov_nelts]; | |
9ee76b5a UD |
752 | iov[0].iov_base = header; |
753 | iov[0].iov_len = file_offset; | |
754 | ||
755 | iov[1].iov_base = valstrtab; | |
9f2da732 UD |
756 | iov[1].iov_len = valstrlen + extrastrlen; |
757 | file_offset += iov[1].iov_len; | |
9ee76b5a | 758 | |
a9e836b0 | 759 | size_t keydataoffset = file_offset + nhashentries_total * sizeof (stridx_t); |
9ee76b5a UD |
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; | |
9ee76b5a UD |
767 | memset (header->dbs[filled_dbs].pad, '\0', |
768 | sizeof (header->dbs[0].pad)); | |
769 | header->dbs[filled_dbs].hashsize = db->nhashentries; | |
770 | ||
2666d441 | 771 | iov[2 + filled_dbs].iov_base = db->hashtable; |
a9e836b0 | 772 | iov[2 + filled_dbs].iov_len = db->nhashentries * sizeof (stridx_t); |
9ee76b5a | 773 | header->dbs[filled_dbs].hashoffset = file_offset; |
2666d441 | 774 | file_offset += iov[2 + filled_dbs].iov_len; |
9ee76b5a | 775 | |
2666d441 UD |
776 | iov[2 + ndatabases + filled_dbs * 2].iov_base = db->keyidxtab; |
777 | iov[2 + ndatabases + filled_dbs * 2].iov_len | |
a9e836b0 | 778 | = db->nhashentries * sizeof (stridx_t); |
2666d441 UD |
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; | |
9ee76b5a UD |
786 | |
787 | ++filled_dbs; | |
788 | } | |
789 | ||
790 | assert (filled_dbs == ndatabases); | |
2666d441 | 791 | assert (file_offset == (iov[0].iov_len + iov[1].iov_len |
a9e836b0 | 792 | + nhashentries_total * sizeof (stridx_t))); |
2666d441 | 793 | header->allocate = file_offset; |
9ee76b5a | 794 | |
30685597 MS |
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 | ||
26492c0a MS |
802 | assert (iov_nelts <= INT_MAX); |
803 | if (writev (fd, iov, iov_nelts) != keydataoffset) | |
793bd4d9 | 804 | { |
9ee76b5a | 805 | error (0, errno, gettext ("failed to write new database file")); |
793bd4d9 UD |
806 | return EXIT_FAILURE; |
807 | } | |
808 | ||
30685597 MS |
809 | #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0) |
810 | DIAG_POP_NEEDS_COMMENT; | |
811 | #endif | |
812 | ||
9ee76b5a UD |
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 *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) | |
793bd4d9 | 835 | { |
2666d441 UD |
836 | const stridx_t *stridxtab |
837 | = ((const stridx_t *) ((const char *) header | |
838 | + header->dbs[dbidx].keyidxoffset)); | |
9ee76b5a | 839 | const char *keystrtab |
2666d441 UD |
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]); | |
793bd4d9 UD |
851 | } |
852 | ||
9ee76b5a UD |
853 | return EXIT_SUCCESS; |
854 | } | |
855 | ||
856 | ||
857 | #ifdef HAVE_SELINUX | |
04726be8 | 858 | |
9ee76b5a UD |
859 | static void |
860 | set_file_creation_context (const char *outname, mode_t mode) | |
861 | { | |
862 | static int enabled; | |
863 | static int enforcing; | |
f278835f AJ |
864 | struct selabel_handle *label_hnd = NULL; |
865 | char* ctx; | |
9ee76b5a UD |
866 | |
867 | /* Check if SELinux is enabled, and remember. */ | |
868 | if (enabled == 0) | |
3d7ba52b | 869 | enabled = is_selinux_enabled () ? 1 : -1; |
9ee76b5a UD |
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 | ||
f278835f AJ |
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 | } | |
9ee76b5a UD |
885 | /* Determine the context which the file should have. */ |
886 | ctx = NULL; | |
f278835f | 887 | if (selabel_lookup(label_hnd, &ctx, outname, S_IFREG | mode) == 0) |
793bd4d9 | 888 | { |
9ee76b5a UD |
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); | |
793bd4d9 | 895 | } |
f278835f AJ |
896 | |
897 | /* Close the file contexts backend. */ | |
898 | selabel_close(label_hnd); | |
9ee76b5a | 899 | } |
793bd4d9 | 900 | |
9ee76b5a UD |
901 | static void |
902 | reset_file_creation_context (void) | |
903 | { | |
904 | setfscreatecon (NULL); | |
793bd4d9 | 905 | } |
9ee76b5a | 906 | #endif |