diff options
Diffstat (limited to 'REORG.TODO/nss/makedb.c')
-rw-r--r-- | REORG.TODO/nss/makedb.c | 879 |
1 files changed, 879 insertions, 0 deletions
diff --git a/REORG.TODO/nss/makedb.c b/REORG.TODO/nss/makedb.c new file mode 100644 index 0000000000..542244dd35 --- /dev/null +++ b/REORG.TODO/nss/makedb.c @@ -0,0 +1,879 @@ +/* Create simple DB database from textual input. + Copyright (C) 1996-2017 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <argp.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <error.h> +#include <fcntl.h> +#include <inttypes.h> +#include <libintl.h> +#include <locale.h> +#include <search.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include "nss_db/nss_db.h" + +/* Get libc version number. */ +#include "../version.h" + +/* The hashing function we use. */ +#include "../intl/hash-string.h" + +/* SELinux support. */ +#ifdef HAVE_SELINUX +# include <selinux/selinux.h> +#endif + +#ifndef MAP_POPULATE +# define MAP_POPULATE 0 +#endif + +#define PACKAGE _libc_intl_domainname + +/* List of data bases. */ +struct database +{ + char dbid; + bool extra_string; + struct database *next; + void *entries; + size_t nentries; + size_t nhashentries; + stridx_t *hashtable; + size_t keystrlen; + stridx_t *keyidxtab; + char *keystrtab; +} *databases; +static size_t ndatabases; +static size_t nhashentries_total; +static size_t valstrlen; +static void *valstrtree; +static char *valstrtab; +static size_t extrastrlen; + +/* Database entry. */ +struct dbentry +{ + stridx_t validx; + uint32_t hashval; + char str[0]; +}; + +/* Stored string entry. */ +struct valstrentry +{ + stridx_t idx; + bool extra_string; + char str[0]; +}; + + +/* True if any entry has been added. */ +static bool any_dbentry; + +/* If non-zero convert key to lower case. */ +static int to_lowercase; + +/* If non-zero print content of input file, one entry per line. */ +static int do_undo; + +/* If non-zero do not print informational messages. */ +static int be_quiet; + +/* Name of output file. */ +static const char *output_name; + +/* Name and version of program. */ +static void print_version (FILE *stream, struct argp_state *state); +void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version; + +/* Definitions of arguments for argp functions. */ +static const struct argp_option options[] = +{ + { "fold-case", 'f', NULL, 0, N_("Convert key to lower case") }, + { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") }, + { "quiet", 'q', NULL, 0, + N_("Do not print messages while building database") }, + { "undo", 'u', NULL, 0, + N_("Print content of database file, one entry a line") }, + { "generated", 'g', N_("CHAR"), 0, + N_("Generated line not part of iteration") }, + { NULL, 0, NULL, 0, NULL } +}; + +/* Short description of program. */ +static const char doc[] = N_("Create simple database from textual input."); + +/* Strings for arguments in help texts. */ +static const char args_doc[] = N_("\ +INPUT-FILE OUTPUT-FILE\n-o OUTPUT-FILE INPUT-FILE\n-u INPUT-FILE"); + +/* Prototype for option handler. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state); + +/* Function to print some extra text in the help message. */ +static char *more_help (int key, const char *text, void *input); + +/* Data structure to communicate with argp functions. */ +static struct argp argp = +{ + options, parse_opt, args_doc, doc, NULL, more_help +}; + + +/* List of databases which are not part of the iteration table. */ +static struct db_option +{ + char dbid; + struct db_option *next; +} *db_options; + + +/* Prototypes for local functions. */ +static int process_input (FILE *input, const char *inname, + int to_lowercase, int be_quiet); +static int print_database (int fd); +static void compute_tables (void); +static int write_output (int fd); + +/* SELinux support. */ +#ifdef HAVE_SELINUX +/* Set the SELinux file creation context for the given file. */ +static void set_file_creation_context (const char *outname, mode_t mode); +static void reset_file_creation_context (void); +#else +# define set_file_creation_context(_outname,_mode) +# define reset_file_creation_context() +#endif + + +/* External functions. */ +#include <programs/xmalloc.h> + + +int +main (int argc, char *argv[]) +{ + const char *input_name; + FILE *input_file; + int remaining; + int mode = 0644; + + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); + + /* Set the text message domain. */ + textdomain (_libc_intl_domainname); + + /* Initialize local variables. */ + input_name = NULL; + + /* Parse and process arguments. */ + argp_parse (&argp, argc, argv, 0, &remaining, NULL); + + /* Determine file names. */ + if (do_undo || output_name != NULL) + { + if (remaining + 1 != argc) + { + wrong_arguments: + error (0, 0, gettext ("wrong number of arguments")); + argp_help (&argp, stdout, ARGP_HELP_SEE, + program_invocation_short_name); + exit (1); + } + input_name = argv[remaining]; + } + else + { + if (remaining + 2 != argc) + goto wrong_arguments; + + input_name = argv[remaining++]; + output_name = argv[remaining]; + } + + /* Special handling if we are asked to print the database. */ + if (do_undo) + { + int fd = open (input_name, O_RDONLY); + if (fd == -1) + error (EXIT_FAILURE, errno, gettext ("cannot open database file `%s'"), + input_name); + + int status = print_database (fd); + + close (fd); + + return status; + } + + /* Open input file. */ + if (strcmp (input_name, "-") == 0 || strcmp (input_name, "/dev/stdin") == 0) + input_file = stdin; + else + { + struct stat64 st; + + input_file = fopen64 (input_name, "r"); + if (input_file == NULL) + error (EXIT_FAILURE, errno, gettext ("cannot open input file `%s'"), + input_name); + + /* Get the access rights from the source file. The output file should + have the same. */ + if (fstat64 (fileno (input_file), &st) >= 0) + mode = st.st_mode & ACCESSPERMS; + } + + /* Start the real work. */ + int status = process_input (input_file, input_name, to_lowercase, be_quiet); + + /* Close files. */ + if (input_file != stdin) + fclose (input_file); + + /* No need to continue when we did not read the file successfully. */ + if (status != EXIT_SUCCESS) + return status; + + /* Bail out if nothing is to be done. */ + if (!any_dbentry) + { + if (be_quiet) + return EXIT_SUCCESS; + else + error (EXIT_SUCCESS, 0, gettext ("no entries to be processed")); + } + + /* Compute hash and string tables. */ + compute_tables (); + + /* Open output file. This must not be standard output so we don't + handle "-" and "/dev/stdout" special. */ + char *tmp_output_name; + if (asprintf (&tmp_output_name, "%s.XXXXXX", output_name) == -1) + error (EXIT_FAILURE, errno, gettext ("cannot create temporary file name")); + + set_file_creation_context (output_name, mode); + int fd = mkstemp (tmp_output_name); + reset_file_creation_context (); + if (fd == -1) + error (EXIT_FAILURE, errno, gettext ("cannot create temporary file")); + + status = write_output (fd); + + if (status == EXIT_SUCCESS) + { + struct stat64 st; + + if (fstat64 (fd, &st) == 0) + { + if ((st.st_mode & ACCESSPERMS) != mode) + /* We ignore problems with changing the mode. */ + fchmod (fd, mode); + } + else + { + error (0, errno, gettext ("cannot stat newly created file")); + status = EXIT_FAILURE; + } + } + + close (fd); + + if (status == EXIT_SUCCESS) + { + if (rename (tmp_output_name, output_name) != 0) + { + error (0, errno, gettext ("cannot rename temporary file")); + status = EXIT_FAILURE; + goto do_unlink; + } + } + else + do_unlink: + unlink (tmp_output_name); + + return status; +} + + +/* Handle program arguments. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + struct db_option *newp; + + switch (key) + { + case 'f': + to_lowercase = 1; + break; + case 'o': + output_name = arg; + break; + case 'q': + be_quiet = 1; + break; + case 'u': + do_undo = 1; + break; + case 'g': + newp = xmalloc (sizeof (*newp)); + newp->dbid = arg[0]; + newp->next = db_options; + db_options = newp; + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + + +static char * +more_help (int key, const char *text, void *input) +{ + char *tp = NULL; + switch (key) + { + case ARGP_KEY_HELP_EXTRA: + /* We print some extra information. */ + if (asprintf (&tp, gettext ("\ +For bug reporting instructions, please see:\n\ +%s.\n"), REPORT_BUGS_TO) < 0) + return NULL; + return tp; + default: + break; + } + return (char *) text; +} + +/* Print the version information. */ +static void +print_version (FILE *stream, struct argp_state *state) +{ + fprintf (stream, "makedb %s%s\n", PKGVERSION, VERSION); + fprintf (stream, gettext ("\ +Copyright (C) %s Free Software Foundation, Inc.\n\ +This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ +"), "2017"); + fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper"); +} + + +static int +dbentry_compare (const void *p1, const void *p2) +{ + const struct dbentry *d1 = (const struct dbentry *) p1; + const struct dbentry *d2 = (const struct dbentry *) p2; + + if (d1->hashval != d2->hashval) + return d1->hashval < d2->hashval ? -1 : 1; + + return strcmp (d1->str, d2->str); +} + + +static int +valstr_compare (const void *p1, const void *p2) +{ + const struct valstrentry *d1 = (const struct valstrentry *) p1; + const struct valstrentry *d2 = (const struct valstrentry *) p2; + + return strcmp (d1->str, d2->str); +} + + +static int +process_input (FILE *input, const char *inname, int to_lowercase, int be_quiet) +{ + char *line; + size_t linelen; + int status; + size_t linenr; + + line = NULL; + linelen = 0; + status = EXIT_SUCCESS; + linenr = 0; + + struct database *last_database = NULL; + + while (!feof_unlocked (input)) + { + ssize_t n = getline (&line, &linelen, input); + if (n < 0) + /* This means end of file or some bug. */ + break; + if (n == 0) + /* Short read. Probably interrupted system call. */ + continue; + + ++linenr; + + if (line[n - 1] == '\n') + /* Remove trailing newline. */ + line[--n] = '\0'; + + char *cp = line; + while (isspace (*cp)) + ++cp; + + if (*cp == '#' || *cp == '\0') + /* First non-space character in line '#': it's a comment. + Also go to the next line if it is empty except for whitespaces. */ + continue; + + /* Skip over the character indicating the database so that it is not + affected by TO_LOWERCASE. */ + char *key = cp++; + while (*cp != '\0' && !isspace (*cp)) + { + if (to_lowercase) + *cp = tolower (*cp); + ++cp; + } + + if (*cp == '\0') + /* It's a line without a value field. */ + continue; + + *cp++ = '\0'; + size_t keylen = cp - key; + + while (isspace (*cp)) + ++cp; + + char *data = cp; + size_t datalen = (&line[n] - cp) + 1; + + /* Find the database. */ + if (last_database == NULL || last_database->dbid != key[0]) + { + last_database = databases; + while (last_database != NULL && last_database->dbid != key[0]) + last_database = last_database->next; + + if (last_database == NULL) + { + last_database = xmalloc (sizeof (*last_database)); + last_database->dbid = key[0]; + last_database->extra_string = false; + last_database->next = databases; + last_database->entries = NULL; + last_database->nentries = 0; + last_database->keystrlen = 0; + databases = last_database; + + struct db_option *runp = db_options; + while (runp != NULL) + if (runp->dbid == key[0]) + { + last_database->extra_string = true; + break; + } + else + runp = runp->next; + } + } + + /* Skip the database selector. */ + ++key; + --keylen; + + /* Store the data. */ + struct valstrentry *nentry = xmalloc (sizeof (struct valstrentry) + + datalen); + if (last_database->extra_string) + nentry->idx = extrastrlen; + else + nentry->idx = valstrlen; + nentry->extra_string = last_database->extra_string; + memcpy (nentry->str, data, datalen); + + struct valstrentry **fdata = tsearch (nentry, &valstrtree, + valstr_compare); + if (fdata == NULL) + error (EXIT_FAILURE, errno, gettext ("cannot create search tree")); + + if (*fdata != nentry) + { + /* We can reuse a string. */ + free (nentry); + nentry = *fdata; + } + else + if (last_database->extra_string) + extrastrlen += datalen; + else + valstrlen += datalen; + + /* Store the key. */ + struct dbentry *newp = xmalloc (sizeof (struct dbentry) + keylen); + newp->validx = nentry->idx; + newp->hashval = __hash_string (key); + memcpy (newp->str, key, keylen); + + struct dbentry **found = tsearch (newp, &last_database->entries, + dbentry_compare); + if (found == NULL) + error (EXIT_FAILURE, errno, gettext ("cannot create search tree")); + + if (*found != newp) + { + free (newp); + if (!be_quiet) + error_at_line (0, 0, inname, linenr, gettext ("duplicate key")); + continue; + } + + ++last_database->nentries; + last_database->keystrlen += keylen; + + any_dbentry = true; + } + + if (ferror_unlocked (input)) + { + error (0, 0, gettext ("problems while reading `%s'"), inname); + status = EXIT_FAILURE; + } + + return status; +} + + +static void +copy_valstr (const void *nodep, const VISIT which, const int depth) +{ + if (which != leaf && which != postorder) + return; + + const struct valstrentry *p = *(const struct valstrentry **) nodep; + + strcpy (valstrtab + (p->extra_string ? valstrlen : 0) + p->idx, p->str); +} + + +/* Determine if the candidate is prime by using a modified trial division + algorithm. The candidate must be both odd and greater than 4. */ +static int +is_prime (size_t candidate) +{ + size_t divn = 3; + size_t sq = divn * divn; + + assert (candidate > 4 && candidate % 2 != 0); + + while (sq < candidate && candidate % divn != 0) + { + ++divn; + sq += 4 * divn; + ++divn; + } + + return candidate % divn != 0; +} + + +static size_t +next_prime (size_t seed) +{ + /* Make sure that we're always greater than 4. */ + seed = (seed + 4) | 1; + + while (!is_prime (seed)) + seed += 2; + + return seed; +} + + +static void +compute_tables (void) +{ + valstrtab = xmalloc (roundup (valstrlen + extrastrlen, sizeof (stridx_t))); + while ((valstrlen + extrastrlen) % sizeof (stridx_t) != 0) + valstrtab[valstrlen++] = '\0'; + twalk (valstrtree, copy_valstr); + + static struct database *db; + for (db = databases; db != NULL; db = db->next) + if (db->nentries != 0) + { + ++ndatabases; + + /* We simply use an odd number large than twice the number of + elements to store in the hash table for the size. This gives + enough efficiency. */ +#define TEST_RANGE 30 + size_t nhashentries_min = next_prime (db->nentries < TEST_RANGE + ? db->nentries + : db->nentries * 2 - TEST_RANGE); + size_t nhashentries_max = MAX (nhashentries_min, db->nentries * 4); + size_t nhashentries_best = nhashentries_min; + size_t chainlength_best = db->nentries; + + db->hashtable = xmalloc (2 * nhashentries_max * sizeof (stridx_t) + + db->keystrlen); + db->keyidxtab = db->hashtable + nhashentries_max; + db->keystrtab = (char *) (db->keyidxtab + nhashentries_max); + + static size_t max_chainlength; + static char *wp; + static size_t nhashentries; + static bool copy_string; + + void add_key(const void *nodep, const VISIT which, const int depth) + { + if (which != leaf && which != postorder) + return; + + const struct dbentry *dbe = *(const struct dbentry **) nodep; + + ptrdiff_t stridx; + if (copy_string) + { + stridx = wp - db->keystrtab; + wp = stpcpy (wp, dbe->str) + 1; + } + else + stridx = 0; + + size_t hidx = dbe->hashval % nhashentries; + size_t hval2 = 1 + dbe->hashval % (nhashentries - 2); + size_t chainlength = 0; + + while (db->hashtable[hidx] != ~((stridx_t) 0)) + { + ++chainlength; + if ((hidx += hval2) >= nhashentries) + hidx -= nhashentries; + } + + db->hashtable[hidx] = ((db->extra_string ? valstrlen : 0) + + dbe->validx); + db->keyidxtab[hidx] = stridx; + + max_chainlength = MAX (max_chainlength, chainlength); + } + + copy_string = false; + nhashentries = nhashentries_min; + for (size_t cnt = 0; cnt < TEST_RANGE; ++cnt) + { + memset (db->hashtable, '\xff', nhashentries * sizeof (stridx_t)); + + max_chainlength = 0; + wp = db->keystrtab; + + twalk (db->entries, add_key); + + if (max_chainlength == 0) + { + /* No need to look further, this is as good as it gets. */ + nhashentries_best = nhashentries; + break; + } + + if (max_chainlength < chainlength_best) + { + chainlength_best = max_chainlength; + nhashentries_best = nhashentries; + } + + nhashentries = next_prime (nhashentries + 1); + if (nhashentries > nhashentries_max) + break; + } + + /* Recompute the best table again, this time fill in the strings. */ + nhashentries = nhashentries_best; + memset (db->hashtable, '\xff', + 2 * nhashentries_max * sizeof (stridx_t)); + copy_string = true; + wp = db->keystrtab; + + twalk (db->entries, add_key); + + db->nhashentries = nhashentries_best; + nhashentries_total += nhashentries_best; + } +} + + +static int +write_output (int fd) +{ + struct nss_db_header *header; + uint64_t file_offset = (sizeof (struct nss_db_header) + + (ndatabases * sizeof (header->dbs[0]))); + header = alloca (file_offset); + + header->magic = NSS_DB_MAGIC; + header->ndbs = ndatabases; + header->valstroffset = file_offset; + header->valstrlen = valstrlen; + + size_t filled_dbs = 0; + struct iovec iov[2 + ndatabases * 3]; + iov[0].iov_base = header; + iov[0].iov_len = file_offset; + + iov[1].iov_base = valstrtab; + iov[1].iov_len = valstrlen + extrastrlen; + file_offset += iov[1].iov_len; + + size_t keydataoffset = file_offset + nhashentries_total * sizeof (stridx_t); + for (struct database *db = databases; db != NULL; db = db->next) + if (db->entries != NULL) + { + assert (file_offset % sizeof (stridx_t) == 0); + assert (filled_dbs < ndatabases); + + header->dbs[filled_dbs].id = db->dbid; + memset (header->dbs[filled_dbs].pad, '\0', + sizeof (header->dbs[0].pad)); + header->dbs[filled_dbs].hashsize = db->nhashentries; + + iov[2 + filled_dbs].iov_base = db->hashtable; + iov[2 + filled_dbs].iov_len = db->nhashentries * sizeof (stridx_t); + header->dbs[filled_dbs].hashoffset = file_offset; + file_offset += iov[2 + filled_dbs].iov_len; + + iov[2 + ndatabases + filled_dbs * 2].iov_base = db->keyidxtab; + iov[2 + ndatabases + filled_dbs * 2].iov_len + = db->nhashentries * sizeof (stridx_t); + header->dbs[filled_dbs].keyidxoffset = keydataoffset; + keydataoffset += iov[2 + ndatabases + filled_dbs * 2].iov_len; + + iov[3 + ndatabases + filled_dbs * 2].iov_base = db->keystrtab; + iov[3 + ndatabases + filled_dbs * 2].iov_len = db->keystrlen; + header->dbs[filled_dbs].keystroffset = keydataoffset; + keydataoffset += iov[3 + ndatabases + filled_dbs * 2].iov_len; + + ++filled_dbs; + } + + assert (filled_dbs == ndatabases); + assert (file_offset == (iov[0].iov_len + iov[1].iov_len + + nhashentries_total * sizeof (stridx_t))); + header->allocate = file_offset; + + if (writev (fd, iov, 2 + ndatabases * 3) != keydataoffset) + { + error (0, errno, gettext ("failed to write new database file")); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + + +static int +print_database (int fd) +{ + struct stat64 st; + if (fstat64 (fd, &st) != 0) + error (EXIT_FAILURE, errno, gettext ("cannot stat database file")); + + const struct nss_db_header *header = mmap (NULL, st.st_size, PROT_READ, + MAP_PRIVATE|MAP_POPULATE, fd, 0); + if (header == MAP_FAILED) + error (EXIT_FAILURE, errno, gettext ("cannot map database file")); + + if (header->magic != NSS_DB_MAGIC) + error (EXIT_FAILURE, 0, gettext ("file not a database file")); + + const char *valstrtab = (const char *) header + header->valstroffset; + + for (unsigned int dbidx = 0; dbidx < header->ndbs; ++dbidx) + { + const stridx_t *stridxtab + = ((const stridx_t *) ((const char *) header + + header->dbs[dbidx].keyidxoffset)); + const char *keystrtab + = (const char *) header + header->dbs[dbidx].keystroffset; + const stridx_t *hashtab + = (const stridx_t *) ((const char *) header + + header->dbs[dbidx].hashoffset); + + for (uint32_t hidx = 0; hidx < header->dbs[dbidx].hashsize; ++hidx) + if (hashtab[hidx] != ~((stridx_t) 0)) + printf ("%c%s %s\n", + header->dbs[dbidx].id, + keystrtab + stridxtab[hidx], + valstrtab + hashtab[hidx]); + } + + return EXIT_SUCCESS; +} + + +#ifdef HAVE_SELINUX +static void +set_file_creation_context (const char *outname, mode_t mode) +{ + static int enabled; + static int enforcing; + security_context_t ctx; + + /* Check if SELinux is enabled, and remember. */ + if (enabled == 0) + enabled = is_selinux_enabled () ? 1 : -1; + if (enabled < 0) + return; + + /* Check if SELinux is enforcing, and remember. */ + if (enforcing == 0) + enforcing = security_getenforce () ? 1 : -1; + + /* Determine the context which the file should have. */ + ctx = NULL; + if (matchpathcon (outname, S_IFREG | mode, &ctx) == 0 && ctx != NULL) + { + if (setfscreatecon (ctx) != 0) + error (enforcing > 0 ? EXIT_FAILURE : 0, 0, + gettext ("cannot set file creation context for `%s'"), + outname); + + freecon (ctx); + } +} + +static void +reset_file_creation_context (void) +{ + setfscreatecon (NULL); +} +#endif |