1 /* Create simple DB database from textual input.
2 Copyright (C) 1996-2021 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
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.
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.
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 <https://www.gnu.org/licenses/>. */
37 #include <sys/param.h>
40 #include "nss_db/nss_db.h"
41 #include <libc-diag.h>
43 /* Get libc version number. */
44 #include "../version.h"
46 /* The hashing function we use. */
47 #include "../intl/hash-string.h"
49 /* SELinux support. */
51 # include <selinux/selinux.h>
55 # define MAP_POPULATE 0
58 #define PACKAGE _libc_intl_domainname
60 /* List of data bases. */
65 struct database
*next
;
74 static size_t ndatabases
;
75 static size_t nhashentries_total
;
76 static size_t valstrlen
;
77 static void *valstrtree
;
78 static char *valstrtab
;
79 static size_t extrastrlen
;
89 /* Stored string entry. */
98 /* True if any entry has been added. */
99 static bool any_dbentry
;
101 /* If non-zero convert key to lower case. */
102 static int to_lowercase
;
104 /* If non-zero print content of input file, one entry per line. */
107 /* If non-zero do not print informational messages. */
110 /* Name of output file. */
111 static const char *output_name
;
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
;
117 /* Definitions of arguments for argp functions. */
118 static const struct argp_option options
[] =
120 { "fold-case", 'f', NULL
, 0, N_("Convert key to lower case") },
121 { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") },
122 { "quiet", 'q', NULL
, 0,
123 N_("Do not print messages while building database") },
124 { "undo", 'u', NULL
, 0,
125 N_("Print content of database file, one entry a line") },
126 { "generated", 'g', N_("CHAR"), 0,
127 N_("Generated line not part of iteration") },
128 { NULL
, 0, NULL
, 0, NULL
}
131 /* Short description of program. */
132 static const char doc
[] = N_("Create simple database from textual input.");
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");
138 /* Prototype for option handler. */
139 static error_t
parse_opt (int key
, char *arg
, struct argp_state
*state
);
141 /* Function to print some extra text in the help message. */
142 static char *more_help (int key
, const char *text
, void *input
);
144 /* Data structure to communicate with argp functions. */
145 static struct argp argp
=
147 options
, parse_opt
, args_doc
, doc
, NULL
, more_help
151 /* List of databases which are not part of the iteration table. */
152 static struct db_option
155 struct db_option
*next
;
159 /* Prototypes for local functions. */
160 static int process_input (FILE *input
, const char *inname
,
161 int to_lowercase
, int be_quiet
);
162 static int print_database (int fd
);
163 static void compute_tables (void);
164 static int write_output (int fd
);
166 /* SELinux support. */
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);
172 # define set_file_creation_context(_outname,_mode)
173 # define reset_file_creation_context()
177 /* External functions. */
178 #include <programs/xmalloc.h>
182 main (int argc
, char *argv
[])
184 const char *input_name
;
189 /* Set locale via LC_ALL. */
190 setlocale (LC_ALL
, "");
192 /* Set the text message domain. */
193 textdomain (_libc_intl_domainname
);
195 /* Initialize local variables. */
198 /* Parse and process arguments. */
199 argp_parse (&argp
, argc
, argv
, 0, &remaining
, NULL
);
201 /* Determine file names. */
202 if (do_undo
|| output_name
!= NULL
)
204 if (remaining
+ 1 != argc
)
207 error (0, 0, gettext ("wrong number of arguments"));
208 argp_help (&argp
, stdout
, ARGP_HELP_SEE
,
209 program_invocation_short_name
);
212 input_name
= argv
[remaining
];
216 if (remaining
+ 2 != argc
)
217 goto wrong_arguments
;
219 input_name
= argv
[remaining
++];
220 output_name
= argv
[remaining
];
223 /* Special handling if we are asked to print the database. */
226 int fd
= open (input_name
, O_RDONLY
);
228 error (EXIT_FAILURE
, errno
, gettext ("cannot open database file `%s'"),
231 int status
= print_database (fd
);
238 /* Open input file. */
239 if (strcmp (input_name
, "-") == 0 || strcmp (input_name
, "/dev/stdin") == 0)
245 input_file
= fopen64 (input_name
, "r");
246 if (input_file
== NULL
)
247 error (EXIT_FAILURE
, errno
, gettext ("cannot open input file `%s'"),
250 /* Get the access rights from the source file. The output file should
252 if (fstat64 (fileno (input_file
), &st
) >= 0)
253 mode
= st
.st_mode
& ACCESSPERMS
;
256 /* Start the real work. */
257 int status
= process_input (input_file
, input_name
, to_lowercase
, be_quiet
);
260 if (input_file
!= stdin
)
263 /* No need to continue when we did not read the file successfully. */
264 if (status
!= EXIT_SUCCESS
)
267 /* Bail out if nothing is to be done. */
273 error (EXIT_SUCCESS
, 0, gettext ("no entries to be processed"));
276 /* Compute hash and string tables. */
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"));
285 set_file_creation_context (output_name
, mode
);
286 int fd
= mkstemp (tmp_output_name
);
287 reset_file_creation_context ();
289 error (EXIT_FAILURE
, errno
, gettext ("cannot create temporary file"));
291 status
= write_output (fd
);
293 if (status
== EXIT_SUCCESS
)
297 if (fstat64 (fd
, &st
) == 0)
299 if ((st
.st_mode
& ACCESSPERMS
) != mode
)
300 /* We ignore problems with changing the mode. */
305 error (0, errno
, gettext ("cannot stat newly created file"));
306 status
= EXIT_FAILURE
;
312 if (status
== EXIT_SUCCESS
)
314 if (rename (tmp_output_name
, output_name
) != 0)
316 error (0, errno
, gettext ("cannot rename temporary file"));
317 status
= EXIT_FAILURE
;
323 unlink (tmp_output_name
);
329 /* Handle program arguments. */
331 parse_opt (int key
, char *arg
, struct argp_state
*state
)
333 struct db_option
*newp
;
350 newp
= xmalloc (sizeof (*newp
));
352 newp
->next
= db_options
;
356 return ARGP_ERR_UNKNOWN
;
363 more_help (int key
, const char *text
, void *input
)
368 case ARGP_KEY_HELP_EXTRA
:
369 /* We print some extra information. */
370 if (asprintf (&tp
, gettext ("\
371 For bug reporting instructions, please see:\n\
372 %s.\n"), REPORT_BUGS_TO
) < 0)
378 return (char *) text
;
381 /* Print the version information. */
383 print_version (FILE *stream
, struct argp_state
*state
)
385 fprintf (stream
, "makedb %s%s\n", PKGVERSION
, VERSION
);
386 fprintf (stream
, gettext ("\
387 Copyright (C) %s Free Software Foundation, Inc.\n\
388 This is free software; see the source for copying conditions. There is NO\n\
389 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
391 fprintf (stream
, gettext ("Written by %s.\n"), "Ulrich Drepper");
396 dbentry_compare (const void *p1
, const void *p2
)
398 const struct dbentry
*d1
= (const struct dbentry
*) p1
;
399 const struct dbentry
*d2
= (const struct dbentry
*) p2
;
401 if (d1
->hashval
!= d2
->hashval
)
402 return d1
->hashval
< d2
->hashval
? -1 : 1;
404 return strcmp (d1
->str
, d2
->str
);
409 valstr_compare (const void *p1
, const void *p2
)
411 const struct valstrentry
*d1
= (const struct valstrentry
*) p1
;
412 const struct valstrentry
*d2
= (const struct valstrentry
*) p2
;
414 return strcmp (d1
->str
, d2
->str
);
419 process_input (FILE *input
, const char *inname
, int to_lowercase
, int be_quiet
)
428 status
= EXIT_SUCCESS
;
431 struct database
*last_database
= NULL
;
433 while (!feof_unlocked (input
))
435 ssize_t n
= getline (&line
, &linelen
, input
);
437 /* This means end of file or some bug. */
440 /* Short read. Probably interrupted system call. */
445 if (line
[n
- 1] == '\n')
446 /* Remove trailing newline. */
450 while (isspace (*cp
))
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. */
458 /* Skip over the character indicating the database so that it is not
459 affected by TO_LOWERCASE. */
461 while (*cp
!= '\0' && !isspace (*cp
))
469 /* It's a line without a value field. */
473 size_t keylen
= cp
- key
;
475 while (isspace (*cp
))
479 size_t datalen
= (&line
[n
] - cp
) + 1;
481 /* Find the database. */
482 if (last_database
== NULL
|| last_database
->dbid
!= key
[0])
484 last_database
= databases
;
485 while (last_database
!= NULL
&& last_database
->dbid
!= key
[0])
486 last_database
= last_database
->next
;
488 if (last_database
== NULL
)
490 last_database
= xmalloc (sizeof (*last_database
));
491 last_database
->dbid
= key
[0];
492 last_database
->extra_string
= false;
493 last_database
->next
= databases
;
494 last_database
->entries
= NULL
;
495 last_database
->nentries
= 0;
496 last_database
->keystrlen
= 0;
497 databases
= last_database
;
499 struct db_option
*runp
= db_options
;
501 if (runp
->dbid
== key
[0])
503 last_database
->extra_string
= true;
511 /* Skip the database selector. */
515 /* Store the data. */
516 struct valstrentry
*nentry
= xmalloc (sizeof (struct valstrentry
)
518 if (last_database
->extra_string
)
519 nentry
->idx
= extrastrlen
;
521 nentry
->idx
= valstrlen
;
522 nentry
->extra_string
= last_database
->extra_string
;
523 memcpy (nentry
->str
, data
, datalen
);
525 struct valstrentry
**fdata
= tsearch (nentry
, &valstrtree
,
528 error (EXIT_FAILURE
, errno
, gettext ("cannot create search tree"));
530 if (*fdata
!= nentry
)
532 /* We can reuse a string. */
537 if (last_database
->extra_string
)
538 extrastrlen
+= datalen
;
540 valstrlen
+= datalen
;
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
);
548 struct dbentry
**found
= tsearch (newp
, &last_database
->entries
,
551 error (EXIT_FAILURE
, errno
, gettext ("cannot create search tree"));
557 error_at_line (0, 0, inname
, linenr
, gettext ("duplicate key"));
561 ++last_database
->nentries
;
562 last_database
->keystrlen
+= keylen
;
567 if (ferror_unlocked (input
))
569 error (0, 0, gettext ("problems while reading `%s'"), inname
);
570 status
= EXIT_FAILURE
;
578 copy_valstr (const void *nodep
, const VISIT which
, const int depth
)
580 if (which
!= leaf
&& which
!= postorder
)
583 const struct valstrentry
*p
= *(const struct valstrentry
**) nodep
;
585 strcpy (valstrtab
+ (p
->extra_string
? valstrlen
: 0) + p
->idx
, p
->str
);
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. */
592 is_prime (size_t candidate
)
595 size_t sq
= divn
* divn
;
597 assert (candidate
> 4 && candidate
% 2 != 0);
599 while (sq
< candidate
&& candidate
% divn
!= 0)
606 return candidate
% divn
!= 0;
611 next_prime (size_t seed
)
613 /* Make sure that we're always greater than 4. */
614 seed
= (seed
+ 4) | 1;
616 while (!is_prime (seed
))
624 compute_tables (void)
626 valstrtab
= xmalloc (roundup (valstrlen
+ extrastrlen
, sizeof (stridx_t
)));
627 while ((valstrlen
+ extrastrlen
) % sizeof (stridx_t
) != 0)
628 valstrtab
[valstrlen
++] = '\0';
629 twalk (valstrtree
, copy_valstr
);
631 static struct database
*db
;
632 for (db
= databases
; db
!= NULL
; db
= db
->next
)
633 if (db
->nentries
!= 0)
637 /* We simply use an odd number large than twice the number of
638 elements to store in the hash table for the size. This gives
639 enough efficiency. */
640 #define TEST_RANGE 30
641 size_t nhashentries_min
= next_prime (db
->nentries
< TEST_RANGE
643 : db
->nentries
* 2 - TEST_RANGE
);
644 size_t nhashentries_max
= MAX (nhashentries_min
, db
->nentries
* 4);
645 size_t nhashentries_best
= nhashentries_min
;
646 size_t chainlength_best
= db
->nentries
;
648 db
->hashtable
= xmalloc (2 * nhashentries_max
* sizeof (stridx_t
)
650 db
->keyidxtab
= db
->hashtable
+ nhashentries_max
;
651 db
->keystrtab
= (char *) (db
->keyidxtab
+ nhashentries_max
);
653 static size_t max_chainlength
;
655 static size_t nhashentries
;
656 static bool copy_string
;
658 void add_key(const void *nodep
, const VISIT which
, const int depth
)
660 if (which
!= leaf
&& which
!= postorder
)
663 const struct dbentry
*dbe
= *(const struct dbentry
**) nodep
;
668 stridx
= wp
- db
->keystrtab
;
669 wp
= stpcpy (wp
, dbe
->str
) + 1;
674 size_t hidx
= dbe
->hashval
% nhashentries
;
675 size_t hval2
= 1 + dbe
->hashval
% (nhashentries
- 2);
676 size_t chainlength
= 0;
678 while (db
->hashtable
[hidx
] != ~((stridx_t
) 0))
681 if ((hidx
+= hval2
) >= nhashentries
)
682 hidx
-= nhashentries
;
685 db
->hashtable
[hidx
] = ((db
->extra_string
? valstrlen
: 0)
687 db
->keyidxtab
[hidx
] = stridx
;
689 max_chainlength
= MAX (max_chainlength
, chainlength
);
693 nhashentries
= nhashentries_min
;
694 for (size_t cnt
= 0; cnt
< TEST_RANGE
; ++cnt
)
696 memset (db
->hashtable
, '\xff', nhashentries
* sizeof (stridx_t
));
701 twalk (db
->entries
, add_key
);
703 if (max_chainlength
== 0)
705 /* No need to look further, this is as good as it gets. */
706 nhashentries_best
= nhashentries
;
710 if (max_chainlength
< chainlength_best
)
712 chainlength_best
= max_chainlength
;
713 nhashentries_best
= nhashentries
;
716 nhashentries
= next_prime (nhashentries
+ 1);
717 if (nhashentries
> nhashentries_max
)
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
));
728 twalk (db
->entries
, add_key
);
730 db
->nhashentries
= nhashentries_best
;
731 nhashentries_total
+= nhashentries_best
;
737 write_output (int fd
)
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
);
744 header
->magic
= NSS_DB_MAGIC
;
745 header
->ndbs
= ndatabases
;
746 header
->valstroffset
= file_offset
;
747 header
->valstrlen
= valstrlen
;
749 size_t filled_dbs
= 0;
750 size_t iov_nelts
= 2 + ndatabases
* 3;
751 struct iovec iov
[iov_nelts
];
752 iov
[0].iov_base
= header
;
753 iov
[0].iov_len
= file_offset
;
755 iov
[1].iov_base
= valstrtab
;
756 iov
[1].iov_len
= valstrlen
+ extrastrlen
;
757 file_offset
+= iov
[1].iov_len
;
759 size_t keydataoffset
= file_offset
+ nhashentries_total
* sizeof (stridx_t
);
760 for (struct database
*db
= databases
; db
!= NULL
; db
= db
->next
)
761 if (db
->entries
!= NULL
)
763 assert (file_offset
% sizeof (stridx_t
) == 0);
764 assert (filled_dbs
< ndatabases
);
766 header
->dbs
[filled_dbs
].id
= db
->dbid
;
767 memset (header
->dbs
[filled_dbs
].pad
, '\0',
768 sizeof (header
->dbs
[0].pad
));
769 header
->dbs
[filled_dbs
].hashsize
= db
->nhashentries
;
771 iov
[2 + filled_dbs
].iov_base
= db
->hashtable
;
772 iov
[2 + filled_dbs
].iov_len
= db
->nhashentries
* sizeof (stridx_t
);
773 header
->dbs
[filled_dbs
].hashoffset
= file_offset
;
774 file_offset
+= iov
[2 + filled_dbs
].iov_len
;
776 iov
[2 + ndatabases
+ filled_dbs
* 2].iov_base
= db
->keyidxtab
;
777 iov
[2 + ndatabases
+ filled_dbs
* 2].iov_len
778 = db
->nhashentries
* sizeof (stridx_t
);
779 header
->dbs
[filled_dbs
].keyidxoffset
= keydataoffset
;
780 keydataoffset
+= iov
[2 + ndatabases
+ filled_dbs
* 2].iov_len
;
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
;
790 assert (filled_dbs
== ndatabases
);
791 assert (file_offset
== (iov
[0].iov_len
+ iov
[1].iov_len
792 + nhashentries_total
* sizeof (stridx_t
)));
793 header
->allocate
= file_offset
;
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
799 DIAG_IGNORE_NEEDS_COMMENT (10, "-Wstringop-overflow");
802 assert (iov_nelts
<= INT_MAX
);
803 if (writev (fd
, iov
, iov_nelts
) != keydataoffset
)
805 error (0, errno
, gettext ("failed to write new database file"));
809 #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0)
810 DIAG_POP_NEEDS_COMMENT
;
818 print_database (int fd
)
821 if (fstat64 (fd
, &st
) != 0)
822 error (EXIT_FAILURE
, errno
, gettext ("cannot stat database file"));
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"));
829 if (header
->magic
!= NSS_DB_MAGIC
)
830 error (EXIT_FAILURE
, 0, gettext ("file not a database file"));
832 const char *valstrtab
= (const char *) header
+ header
->valstroffset
;
834 for (unsigned int dbidx
= 0; dbidx
< header
->ndbs
; ++dbidx
)
836 const stridx_t
*stridxtab
837 = ((const stridx_t
*) ((const char *) header
838 + header
->dbs
[dbidx
].keyidxoffset
));
839 const char *keystrtab
840 = (const char *) header
+ header
->dbs
[dbidx
].keystroffset
;
841 const stridx_t
*hashtab
842 = (const stridx_t
*) ((const char *) header
843 + header
->dbs
[dbidx
].hashoffset
);
845 for (uint32_t hidx
= 0; hidx
< header
->dbs
[dbidx
].hashsize
; ++hidx
)
846 if (hashtab
[hidx
] != ~((stridx_t
) 0))
848 header
->dbs
[dbidx
].id
,
849 keystrtab
+ stridxtab
[hidx
],
850 valstrtab
+ hashtab
[hidx
]);
859 /* security_context_t and matchpathcon (along with several other symbols) were
860 marked as deprecated by the SELinux API starting from version 3.1. We use
861 them here, but should eventually switch to the newer API. */
862 DIAG_PUSH_NEEDS_COMMENT
863 DIAG_IGNORE_NEEDS_COMMENT (10, "-Wdeprecated-declarations");
866 set_file_creation_context (const char *outname
, mode_t mode
)
869 static int enforcing
;
870 security_context_t ctx
;
872 /* Check if SELinux is enabled, and remember. */
874 enabled
= is_selinux_enabled () ? 1 : -1;
878 /* Check if SELinux is enforcing, and remember. */
880 enforcing
= security_getenforce () ? 1 : -1;
882 /* Determine the context which the file should have. */
884 if (matchpathcon (outname
, S_IFREG
| mode
, &ctx
) == 0 && ctx
!= NULL
)
886 if (setfscreatecon (ctx
) != 0)
887 error (enforcing
> 0 ? EXIT_FAILURE
: 0, 0,
888 gettext ("cannot set file creation context for `%s'"),
894 DIAG_POP_NEEDS_COMMENT
897 reset_file_creation_context (void)
899 setfscreatecon (NULL
);