]> git.sesse.net Git - plocate/blob - conf.cpp
Release plocate 1.1.22.
[plocate] / conf.cpp
1 /* updatedb configuration.
2
3 Copyright (C) 2005, 2007, 2008 Red Hat, Inc. All rights reserved.
4 This copyrighted material is made available to anyone wishing to use, modify,
5 copy, or redistribute it subject to the terms and conditions of the GNU General
6 Public License v.2.
7
8 This program is distributed in the hope that it will be useful, but WITHOUT ANY
9 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 PARTICULAR PURPOSE. See the GNU General Public License for more details.
11
12 You should have received a copy of the GNU General Public License along with
13 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
14 Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
16 Author: Miloslav Trmac <mitr@redhat.com>
17
18 plocate modifications: Copyright (C) 2020 Steinar H. Gunderson.
19 plocate parts and modifications are licensed under the GPLv2 or, at your option,
20 any later version.
21  */
22
23 #include "conf.h"
24
25 #include "error.h"
26 #include "lib.h"
27
28 #include <algorithm>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <getopt.h>
32 #include <limits.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38
39 using namespace std;
40
41 /* true if locate(1) should check whether files are visible before reporting
42    them */
43 bool conf_check_visibility = true;
44
45 /* Filesystems to skip, converted to uppercase and sorted by name */
46 vector<string> conf_prunefs;
47
48 /* Directory names to skip, sorted by name */
49 vector<string> conf_prunenames;
50
51 /* Paths to skip, sorted by name using dir_path_cmp () */
52 vector<string> conf_prunepaths;
53
54 /* true if bind mounts should be skipped */
55 bool conf_prune_bind_mounts; /* = false; */
56
57 /* true if pruning debug output was requested */
58 bool conf_debug_pruning; /* = false; */
59
60 /* Root of the directory tree to store in the database (canonical) */
61 char *conf_scan_root; /* = NULL; */
62
63 /* Absolute (not necessarily canonical) path to the database */
64 string conf_output;
65
66 /* 1 if file names should be written to stdout as they are found */
67 bool conf_verbose; /* = false; */
68
69 /* Configuration representation for the database configuration block */
70 string conf_block;
71
72 int conf_block_size = 32;
73 bool use_debug = false;
74
75 /* Parse a STR, store the parsed boolean value to DEST;
76    return 0 if OK, -1 on error. */
77 static int
78 parse_bool(bool *dest, const char *str)
79 {
80         if (strcmp(str, "0") == 0 || strcmp(str, "no") == 0) {
81                 *dest = false;
82                 return 0;
83         }
84         if (strcmp(str, "1") == 0 || strcmp(str, "yes") == 0) {
85                 *dest = true;
86                 return 0;
87         }
88         return -1;
89 }
90
91 /* String list handling */
92
93 /* Add values from space-separated VAL to VAR and LIST */
94 static void
95 var_add_values(vector<string> *list, const char *val)
96 {
97         for (;;) {
98                 const char *start;
99
100                 while (isspace((unsigned char)*val))
101                         val++;
102                 if (*val == 0)
103                         break;
104                 start = val;
105                 do
106                         val++;
107                 while (*val != 0 && !isspace((unsigned char)*val));
108                 list->emplace_back(start, val - start);
109         }
110 }
111
112 /* Finish variable LIST, sort its contents, remove duplicates */
113 static void
114 var_finish(vector<string> *list)
115 {
116         sort(list->begin(), list->end());
117         auto new_end = unique(list->begin(), list->end());
118         list->erase(new_end, list->end());
119 }
120
121 /* UPDATEDB_CONF parsing */
122
123 /* UPDATEDB_CONF (locked) */
124 static FILE *uc_file;
125 /* Line number at token start; type matches error_at_line () */
126 static unsigned uc_line;
127 /* Current line number; type matches error_at_line () */
128 static unsigned uc_current_line;
129 /* Last string returned by uc_lex */
130 static string uc_lex_buf;
131
132 /* Token types */
133 enum {
134         UCT_EOF,
135         UCT_EOL,
136         UCT_IDENTIFIER,
137         UCT_EQUAL,
138         UCT_QUOTED,
139         UCT_OTHER,
140         UCT_PRUNE_BIND_MOUNTS,
141         UCT_PRUNEFS,
142         UCT_PRUNENAMES,
143         UCT_PRUNEPATHS
144 };
145
146 /* Return next token from uc_file; for UCT_IDENTIFIER, UCT_QUOTED or keywords,
147    store the data to uc_lex_buf (valid until next call). */
148 static int
149 uc_lex(void)
150 {
151         int c;
152
153         uc_lex_buf.clear();
154         uc_line = uc_current_line;
155         do {
156                 c = getc_unlocked(uc_file);
157                 if (c == EOF)
158                         return UCT_EOF;
159         } while (c != '\n' && isspace((unsigned char)c));
160         switch (c) {
161         case '#':
162                 do {
163                         c = getc_unlocked(uc_file);
164                         if (c == EOF)
165                                 return UCT_EOF;
166                 } while (c != '\n');
167                 /* Fall through */
168         case '\n':
169                 uc_current_line++;
170                 if (uc_current_line == 0) {
171                         error_at_line(0, 0, UPDATEDB_CONF, uc_current_line - 1,
172                                       _("warning: Line number overflow"));
173                         error_message_count--; /* Don't count as an error */
174                 }
175                 return UCT_EOL;
176
177         case '=':
178                 return UCT_EQUAL;
179
180         case '"': {
181                 while ((c = getc_unlocked(uc_file)) != '"') {
182                         if (c == EOF || c == '\n') {
183                                 error_at_line(0, 0, UPDATEDB_CONF, uc_line,
184                                               _("missing closing `\"'"));
185                                 ungetc(c, uc_file);
186                                 break;
187                         }
188                         uc_lex_buf.push_back(c);
189                 }
190                 return UCT_QUOTED;
191         }
192
193         default: {
194                 if (!isalpha((unsigned char)c) && c != '_')
195                         return UCT_OTHER;
196                 do {
197                         uc_lex_buf.push_back(c);
198                         c = getc_unlocked(uc_file);
199                 } while (c != EOF && (isalnum((unsigned char)c) || c == '_'));
200                 ungetc(c, uc_file);
201                 if (uc_lex_buf == "PRUNE_BIND_MOUNTS")
202                         return UCT_PRUNE_BIND_MOUNTS;
203                 if (uc_lex_buf == "PRUNEFS")
204                         return UCT_PRUNEFS;
205                 if (uc_lex_buf == "PRUNENAMES")
206                         return UCT_PRUNENAMES;
207                 if (uc_lex_buf == "PRUNEPATHS")
208                         return UCT_PRUNEPATHS;
209                 return UCT_IDENTIFIER;
210         }
211         }
212 }
213
214 /* Parse /etc/updatedb.conf.  Exit on I/O or syntax error. */
215 static void
216 parse_updatedb_conf(void)
217 {
218         int old_error_one_per_line;
219         unsigned old_error_message_count;
220         bool had_prune_bind_mounts, had_prunefs, had_prunenames, had_prunepaths;
221
222         uc_file = fopen(UPDATEDB_CONF, "r");
223         if (uc_file == NULL) {
224                 if (errno != ENOENT)
225                         error(EXIT_FAILURE, errno, _("can not open `%s'"), UPDATEDB_CONF);
226                 goto err;
227         }
228         flockfile(uc_file);
229         uc_current_line = 1;
230         old_error_message_count = error_message_count;
231         old_error_one_per_line = error_one_per_line;
232         error_one_per_line = 1;
233         had_prune_bind_mounts = false;
234         had_prunefs = false;
235         had_prunenames = false;
236         had_prunepaths = false;
237         for (;;) {
238                 bool *had_var;
239                 int var_token, token;
240
241                 token = uc_lex();
242                 switch (token) {
243                 case UCT_EOF:
244                         goto eof;
245
246                 case UCT_EOL:
247                         continue;
248
249                 case UCT_PRUNE_BIND_MOUNTS:
250                         had_var = &had_prune_bind_mounts;
251                         break;
252
253                 case UCT_PRUNEFS:
254                         had_var = &had_prunefs;
255                         break;
256
257                 case UCT_PRUNENAMES:
258                         had_var = &had_prunenames;
259                         break;
260
261                 case UCT_PRUNEPATHS:
262                         had_var = &had_prunepaths;
263                         break;
264
265                 case UCT_IDENTIFIER:
266                         error_at_line(0, 0, UPDATEDB_CONF, uc_line,
267                                       _("unknown variable `%s'"), uc_lex_buf.c_str());
268                         goto skip_to_eol;
269
270                 default:
271                         error_at_line(0, 0, UPDATEDB_CONF, uc_line,
272                                       _("variable name expected"));
273                         goto skip_to_eol;
274                 }
275                 if (*had_var != false) {
276                         error_at_line(0, 0, UPDATEDB_CONF, uc_line,
277                                       _("variable `%s' was already defined"), uc_lex_buf.c_str());
278                         goto skip_to_eol;
279                 }
280                 *had_var = true;
281                 var_token = token;
282                 token = uc_lex();
283                 if (token != UCT_EQUAL) {
284                         error_at_line(0, 0, UPDATEDB_CONF, uc_line,
285                                       _("`=' expected after variable name"));
286                         goto skip_to_eol;
287                 }
288                 token = uc_lex();
289                 if (token != UCT_QUOTED) {
290                         error_at_line(0, 0, UPDATEDB_CONF, uc_line,
291                                       _("value in quotes expected after `='"));
292                         goto skip_to_eol;
293                 }
294                 if (var_token == UCT_PRUNE_BIND_MOUNTS) {
295                         if (parse_bool(&conf_prune_bind_mounts, uc_lex_buf.c_str()) != 0) {
296                                 error_at_line(0, 0, UPDATEDB_CONF, uc_line,
297                                               _("invalid value `%s' of PRUNE_BIND_MOUNTS"),
298                                               uc_lex_buf.c_str());
299                                 goto skip_to_eol;
300                         }
301                 } else if (var_token == UCT_PRUNEFS)
302                         var_add_values(&conf_prunefs, uc_lex_buf.c_str());
303                 else if (var_token == UCT_PRUNENAMES)
304                         var_add_values(&conf_prunenames, uc_lex_buf.c_str());
305                 else if (var_token == UCT_PRUNEPATHS)
306                         var_add_values(&conf_prunepaths, uc_lex_buf.c_str());
307                 else
308                         abort();
309                 token = uc_lex();
310                 if (token != UCT_EOL && token != UCT_EOF) {
311                         error_at_line(0, 0, UPDATEDB_CONF, uc_line,
312                                       _("unexpected data after variable value"));
313                         goto skip_to_eol;
314                 }
315                 /* Fall through */
316         skip_to_eol:
317                 while (token != UCT_EOL) {
318                         if (token == UCT_EOF)
319                                 goto eof;
320                         token = uc_lex();
321                 }
322         }
323 eof:
324         if (ferror(uc_file))
325                 error(EXIT_FAILURE, 0, _("I/O error reading `%s'"), UPDATEDB_CONF);
326         error_one_per_line = old_error_one_per_line;
327         funlockfile(uc_file);
328         fclose(uc_file);
329         if (error_message_count != old_error_message_count)
330                 exit(EXIT_FAILURE);
331 err:;
332 }
333
334 /* Command-line argument parsing */
335
336 /* Output --help text */
337 static void
338 help(void)
339 {
340         printf(_("Usage: updatedb [OPTION]...\n"
341                  "Update a plocate database.\n"
342                  "\n"
343                  "  -f, --add-prunefs FS           omit also FS\n"
344                  "  -n, --add-prunenames NAMES     omit also NAMES\n"
345                  "  -e, --add-prunepaths PATHS     omit also PATHS\n"
346                  "  -U, --database-root PATH       the subtree to store in "
347                  "database (default \"/\")\n"
348                  "  -h, --help                     print this help\n"
349                  "  -o, --output FILE              database to update (default\n"
350                  "                                 `%s')\n"
351                  "  -b, --block-size SIZE          number of filenames to store\n"
352                  "                                 in each block (default 32)\n"
353                  "      --prune-bind-mounts FLAG   omit bind mounts (default "
354                  "\"no\")\n"
355                  "      --prunefs FS               filesystems to omit from "
356                  "database\n"
357                  "      --prunenames NAMES         directory names to omit from "
358                  "database\n"
359                  "      --prunepaths PATHS         paths to omit from database\n"
360                  "  -l, --require-visibility FLAG  check visibility before "
361                  "reporting files\n"
362                  "                                 (default \"yes\")\n"
363                  "  -v, --verbose                  print paths of files as they "
364                  "are found\n"
365                  "  -V, --version                  print version information\n"
366                  "\n"
367                  "The configuration defaults to values read from\n"
368                  "`%s'.\n"),
369                DBFILE, UPDATEDB_CONF);
370         printf(_("\n"
371                  "Report bugs to %s.\n"),
372                PACKAGE_BUGREPORT);
373 }
374
375 /* Prepend current working directory to PATH;
376    return resulting path */
377 static string
378 prepend_cwd(const string &path)
379 {
380         const char *res;
381         string buf;
382         buf.resize(BUFSIZ); /* Not PATH_MAX because it is not defined on some platforms. */
383         do
384                 buf.resize(buf.size() * 1.5);
385         while ((res = getcwd(buf.data(), buf.size())) == NULL && errno == ERANGE);
386         if (res == NULL)
387                 error(EXIT_FAILURE, errno, _("can not get current working directory"));
388         buf.resize(strlen(buf.data()));
389         return buf + '/' + path;
390 }
391
392 /* Parse ARGC, ARGV.  Exit on error or --help, --version. */
393 static void
394 parse_arguments(int argc, char *argv[])
395 {
396         enum { OPT_DEBUG_PRUNING = CHAR_MAX + 1 };
397
398         static const struct option options[] = {
399                 { "add-prunefs", required_argument, NULL, 'f' },
400                 { "add-prunenames", required_argument, NULL, 'n' },
401                 { "add-prunepaths", required_argument, NULL, 'e' },
402                 { "database-root", required_argument, NULL, 'U' },
403                 { "debug-pruning", no_argument, NULL, OPT_DEBUG_PRUNING },
404                 { "help", no_argument, NULL, 'h' },
405                 { "output", required_argument, NULL, 'o' },
406                 { "prune-bind-mounts", required_argument, NULL, 'B' },
407                 { "prunefs", required_argument, NULL, 'F' },
408                 { "prunenames", required_argument, NULL, 'N' },
409                 { "prunepaths", required_argument, NULL, 'P' },
410                 { "require-visibility", required_argument, NULL, 'l' },
411                 { "verbose", no_argument, NULL, 'v' },
412                 { "version", no_argument, NULL, 'V' },
413                 { "block-size", required_argument, 0, 'b' },
414                 { "debug", no_argument, 0, 'D' },  // Not documented.
415                 { NULL, 0, NULL, 0 }
416         };
417
418         bool prunefs_changed, prunenames_changed, prunepaths_changed;
419         bool got_prune_bind_mounts, got_visibility;
420
421         prunefs_changed = false;
422         prunenames_changed = false;
423         prunepaths_changed = false;
424         got_prune_bind_mounts = false;
425         got_visibility = false;
426         for (;;) {
427                 int opt, idx;
428
429                 opt = getopt_long(argc, argv, "U:Ve:f:hl:n:o:vb:D", options, &idx);
430                 switch (opt) {
431                 case -1:
432                         goto options_done;
433
434                 case '?':
435                         exit(EXIT_FAILURE);
436
437                 case 'B':
438                         if (got_prune_bind_mounts != false)
439                                 error(EXIT_FAILURE, 0,
440                                       _("--%s would override earlier command-line argument"),
441                                       "prune-bind-mounts");
442                         got_prune_bind_mounts = true;
443                         if (parse_bool(&conf_prune_bind_mounts, optarg) != 0)
444                                 error(EXIT_FAILURE, 0, _("invalid value `%s' of --%s"), optarg,
445                                       "prune-bind-mounts");
446                         break;
447
448                 case 'F':
449                         if (prunefs_changed != false)
450                                 error(EXIT_FAILURE, 0,
451                                       _("--%s would override earlier command-line argument"),
452                                       "prunefs");
453                         prunefs_changed = true;
454                         conf_prunefs.clear();
455                         var_add_values(&conf_prunefs, optarg);
456                         break;
457
458                 case 'N':
459                         if (prunenames_changed != false)
460                                 error(EXIT_FAILURE, 0,
461                                       _("--%s would override earlier command-line argument"),
462                                       "prunenames");
463                         prunenames_changed = true;
464                         conf_prunenames.clear();
465                         var_add_values(&conf_prunenames, optarg);
466                         break;
467
468                 case 'P':
469                         if (prunepaths_changed != false)
470                                 error(EXIT_FAILURE, 0,
471                                       _("--%s would override earlier command-line argument"),
472                                       "prunepaths");
473                         prunepaths_changed = true;
474                         conf_prunepaths.clear(),
475                                 var_add_values(&conf_prunepaths, optarg);
476                         break;
477
478                 case 'U':
479                         if (conf_scan_root != NULL)
480                                 error(EXIT_FAILURE, 0, _("--%s specified twice"),
481                                       "database-root");
482                         conf_scan_root = canonicalize_file_name(optarg);
483                         if (conf_scan_root == NULL)
484                                 error(EXIT_FAILURE, errno, _("invalid value `%s' of --%s"), optarg,
485                                       "database-root");
486                         break;
487
488                 case 'V':
489                         puts("updatedb (" PACKAGE_NAME ") " PACKAGE_VERSION);
490                         puts(_("Copyright (C) 2007 Red Hat, Inc. All rights reserved.\n"
491                                "This software is distributed under the GPL v.2.\n"
492                                "\n"
493                                "This program is provided with NO WARRANTY, to the extent "
494                                "permitted by law."));
495                         exit(EXIT_SUCCESS);
496
497                 case 'e':
498                         prunepaths_changed = true;
499                         var_add_values(&conf_prunepaths, optarg);
500                         break;
501
502                 case 'f':
503                         prunefs_changed = true;
504                         var_add_values(&conf_prunefs, optarg);
505                         break;
506
507                 case 'h':
508                         help();
509                         exit(EXIT_SUCCESS);
510
511                 case 'l':
512                         if (got_visibility != false)
513                                 error(EXIT_FAILURE, 0, _("--%s specified twice"),
514                                       "require-visibility");
515                         got_visibility = true;
516                         if (parse_bool(&conf_check_visibility, optarg) != 0)
517                                 error(EXIT_FAILURE, 0, _("invalid value `%s' of --%s"), optarg,
518                                       "require-visibility");
519                         break;
520
521                 case 'n':
522                         prunenames_changed = true;
523                         var_add_values(&conf_prunenames, optarg);
524                         break;
525
526                 case 'o':
527                         if (!conf_output.empty())
528                                 error(EXIT_FAILURE, 0, _("--%s specified twice"), "output");
529                         conf_output = optarg;
530                         break;
531
532                 case 'v':
533                         conf_verbose = true;
534                         break;
535
536                 case 'b':
537                         conf_block_size = atoi(optarg);
538                         break;
539
540                 case 'D':
541                         use_debug = true;
542                         break;
543
544                 case OPT_DEBUG_PRUNING:
545                         conf_debug_pruning = true;
546                         break;
547
548                 default:
549                         abort();
550                 }
551         }
552 options_done:
553         if (optind != argc)
554                 error(EXIT_FAILURE, 0, _("unexpected operand on command line"));
555         if (conf_scan_root == NULL) {
556                 static char root[] = "/";
557
558                 conf_scan_root = root;
559         }
560         if (conf_output.empty())
561                 conf_output = DBFILE;
562         if (conf_output[0] != '/')
563                 conf_output = prepend_cwd(conf_output);
564 }
565
566 /* Conversion of configuration for main code */
567
568 /* Store a string list to OBSTACK */
569 static void
570 gen_conf_block_string_list(string *obstack,
571                            const vector<string> *strings)
572 {
573         for (const string &str : *strings) {
574                 *obstack += str;
575                 *obstack += '\0';
576         }
577         *obstack += '\0';
578 }
579
580 /* Generate conf_block */
581 static void
582 gen_conf_block(void)
583 {
584         conf_block.clear();
585
586 #define CONST(S) conf_block.append(S, sizeof(S))
587         /* conf_check_visibility value is stored in the header */
588         CONST("prune_bind_mounts");
589         /* Add two NUL bytes after the value */
590         conf_block.append(conf_prune_bind_mounts != false ? "1\0" : "0\0", 3);
591         CONST("prunefs");
592         gen_conf_block_string_list(&conf_block, &conf_prunefs);
593         CONST("prunenames");
594         gen_conf_block_string_list(&conf_block, &conf_prunenames);
595         CONST("prunepaths");
596         gen_conf_block_string_list(&conf_block, &conf_prunepaths);
597         /* scan_root is contained directly in the header */
598         /* conf_output, conf_verbose are not relevant */
599 #undef CONST
600 }
601
602 /* Parse /etc/updatedb.conf and command-line arguments ARGC, ARGV.
603    Exit on error or --help, --version. */
604 void conf_prepare(int argc, char *argv[])
605 {
606         parse_updatedb_conf();
607         parse_arguments(argc, argv);
608         for (string &str : conf_prunefs) {
609                 /* Assuming filesystem names are ASCII-only */
610                 for (char &c : str)
611                         c = toupper(c);
612         }
613         /* Finish the variable only after converting filesystem names to upper case
614            to avoid keeping duplicates that originally differed in case and to sort
615            them correctly. */
616         var_finish(&conf_prunefs);
617         var_finish(&conf_prunenames);
618         var_finish(&conf_prunepaths);
619         gen_conf_block();
620         string_list_dir_path_sort(&conf_prunepaths);
621
622         if (conf_debug_pruning) {
623                 /* This is debuging output, don't mark anything for translation */
624                 fprintf(stderr, "conf_block:\n");
625                 for (char c : conf_block) {
626                         if (isascii((unsigned char)c) && isprint((unsigned char)c) && c != '\\')
627                                 putc(c, stderr);
628                         else {
629                                 fprintf(stderr, "\\%03o", (unsigned)(unsigned char)c);
630                                 if (c == 0)
631                                         putc('\n', stderr);
632                         }
633                 }
634                 fprintf(stderr, "\n-----------------------\n");
635         }
636 }