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