]> git.sesse.net Git - plocate/blob - conf.cpp
Release plocate 1.1.15.
[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 (space-separated)\n"
344                  "  -n, --add-prunenames NAMES     omit also NAMES (space-separated)\n"
345                  "  -e, --add-prunepaths PATHS     omit also PATHS (space-separated)\n"
346                  "      --add-single-prunepath PATH  omit also PATH\n"
347                  "  -U, --database-root PATH       the subtree to store in "
348                  "database (default \"/\")\n"
349                  "  -h, --help                     print this help\n"
350                  "  -o, --output FILE              database to update (default\n"
351                  "                                 `%s')\n"
352                  "  -b, --block-size SIZE          number of filenames to store\n"
353                  "                                 in each block (default 32)\n"
354                  "      --prune-bind-mounts FLAG   omit bind mounts (default "
355                  "\"no\")\n"
356                  "      --prunefs FS               filesystems to omit from "
357                  "database\n"
358                  "      --prunenames NAMES         directory names to omit from "
359                  "database\n"
360                  "      --prunepaths PATHS         paths to omit from database\n"
361                  "  -l, --require-visibility FLAG  check visibility before "
362                  "reporting files\n"
363                  "                                 (default \"yes\")\n"
364                  "  -v, --verbose                  print paths of files as they "
365                  "are found\n"
366                  "  -V, --version                  print version information\n"
367                  "\n"
368                  "The configuration defaults to values read from\n"
369                  "`%s'.\n"),
370                DBFILE, UPDATEDB_CONF);
371         printf(_("\n"
372                  "Report bugs to %s.\n"),
373                PACKAGE_BUGREPORT);
374 }
375
376 /* Prepend current working directory to PATH;
377    return resulting path */
378 static string
379 prepend_cwd(const string &path)
380 {
381         const char *res;
382         string buf;
383         buf.resize(BUFSIZ); /* Not PATH_MAX because it is not defined on some platforms. */
384         do
385                 buf.resize(buf.size() * 1.5);
386         while ((res = getcwd(buf.data(), buf.size())) == NULL && errno == ERANGE);
387         if (res == NULL)
388                 error(EXIT_FAILURE, errno, _("can not get current working directory"));
389         buf.resize(strlen(buf.data()));
390         return buf + '/' + path;
391 }
392
393 /* Parse ARGC, ARGV.  Exit on error or --help, --version. */
394 static void
395 parse_arguments(int argc, char *argv[])
396 {
397         enum { OPT_DEBUG_PRUNING = CHAR_MAX + 1,
398                OPT_ADD_SINGLE_PRUNEPATH = CHAR_MAX + 2 };
399
400         static const struct option options[] = {
401                 { "add-prunefs", required_argument, NULL, 'f' },
402                 { "add-prunenames", required_argument, NULL, 'n' },
403                 { "add-prunepaths", required_argument, NULL, 'e' },
404                 { "add-single-prunepath", required_argument, NULL, OPT_ADD_SINGLE_PRUNEPATH },
405                 { "database-root", required_argument, NULL, 'U' },
406                 { "debug-pruning", no_argument, NULL, OPT_DEBUG_PRUNING },
407                 { "help", no_argument, NULL, 'h' },
408                 { "output", required_argument, NULL, 'o' },
409                 { "prune-bind-mounts", required_argument, NULL, 'B' },
410                 { "prunefs", required_argument, NULL, 'F' },
411                 { "prunenames", required_argument, NULL, 'N' },
412                 { "prunepaths", required_argument, NULL, 'P' },
413                 { "require-visibility", required_argument, NULL, 'l' },
414                 { "verbose", no_argument, NULL, 'v' },
415                 { "version", no_argument, NULL, 'V' },
416                 { "block-size", required_argument, 0, 'b' },
417                 { "debug", no_argument, 0, 'D' },  // Not documented.
418                 { NULL, 0, NULL, 0 }
419         };
420
421         bool prunefs_changed, prunenames_changed, prunepaths_changed;
422         bool got_prune_bind_mounts, got_visibility;
423
424         prunefs_changed = false;
425         prunenames_changed = false;
426         prunepaths_changed = false;
427         got_prune_bind_mounts = false;
428         got_visibility = false;
429         for (;;) {
430                 int opt, idx;
431
432                 opt = getopt_long(argc, argv, "U:Ve:f:hl:n:o:vb:D", options, &idx);
433                 switch (opt) {
434                 case -1:
435                         goto options_done;
436
437                 case '?':
438                         exit(EXIT_FAILURE);
439
440                 case 'B':
441                         if (got_prune_bind_mounts != false)
442                                 error(EXIT_FAILURE, 0,
443                                       _("--%s would override earlier command-line argument"),
444                                       "prune-bind-mounts");
445                         got_prune_bind_mounts = true;
446                         if (parse_bool(&conf_prune_bind_mounts, optarg) != 0)
447                                 error(EXIT_FAILURE, 0, _("invalid value `%s' of --%s"), optarg,
448                                       "prune-bind-mounts");
449                         break;
450
451                 case 'F':
452                         if (prunefs_changed != false)
453                                 error(EXIT_FAILURE, 0,
454                                       _("--%s would override earlier command-line argument"),
455                                       "prunefs");
456                         prunefs_changed = true;
457                         conf_prunefs.clear();
458                         var_add_values(&conf_prunefs, optarg);
459                         break;
460
461                 case 'N':
462                         if (prunenames_changed != false)
463                                 error(EXIT_FAILURE, 0,
464                                       _("--%s would override earlier command-line argument"),
465                                       "prunenames");
466                         prunenames_changed = true;
467                         conf_prunenames.clear();
468                         var_add_values(&conf_prunenames, optarg);
469                         break;
470
471                 case 'P':
472                         if (prunepaths_changed != false)
473                                 error(EXIT_FAILURE, 0,
474                                       _("--%s would override earlier command-line argument"),
475                                       "prunepaths");
476                         prunepaths_changed = true;
477                         conf_prunepaths.clear();
478                         var_add_values(&conf_prunepaths, optarg);
479                         break;
480
481                 case 'U':
482                         if (conf_scan_root != NULL)
483                                 error(EXIT_FAILURE, 0, _("--%s specified twice"),
484                                       "database-root");
485                         conf_scan_root = realpath(optarg, nullptr);
486                         if (conf_scan_root == NULL)
487                                 error(EXIT_FAILURE, errno, _("invalid value `%s' of --%s"), optarg,
488                                       "database-root");
489                         break;
490
491                 case 'V':
492                         puts("updatedb (" PACKAGE_NAME ") " PACKAGE_VERSION);
493                         puts(_("Copyright (C) 2007 Red Hat, Inc. All rights reserved.\n"
494                                "This software is distributed under the GPL v.2.\n"
495                                "\n"
496                                "This program is provided with NO WARRANTY, to the extent "
497                                "permitted by law."));
498                         exit(EXIT_SUCCESS);
499
500                 case 'e':
501                         prunepaths_changed = true;
502                         var_add_values(&conf_prunepaths, optarg);
503                         break;
504
505                 case OPT_ADD_SINGLE_PRUNEPATH:
506                         prunepaths_changed = true;
507                         conf_prunepaths.push_back(optarg);
508                         break;
509
510                 case 'f':
511                         prunefs_changed = true;
512                         var_add_values(&conf_prunefs, optarg);
513                         break;
514
515                 case 'h':
516                         help();
517                         exit(EXIT_SUCCESS);
518
519                 case 'l':
520                         if (got_visibility != false)
521                                 error(EXIT_FAILURE, 0, _("--%s specified twice"),
522                                       "require-visibility");
523                         got_visibility = true;
524                         if (parse_bool(&conf_check_visibility, optarg) != 0)
525                                 error(EXIT_FAILURE, 0, _("invalid value `%s' of --%s"), optarg,
526                                       "require-visibility");
527                         break;
528
529                 case 'n':
530                         prunenames_changed = true;
531                         var_add_values(&conf_prunenames, optarg);
532                         break;
533
534                 case 'o':
535                         if (!conf_output.empty())
536                                 error(EXIT_FAILURE, 0, _("--%s specified twice"), "output");
537                         conf_output = optarg;
538                         break;
539
540                 case 'v':
541                         conf_verbose = true;
542                         break;
543
544                 case 'b':
545                         conf_block_size = atoi(optarg);
546                         break;
547
548                 case 'D':
549                         use_debug = true;
550                         break;
551
552                 case OPT_DEBUG_PRUNING:
553                         conf_debug_pruning = true;
554                         break;
555
556                 default:
557                         abort();
558                 }
559         }
560 options_done:
561         if (optind != argc)
562                 error(EXIT_FAILURE, 0, _("unexpected operand on command line"));
563         if (conf_scan_root == NULL) {
564                 static char root[] = "/";
565
566                 conf_scan_root = root;
567         }
568         if (conf_output.empty())
569                 conf_output = DBFILE;
570         if (conf_output[0] != '/')
571                 conf_output = prepend_cwd(conf_output);
572 }
573
574 /* Conversion of configuration for main code */
575
576 /* Store a string list to OBSTACK */
577 static void
578 gen_conf_block_string_list(string *obstack,
579                            const vector<string> *strings)
580 {
581         for (const string &str : *strings) {
582                 *obstack += str;
583                 *obstack += '\0';
584         }
585         *obstack += '\0';
586 }
587
588 /* Generate conf_block */
589 static void
590 gen_conf_block(void)
591 {
592         conf_block.clear();
593
594 #define CONST(S) conf_block.append(S, sizeof(S))
595         /* conf_check_visibility value is stored in the header */
596         CONST("prune_bind_mounts");
597         /* Add two NUL bytes after the value */
598         conf_block.append(conf_prune_bind_mounts != false ? "1\0" : "0\0", 3);
599         CONST("prunefs");
600         gen_conf_block_string_list(&conf_block, &conf_prunefs);
601         CONST("prunenames");
602         gen_conf_block_string_list(&conf_block, &conf_prunenames);
603         CONST("prunepaths");
604         gen_conf_block_string_list(&conf_block, &conf_prunepaths);
605         /* scan_root is contained directly in the header */
606         /* conf_output, conf_verbose are not relevant */
607 #undef CONST
608 }
609
610 /* Parse /etc/updatedb.conf and command-line arguments ARGC, ARGV.
611    Exit on error or --help, --version. */
612 void conf_prepare(int argc, char *argv[])
613 {
614         parse_updatedb_conf();
615         parse_arguments(argc, argv);
616         for (string &str : conf_prunefs) {
617                 /* Assuming filesystem names are ASCII-only */
618                 for (char &c : str)
619                         c = toupper(c);
620         }
621         /* Finish the variable only after converting filesystem names to upper case
622            to avoid keeping duplicates that originally differed in case and to sort
623            them correctly. */
624         var_finish(&conf_prunefs);
625         var_finish(&conf_prunenames);
626         var_finish(&conf_prunepaths);
627         gen_conf_block();
628         string_list_dir_path_sort(&conf_prunepaths);
629
630         if (conf_debug_pruning) {
631                 /* This is debuging output, don't mark anything for translation */
632                 fprintf(stderr, "conf_block:\n");
633                 for (char c : conf_block) {
634                         if (isascii((unsigned char)c) && isprint((unsigned char)c) && c != '\\')
635                                 putc(c, stderr);
636                         else {
637                                 fprintf(stderr, "\\%03o", (unsigned)(unsigned char)c);
638                                 if (c == 0)
639                                         putc('\n', stderr);
640                         }
641                 }
642                 fprintf(stderr, "\n-----------------------\n");
643         }
644 }