1 /* Create simple DB database from textual input.
2 Copyright (C) 1996-2024 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
5 The GNU C Library is free software; you can redistribute it and/or
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.
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
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
28 #include <scratch_buffer.h>
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/label.h>
52 # include <selinux/selinux.h>
56 # define MAP_POPULATE 0
59 #define PACKAGE _libc_intl_domainname
61 /* List of data bases. */
66 struct database
*next
;
75 static size_t ndatabases
;
76 static size_t nhashentries_total
;
77 static size_t valstrlen
;
78 static void *valstrtree
;
79 static char *valstrtab
;
80 static size_t extrastrlen
;
90 /* Stored string entry. */
99 /* True if any entry has been added. */
100 static bool any_dbentry
;
102 /* If non-zero convert key to lower case. */
103 static int to_lowercase
;
105 /* If non-zero print content of input file, one entry per line. */
108 /* If non-zero do not print informational messages. */
111 /* Name of output file. */
112 static const char *output_name
;
114 /* Name and version of program. */
115 static void print_version (FILE *stream
, struct argp_state
*state
);
116 void (*argp_program_version_hook
) (FILE *, struct argp_state
*) = print_version
;
118 /* Definitions of arguments for argp functions. */
119 static const struct argp_option options
[] =
121 { "fold-case", 'f', NULL
, 0, N_("Convert key to lower case") },
122 { "output", 'o', N_("NAME"), 0, N_("Write output to file NAME") },
123 { "quiet", 'q', NULL
, 0,
124 N_("Do not print messages while building database") },
125 { "undo", 'u', NULL
, 0,
126 N_("Print content of database file, one entry a line") },
127 { "generated", 'g', N_("CHAR"), 0,
128 N_("Generated line not part of iteration") },
129 { NULL
, 0, NULL
, 0, NULL
}
132 /* Short description of program. */
133 static const char doc
[] = N_("Create simple database from textual input.");
135 /* Strings for arguments in help texts. */
136 static const char args_doc
[] = N_("\
137 INPUT-FILE OUTPUT-FILE\n-o OUTPUT-FILE INPUT-FILE\n-u INPUT-FILE");
139 /* Prototype for option handler. */
140 static error_t
parse_opt (int key
, char *arg
, struct argp_state
*state
);
142 /* Function to print some extra text in the help message. */
143 static char *more_help (int key
, const char *text
, void *input
);
145 /* Data structure to communicate with argp functions. */
146 static struct argp argp
=
148 options
, parse_opt
, args_doc
, doc
, NULL
, more_help
152 /* List of databases which are not part of the iteration table. */
153 static struct db_option
156 struct db_option
*next
;
160 /* Prototypes for local functions. */
161 static int process_input (FILE *input
, const char *inname
,
162 int to_lowercase
, int be_quiet
);
163 static int print_database (int fd
);
164 static void compute_tables (void);
165 static int write_output (int fd
);
167 /* SELinux support. */
169 /* Set the SELinux file creation context for the given file. */
170 static void set_file_creation_context (const char *outname
, mode_t mode
);
171 static void reset_file_creation_context (void);
173 # define set_file_creation_context(_outname,_mode)
174 # define reset_file_creation_context()
178 /* External functions. */
179 #include <programs/xmalloc.h>
183 main (int argc
, char *argv
[])
185 const char *input_name
;
190 /* Set locale via LC_ALL. */
191 setlocale (LC_ALL
, "");
193 /* Set the text message domain. */
194 textdomain (_libc_intl_domainname
);
196 /* Initialize local variables. */
199 /* Parse and process arguments. */
200 argp_parse (&argp
, argc
, argv
, 0, &remaining
, NULL
);
202 /* Determine file names. */
203 if (do_undo
|| output_name
!= NULL
)
205 if (remaining
+ 1 != argc
)
208 error (0, 0, gettext ("wrong number of arguments"));
209 argp_help (&argp
, stdout
, ARGP_HELP_SEE
,
210 program_invocation_short_name
);
213 input_name
= argv
[remaining
];
217 if (remaining
+ 2 != argc
)
218 goto wrong_arguments
;
220 input_name
= argv
[remaining
++];
221 output_name
= argv
[remaining
];
224 /* Special handling if we are asked to print the database. */
227 int fd
= open (input_name
, O_RDONLY
);
229 error (EXIT_FAILURE
, errno
, gettext ("cannot open database file `%s'"),
232 int status
= print_database (fd
);
239 /* Open input file. */
240 if (strcmp (input_name
, "-") == 0 || strcmp (input_name
, "/dev/stdin") == 0)
246 input_file
= fopen64 (input_name
, "r");
247 if (input_file
== NULL
)
248 error (EXIT_FAILURE
, errno
, gettext ("cannot open input file `%s'"),
251 /* Get the access rights from the source file. The output file should
253 if (fstat64 (fileno (input_file
), &st
) >= 0)
254 mode
= st
.st_mode
& ACCESSPERMS
;
257 /* Start the real work. */
258 int status
= process_input (input_file
, input_name
, to_lowercase
, be_quiet
);
261 if (input_file
!= stdin
)
264 /* No need to continue when we did not read the file successfully. */
265 if (status
!= EXIT_SUCCESS
)
268 /* Bail out if nothing is to be done. */
274 error (EXIT_SUCCESS
, 0, gettext ("no entries to be processed"));
277 /* Compute hash and string tables. */
280 /* Open output file. This must not be standard output so we don't
281 handle "-" and "/dev/stdout" special. */
282 char *tmp_output_name
;
283 if (asprintf (&tmp_output_name
, "%s.XXXXXX", output_name
) == -1)
284 error (EXIT_FAILURE
, errno
, gettext ("cannot create temporary file name"));
286 set_file_creation_context (output_name
, mode
);
287 int fd
= mkstemp (tmp_output_name
);
288 reset_file_creation_context ();
290 error (EXIT_FAILURE
, errno
, gettext ("cannot create temporary file"));
292 status
= write_output (fd
);
294 if (status
== EXIT_SUCCESS
)
298 if (fstat64 (fd
, &st
) == 0)
300 if ((st
.st_mode
& ACCESSPERMS
) != mode
)
301 /* We ignore problems with changing the mode. */
306 error (0, errno
, gettext ("cannot stat newly created file"));
307 status
= EXIT_FAILURE
;
313 if (status
== EXIT_SUCCESS
)
315 if (rename (tmp_output_name
, output_name
) != 0)
317 error (0, errno
, gettext ("cannot rename temporary file"));
318 status
= EXIT_FAILURE
;
324 unlink (tmp_output_name
);
330 /* Handle program arguments. */
332 parse_opt (int key
, char *arg
, struct argp_state
*state
)
334 struct db_option
*newp
;
351 newp
= xmalloc (sizeof (*newp
));
353 newp
->next
= db_options
;
357 return ARGP_ERR_UNKNOWN
;
364 more_help (int key
, const char *text
, void *input
)
369 case ARGP_KEY_HELP_EXTRA
:
370 /* We print some extra information. */
371 if (asprintf (&tp
, gettext ("\
372 For bug reporting instructions, please see:\n\
373 %s.\n"), REPORT_BUGS_TO
) < 0)
379 return (char *) text
;
382 /* Print the version information. */
384 print_version (FILE *stream
, struct argp_state
*state
)
386 fprintf (stream
, "makedb %s%s\n", PKGVERSION
, VERSION
);
387 fprintf (stream
, gettext ("\
388 Copyright (C) %s Free Software Foundation, Inc.\n\
389 This is free software; see the source for copying conditions. There is NO\n\
390 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
392 fprintf (stream
, gettext ("Written by %s.\n"), "Ulrich Drepper");
397 dbentry_compare (const void *p1
, const void *p2
)
399 const struct dbentry
*d1
= (const struct dbentry
*) p1
;
400 const struct dbentry
*d2
= (const struct dbentry
*) p2
;
402 if (d1
->hashval
!= d2
->hashval
)
403 return d1
->hashval
< d2
->hashval
? -1 : 1;
405 return strcmp (d1
->str
, d2
->str
);
410 valstr_compare (const void *p1
, const void *p2
)
412 const struct valstrentry
*d1
= (const struct valstrentry
*) p1
;
413 const struct valstrentry
*d2
= (const struct valstrentry
*) p2
;
415 return strcmp (d1
->str
, d2
->str
);
420 process_input (FILE *input
, const char *inname
, int to_lowercase
, int be_quiet
)
429 status
= EXIT_SUCCESS
;
432 struct database
*last_database
= NULL
;
434 while (!feof_unlocked (input
))
436 ssize_t n
= getline (&line
, &linelen
, input
);
438 /* This means end of file or some bug. */
441 /* Short read. Probably interrupted system call. */
446 if (line
[n
- 1] == '\n')
447 /* Remove trailing newline. */
451 while (isspace (*cp
))
454 if (*cp
== '#' || *cp
== '\0')
455 /* First non-space character in line '#': it's a comment.
456 Also go to the next line if it is empty except for whitespaces. */
459 /* Skip over the character indicating the database so that it is not
460 affected by TO_LOWERCASE. */
462 while (*cp
!= '\0' && !isspace (*cp
))
470 /* It's a line without a value field. */
474 size_t keylen
= cp
- key
;
476 while (isspace (*cp
))
480 size_t datalen
= (&line
[n
] - cp
) + 1;
482 /* Find the database. */
483 if (last_database
== NULL
|| last_database
->dbid
!= key
[0])
485 last_database
= databases
;
486 while (last_database
!= NULL
&& last_database
->dbid
!= key
[0])
487 last_database
= last_database
->next
;
489 if (last_database
== NULL
)
491 last_database
= xmalloc (sizeof (*last_database
));
492 last_database
->dbid
= key
[0];
493 last_database
->extra_string
= false;
494 last_database
->next
= databases
;
495 last_database
->entries
= NULL
;
496 last_database
->nentries
= 0;
497 last_database
->keystrlen
= 0;
498 databases
= last_database
;
500 struct db_option
*runp
= db_options
;
502 if (runp
->dbid
== key
[0])
504 last_database
->extra_string
= true;
512 /* Skip the database selector. */
516 /* Store the data. */
517 struct valstrentry
*nentry
= xmalloc (sizeof (struct valstrentry
)
519 if (last_database
->extra_string
)
520 nentry
->idx
= extrastrlen
;
522 nentry
->idx
= valstrlen
;
523 nentry
->extra_string
= last_database
->extra_string
;
524 memcpy (nentry
->str
, data
, datalen
);
526 struct valstrentry
**fdata
= tsearch (nentry
, &valstrtree
,
529 error (EXIT_FAILURE
, errno
, gettext ("cannot create search tree"));
531 if (*fdata
!= nentry
)
533 /* We can reuse a string. */
538 if (last_database
->extra_string
)
539 extrastrlen
+= datalen
;
541 valstrlen
+= datalen
;
544 struct dbentry
*newp
= xmalloc (sizeof (struct dbentry
) + keylen
);
545 newp
->validx
= nentry
->idx
;
546 newp
->hashval
= __hash_string (key
);
547 memcpy (newp
->str
, key
, keylen
);
549 struct dbentry
**found
= tsearch (newp
, &last_database
->entries
,
552 error (EXIT_FAILURE
, errno
, gettext ("cannot create search tree"));
558 error_at_line (0, 0, inname
, linenr
, gettext ("duplicate key"));
562 ++last_database
->nentries
;
563 last_database
->keystrlen
+= keylen
;
568 if (ferror_unlocked (input
))
570 error (0, 0, gettext ("problems while reading `%s'"), inname
);
571 status
= EXIT_FAILURE
;
579 copy_valstr (const void *nodep
, const VISIT which
, const int depth
)
581 if (which
!= leaf
&& which
!= postorder
)
584 const struct valstrentry
*p
= *(const struct valstrentry
**) nodep
;
586 strcpy (valstrtab
+ (p
->extra_string
? valstrlen
: 0) + p
->idx
, p
->str
);
590 /* Determine if the candidate is prime by using a modified trial division
591 algorithm. The candidate must be both odd and greater than 4. */
593 is_prime (size_t candidate
)
596 size_t sq
= divn
* divn
;
598 assert (candidate
> 4 && candidate
% 2 != 0);
600 while (sq
< candidate
&& candidate
% divn
!= 0)
607 return candidate
% divn
!= 0;
612 next_prime (size_t seed
)
614 /* Make sure that we're always greater than 4. */
615 seed
= (seed
+ 4) | 1;
617 while (!is_prime (seed
))
623 static size_t max_chainlength
;
625 static size_t nhashentries
;
626 static bool copy_string
;
628 void add_key(const void *nodep
, VISIT which
, void *arg
)
630 if (which
!= leaf
&& which
!= postorder
)
633 const struct database
*db
= (const struct database
*) arg
;
634 const struct dbentry
*dbe
= *(const struct dbentry
**) nodep
;
639 stridx
= wp
- db
->keystrtab
;
640 wp
= stpcpy (wp
, dbe
->str
) + 1;
645 size_t hidx
= dbe
->hashval
% nhashentries
;
646 size_t hval2
= 1 + dbe
->hashval
% (nhashentries
- 2);
647 size_t chainlength
= 0;
649 while (db
->hashtable
[hidx
] != ~((stridx_t
) 0))
652 if ((hidx
+= hval2
) >= nhashentries
)
653 hidx
-= nhashentries
;
656 db
->hashtable
[hidx
] = ((db
->extra_string
? valstrlen
: 0)
658 db
->keyidxtab
[hidx
] = stridx
;
660 max_chainlength
= MAX (max_chainlength
, chainlength
);
664 compute_tables (void)
666 valstrtab
= xmalloc (roundup (valstrlen
+ extrastrlen
, sizeof (stridx_t
)));
667 while ((valstrlen
+ extrastrlen
) % sizeof (stridx_t
) != 0)
668 valstrtab
[valstrlen
++] = '\0';
669 twalk (valstrtree
, copy_valstr
);
671 static struct database
*db
;
672 for (db
= databases
; db
!= NULL
; db
= db
->next
)
673 if (db
->nentries
!= 0)
677 /* We simply use an odd number large than twice the number of
678 elements to store in the hash table for the size. This gives
679 enough efficiency. */
680 #define TEST_RANGE 30
681 size_t nhashentries_min
= next_prime (db
->nentries
< TEST_RANGE
683 : db
->nentries
* 2 - TEST_RANGE
);
684 size_t nhashentries_max
= MAX (nhashentries_min
, db
->nentries
* 4);
685 size_t nhashentries_best
= nhashentries_min
;
686 size_t chainlength_best
= db
->nentries
;
688 db
->hashtable
= xmalloc (2 * nhashentries_max
* sizeof (stridx_t
)
690 db
->keyidxtab
= db
->hashtable
+ nhashentries_max
;
691 db
->keystrtab
= (char *) (db
->keyidxtab
+ nhashentries_max
);
694 nhashentries
= nhashentries_min
;
695 for (size_t cnt
= 0; cnt
< TEST_RANGE
; ++cnt
)
697 memset (db
->hashtable
, '\xff', nhashentries
* sizeof (stridx_t
));
702 twalk_r (db
->entries
, add_key
, db
);
704 if (max_chainlength
== 0)
706 /* No need to look further, this is as good as it gets. */
707 nhashentries_best
= nhashentries
;
711 if (max_chainlength
< chainlength_best
)
713 chainlength_best
= max_chainlength
;
714 nhashentries_best
= nhashentries
;
717 nhashentries
= next_prime (nhashentries
+ 1);
718 if (nhashentries
> nhashentries_max
)
722 /* Recompute the best table again, this time fill in the strings. */
723 nhashentries
= nhashentries_best
;
724 memset (db
->hashtable
, '\xff',
725 2 * nhashentries_max
* sizeof (stridx_t
));
729 twalk_r (db
->entries
, add_key
, db
);
731 db
->nhashentries
= nhashentries_best
;
732 nhashentries_total
+= nhashentries_best
;
738 write_output (int fd
)
740 struct nss_db_header
*header
;
741 uint64_t file_offset
= (sizeof (struct nss_db_header
)
742 + (ndatabases
* sizeof (header
->dbs
[0])));
743 struct scratch_buffer sbuf
;
744 scratch_buffer_init (&sbuf
);
746 if (!scratch_buffer_set_array_size (&sbuf
, 1, file_offset
))
748 error (0, errno
, gettext ("failed to allocate memory"));
753 header
->magic
= NSS_DB_MAGIC
;
754 header
->ndbs
= ndatabases
;
755 header
->valstroffset
= file_offset
;
756 header
->valstrlen
= valstrlen
;
758 size_t filled_dbs
= 0;
759 size_t iov_nelts
= 2 + ndatabases
* 3;
760 struct iovec iov
[iov_nelts
];
761 iov
[0].iov_base
= header
;
762 iov
[0].iov_len
= file_offset
;
764 iov
[1].iov_base
= valstrtab
;
765 iov
[1].iov_len
= valstrlen
+ extrastrlen
;
766 file_offset
+= iov
[1].iov_len
;
768 size_t keydataoffset
= file_offset
+ nhashentries_total
* sizeof (stridx_t
);
769 for (struct database
*db
= databases
; db
!= NULL
; db
= db
->next
)
770 if (db
->entries
!= NULL
)
772 assert (file_offset
% sizeof (stridx_t
) == 0);
773 assert (filled_dbs
< ndatabases
);
775 header
->dbs
[filled_dbs
].id
= db
->dbid
;
776 memset (header
->dbs
[filled_dbs
].pad
, '\0',
777 sizeof (header
->dbs
[0].pad
));
778 header
->dbs
[filled_dbs
].hashsize
= db
->nhashentries
;
780 iov
[2 + filled_dbs
].iov_base
= db
->hashtable
;
781 iov
[2 + filled_dbs
].iov_len
= db
->nhashentries
* sizeof (stridx_t
);
782 header
->dbs
[filled_dbs
].hashoffset
= file_offset
;
783 file_offset
+= iov
[2 + filled_dbs
].iov_len
;
785 iov
[2 + ndatabases
+ filled_dbs
* 2].iov_base
= db
->keyidxtab
;
786 iov
[2 + ndatabases
+ filled_dbs
* 2].iov_len
787 = db
->nhashentries
* sizeof (stridx_t
);
788 header
->dbs
[filled_dbs
].keyidxoffset
= keydataoffset
;
789 keydataoffset
+= iov
[2 + ndatabases
+ filled_dbs
* 2].iov_len
;
791 iov
[3 + ndatabases
+ filled_dbs
* 2].iov_base
= db
->keystrtab
;
792 iov
[3 + ndatabases
+ filled_dbs
* 2].iov_len
= db
->keystrlen
;
793 header
->dbs
[filled_dbs
].keystroffset
= keydataoffset
;
794 keydataoffset
+= iov
[3 + ndatabases
+ filled_dbs
* 2].iov_len
;
799 assert (filled_dbs
== ndatabases
);
800 assert (file_offset
== (iov
[0].iov_len
+ iov
[1].iov_len
801 + nhashentries_total
* sizeof (stridx_t
)));
802 header
->allocate
= file_offset
;
804 #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0)
805 DIAG_PUSH_NEEDS_COMMENT
;
806 /* Avoid GCC 10 false positive warning: specified size exceeds maximum
808 DIAG_IGNORE_NEEDS_COMMENT (10, "-Wstringop-overflow");
811 assert (iov_nelts
<= INT_MAX
);
812 if (writev (fd
, iov
, iov_nelts
) != keydataoffset
)
814 error (0, errno
, gettext ("failed to write new database file"));
815 scratch_buffer_free (&sbuf
);
819 #if __GNUC_PREREQ (10, 0) && !__GNUC_PREREQ (11, 0)
820 DIAG_POP_NEEDS_COMMENT
;
823 scratch_buffer_free (&sbuf
);
829 print_database (int fd
)
832 if (fstat64 (fd
, &st
) != 0)
833 error (EXIT_FAILURE
, errno
, gettext ("cannot stat database file"));
835 const struct nss_db_header
*header
= mmap (NULL
, st
.st_size
, PROT_READ
,
836 MAP_PRIVATE
|MAP_POPULATE
, fd
, 0);
837 if (header
== MAP_FAILED
)
838 error (EXIT_FAILURE
, errno
, gettext ("cannot map database file"));
840 if (header
->magic
!= NSS_DB_MAGIC
)
841 error (EXIT_FAILURE
, 0, gettext ("file not a database file"));
843 const char *valstrtab
= (const char *) header
+ header
->valstroffset
;
845 for (unsigned int dbidx
= 0; dbidx
< header
->ndbs
; ++dbidx
)
847 const stridx_t
*stridxtab
848 = ((const stridx_t
*) ((const char *) header
849 + header
->dbs
[dbidx
].keyidxoffset
));
850 const char *keystrtab
851 = (const char *) header
+ header
->dbs
[dbidx
].keystroffset
;
852 const stridx_t
*hashtab
853 = (const stridx_t
*) ((const char *) header
854 + header
->dbs
[dbidx
].hashoffset
);
856 for (uint32_t hidx
= 0; hidx
< header
->dbs
[dbidx
].hashsize
; ++hidx
)
857 if (hashtab
[hidx
] != ~((stridx_t
) 0))
859 header
->dbs
[dbidx
].id
,
860 keystrtab
+ stridxtab
[hidx
],
861 valstrtab
+ hashtab
[hidx
]);
871 set_file_creation_context (const char *outname
, mode_t mode
)
874 static int enforcing
;
875 struct selabel_handle
*label_hnd
= NULL
;
878 /* Check if SELinux is enabled, and remember. */
880 enabled
= is_selinux_enabled () ? 1 : -1;
884 /* Check if SELinux is enforcing, and remember. */
886 enforcing
= security_getenforce () ? 1 : -1;
888 /* Open the file contexts backend. */
889 label_hnd
= selabel_open(SELABEL_CTX_FILE
, NULL
, 0);
892 error (enforcing
> 0 ? EXIT_FAILURE
: 0, 0,
893 gettext ("cannot initialize SELinux context"));
896 /* Determine the context which the file should have. */
898 if (selabel_lookup(label_hnd
, &ctx
, outname
, S_IFREG
| mode
) == 0)
900 if (setfscreatecon (ctx
) != 0)
901 error (enforcing
> 0 ? EXIT_FAILURE
: 0, 0,
902 gettext ("cannot set file creation context for `%s'"),
908 /* Close the file contexts backend. */
909 selabel_close(label_hnd
);
913 reset_file_creation_context (void)
915 setfscreatecon (NULL
);