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