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