]> git.sesse.net Git - plocate/blob - updatedb.cpp
Clean up some legacy logic in filesystem_is_excluded().
[plocate] / updatedb.cpp
1 /* updatedb(8).
2
3 Copyright (C) 2005, 2007, 2008 Red Hat, Inc. All rights reserved.
4
5 This copyrighted material is made available to anyone wishing to use, modify,
6 copy, or redistribute it subject to the terms and conditions of the GNU General
7 Public License v.2.
8
9 This program is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11 PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with
14 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
15 Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
17 Author: Miloslav Trmac <mitr@redhat.com>
18
19
20 plocate modifications: Copyright (C) 2020 Steinar H. Gunderson.
21 plocate parts and modifications are licensed under the GPLv2 or, at your option,
22 any later version.
23  */
24
25 #include "bind-mount.h"
26 #include "complete_pread.h"
27 #include "conf.h"
28 #include "database-builder.h"
29 #include "db.h"
30 #include "dprintf.h"
31 #include "io_uring_engine.h"
32 #include "lib.h"
33
34 #include <algorithm>
35 #include <arpa/inet.h>
36 #include <assert.h>
37 #include <chrono>
38 #include <dirent.h>
39 #include <fcntl.h>
40 #include <getopt.h>
41 #include <grp.h>
42 #include <inttypes.h>
43 #include <iosfwd>
44 #include <math.h>
45 #include <memory>
46 #include <mntent.h>
47 #include <random>
48 #include <stdint.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <string>
53 #include <sys/resource.h>
54 #include <sys/stat.h>
55 #include <sys/time.h>
56 #include <sys/types.h>
57 #include <unistd.h>
58 #include <utility>
59 #include <vector>
60
61 using namespace std;
62 using namespace std::chrono;
63
64 /* Next conf_prunepaths entry */
65 static size_t conf_prunepaths_index; /* = 0; */
66
67 void usage()
68 {
69         printf(
70                 "Usage: updatedb PLOCATE_DB\n"
71                 "\n"
72                 "Generate plocate index from mlocate.db, typically /var/lib/mlocate/mlocate.db.\n"
73                 "Normally, the destination should be /var/lib/mlocate/plocate.db.\n"
74                 "\n"
75                 "  -b, --block-size SIZE  number of filenames to store in each block (default 32)\n"
76                 "  -p, --plaintext        input is a plaintext file, not an mlocate database\n"
77                 "      --help             print this help\n"
78                 "      --version          print version information\n");
79 }
80
81 void version()
82 {
83         printf("updatedb %s\n", PACKAGE_VERSION);
84         printf("Copyright (C) 2007 Red Hat, Inc. All rights reserved.\n");
85         printf("Copyright 2020 Steinar H. Gunderson\n");
86         printf("This software is distributed under the GPL v.2.\n");
87         printf("\n");
88         printf("This program is provided with NO WARRANTY, to the extent permitted by law.\n");
89 }
90
91 int opendir_noatime(int dirfd, const char *path)
92 {
93         static bool noatime_failed = false;
94
95         if (!noatime_failed) {
96 #ifdef O_NOATIME
97                 int fd = openat(dirfd, path, O_RDONLY | O_DIRECTORY | O_NOATIME);
98 #else
99                 int fd = openat(dirfd, path, O_RDONLY | O_DIRECTORY);
100 #endif
101                 if (fd != -1) {
102                         return fd;
103                 } else if (errno == EPERM) {
104                         /* EPERM is fairly O_NOATIME-specific; missing access rights cause
105                            EACCES. */
106                         noatime_failed = true;
107                         // Retry below.
108                 } else {
109                         return -1;
110                 }
111         }
112         return openat(dirfd, path, O_RDONLY | O_DIRECTORY);
113 }
114
115 bool time_is_current(const dir_time &t)
116 {
117         static dir_time cache{ 0, 0 };
118
119         /* This is more difficult than it should be because Linux uses a cheaper time
120            source for filesystem timestamps than for gettimeofday() and they can get
121            slightly out of sync, see
122            https://bugzilla.redhat.com/show_bug.cgi?id=244697 .  This affects even
123            nanosecond timestamps (and don't forget that tv_nsec existence doesn't
124            guarantee that the underlying filesystem has such resolution - it might be
125            microseconds or even coarser).
126
127            The worst case is probably FAT timestamps with 2-second resolution
128            (although using such a filesystem violates POSIX file times requirements).
129
130            So, to be on the safe side, require a >3.0 second difference (2 seconds to
131            make sure the FAT timestamp changed, 1 more to account for the Linux
132            timestamp races).  This large margin might make updatedb marginally more
133            expensive, but it only makes a difference if the directory was very
134            recently updated _and_ is will not be updated again until the next
135            updatedb run; this is not likely to happen for most directories. */
136
137         /* Cache gettimeofday () results to rule out obviously old time stamps;
138            CACHE contains the earliest time we reject as too current. */
139         if (t < cache) {
140                 return false;
141         }
142
143         struct timeval tv;
144         gettimeofday(&tv, nullptr);
145         cache.sec = tv.tv_sec - 3;
146         cache.nsec = tv.tv_usec * 1000;
147
148         return t >= cache;
149 }
150
151 struct entry {
152         string name;
153         bool is_directory;
154
155         // For directories only:
156         int fd = -1;
157         dir_time dt = unknown_dir_time;
158         dir_time db_modified = unknown_dir_time;
159         dev_t dev;
160 };
161
162 bool filesystem_is_excluded(const string &path)
163 {
164         if (conf_debug_pruning) {
165                 fprintf(stderr, "Checking whether filesystem `%s' is excluded:\n", path.c_str());
166         }
167         FILE *f = setmntent("/proc/mounts", "r");
168         if (f == nullptr) {
169                 return false;
170         }
171
172         struct mntent *me;
173         while ((me = getmntent(f)) != nullptr) {
174                 if (conf_debug_pruning) {
175                         fprintf(stderr, " `%s', type `%s'\n", me->mnt_dir, me->mnt_type);
176                 }
177                 if (path != me->mnt_dir) {
178                         continue;
179                 }
180                 string type(me->mnt_type);
181                 for (char &p : type) {
182                         p = toupper(p);
183                 }
184                 bool exclude = (find(conf_prunefs.begin(), conf_prunefs.end(), type) != conf_prunefs.end());
185                 if (exclude && conf_debug_pruning) {
186                         fprintf(stderr, " => excluded due to filesystem type\n");
187                 }
188                 endmntent(f);
189                 return exclude;
190         }
191         if (conf_debug_pruning) {
192                 fprintf(stderr, "...not found in mount list\n");
193         }
194         endmntent(f);
195         return false;
196 }
197
198 dir_time get_dirtime_from_stat(const struct stat &buf)
199 {
200         dir_time ctime{ buf.st_ctim.tv_sec, int32_t(buf.st_ctim.tv_nsec) };
201         dir_time mtime{ buf.st_mtim.tv_sec, int32_t(buf.st_mtim.tv_nsec) };
202         dir_time dt = max(ctime, mtime);
203
204         if (time_is_current(dt)) {
205                 /* The directory might be changing right now and we can't be sure the
206                    timestamp will be changed again if more changes happen very soon, mark
207                    the timestamp as invalid to force rescanning the directory next time
208                    updatedb is run. */
209                 return unknown_dir_time;
210         } else {
211                 return dt;
212         }
213 }
214
215 // Represents the old database we are updating.
216 class ExistingDB {
217 public:
218         explicit ExistingDB(int fd);
219         ~ExistingDB();
220
221         pair<string, dir_time> read_next();
222         void unread(pair<string, dir_time> record)
223         {
224                 unread_record = move(record);
225         }
226         string read_next_dictionary() const;
227         bool get_error() const { return error; }
228
229 private:
230         const int fd;
231         Header hdr;
232
233         uint32_t current_docid = 0;
234
235         string current_filename_block;
236         const char *current_filename_ptr = nullptr, *current_filename_end = nullptr;
237
238         off_t compressed_dir_time_pos;
239         string compressed_dir_time;
240         string current_dir_time_block;
241         const char *current_dir_time_ptr = nullptr, *current_dir_time_end = nullptr;
242
243         pair<string, dir_time> unread_record;
244
245         // Used in one-shot mode, repeatedly.
246         ZSTD_DCtx *ctx;
247
248         // Used in streaming mode.
249         ZSTD_DCtx *dir_time_ctx;
250
251         ZSTD_DDict *ddict = nullptr;
252
253         // If true, we've discovered an error or EOF, and will return only
254         // empty data from here.
255         bool eof = false, error = false;
256 };
257
258 ExistingDB::ExistingDB(int fd)
259         : fd(fd)
260 {
261         if (fd == -1) {
262                 error = true;
263                 return;
264         }
265
266         if (!try_complete_pread(fd, &hdr, sizeof(hdr), /*offset=*/0)) {
267                 if (conf_verbose) {
268                         perror("pread(header)");
269                 }
270                 error = true;
271                 return;
272         }
273         if (memcmp(hdr.magic, "\0plocate", 8) != 0) {
274                 if (conf_verbose) {
275                         fprintf(stderr, "Old database had header mismatch, ignoring.\n");
276                 }
277                 error = true;
278                 return;
279         }
280         if (hdr.version != 1 || hdr.max_version < 2) {
281                 if (conf_verbose) {
282                         fprintf(stderr, "Old database had version mismatch (version=%d max_version=%d), ignoring.\n",
283                                 hdr.version, hdr.max_version);
284                 }
285                 error = true;
286                 return;
287         }
288
289         // Compare the configuration block with our current one.
290         if (hdr.conf_block_length_bytes != conf_block.size()) {
291                 if (conf_verbose) {
292                         fprintf(stderr, "Old database had different configuration block (size mismatch), ignoring.\n");
293                 }
294                 error = true;
295                 return;
296         }
297         string str;
298         str.resize(hdr.conf_block_length_bytes);
299         if (!try_complete_pread(fd, str.data(), hdr.conf_block_length_bytes, hdr.conf_block_offset_bytes)) {
300                 if (conf_verbose) {
301                         perror("pread(conf_block)");
302                 }
303                 error = true;
304                 return;
305         }
306         if (str != conf_block) {
307                 if (conf_verbose) {
308                         fprintf(stderr, "Old database had different configuration block (contents mismatch), ignoring.\n");
309                 }
310                 error = true;
311                 return;
312         }
313
314         // Read dictionary, if it exists.
315         if (hdr.zstd_dictionary_length_bytes > 0) {
316                 string dictionary;
317                 dictionary.resize(hdr.zstd_dictionary_length_bytes);
318                 if (try_complete_pread(fd, &dictionary[0], hdr.zstd_dictionary_length_bytes, hdr.zstd_dictionary_offset_bytes)) {
319                         ddict = ZSTD_createDDict(dictionary.data(), dictionary.size());
320                 } else {
321                         if (conf_verbose) {
322                                 perror("pread(dictionary)");
323                         }
324                         error = true;
325                         return;
326                 }
327         }
328         compressed_dir_time_pos = hdr.directory_data_offset_bytes;
329
330         ctx = ZSTD_createDCtx();
331         dir_time_ctx = ZSTD_createDCtx();
332 }
333
334 ExistingDB::~ExistingDB()
335 {
336         if (fd != -1) {
337                 close(fd);
338         }
339 }
340
341 pair<string, dir_time> ExistingDB::read_next()
342 {
343         if (!unread_record.first.empty()) {
344                 auto ret = move(unread_record);
345                 unread_record.first.clear();
346                 return ret;
347         }
348
349         if (eof || error) {
350                 return { "", not_a_dir };
351         }
352
353         // See if we need to read a new filename block.
354         if (current_filename_ptr == nullptr) {
355                 if (current_docid >= hdr.num_docids) {
356                         eof = true;
357                         return { "", not_a_dir };
358                 }
359
360                 // Read the file offset from this docid and the next one.
361                 // This is always allowed, since we have a sentinel block at the end.
362                 off_t offset_for_block = hdr.filename_index_offset_bytes + current_docid * sizeof(uint64_t);
363                 uint64_t vals[2];
364                 if (!try_complete_pread(fd, vals, sizeof(vals), offset_for_block)) {
365                         if (conf_verbose) {
366                                 perror("pread(offset)");
367                         }
368                         error = true;
369                         return { "", not_a_dir };
370                 }
371
372                 off_t offset = vals[0];
373                 size_t compressed_len = vals[1] - vals[0];
374                 unique_ptr<char[]> compressed(new char[compressed_len]);
375                 if (!try_complete_pread(fd, compressed.get(), compressed_len, offset)) {
376                         if (conf_verbose) {
377                                 perror("pread(block)");
378                         }
379                         error = true;
380                         return { "", not_a_dir };
381                 }
382
383                 unsigned long long uncompressed_len = ZSTD_getFrameContentSize(compressed.get(), compressed_len);
384                 if (uncompressed_len == ZSTD_CONTENTSIZE_UNKNOWN || uncompressed_len == ZSTD_CONTENTSIZE_ERROR) {
385                         if (conf_verbose) {
386                                 fprintf(stderr, "ZSTD_getFrameContentSize() failed\n");
387                         }
388                         error = true;
389                         return { "", not_a_dir };
390                 }
391
392                 string block;
393                 block.resize(uncompressed_len + 1);
394
395                 size_t err;
396                 if (ddict != nullptr) {
397                         err = ZSTD_decompress_usingDDict(ctx, &block[0], block.size(), compressed.get(),
398                                                          compressed_len, ddict);
399                 } else {
400                         err = ZSTD_decompressDCtx(ctx, &block[0], block.size(), compressed.get(),
401                                                   compressed_len);
402                 }
403                 if (ZSTD_isError(err)) {
404                         if (conf_verbose) {
405                                 fprintf(stderr, "ZSTD_decompress(): %s\n", ZSTD_getErrorName(err));
406                         }
407                         error = true;
408                         return { "", not_a_dir };
409                 }
410                 block[block.size() - 1] = '\0';
411                 current_filename_block = move(block);
412                 current_filename_ptr = current_filename_block.data();
413                 current_filename_end = current_filename_block.data() + current_filename_block.size();
414                 ++current_docid;
415         }
416
417         // See if we need to read more directory time data.
418         while (current_dir_time_ptr == current_dir_time_end ||
419                (*current_dir_time_ptr != 0 &&
420                 size_t(current_dir_time_end - current_dir_time_ptr) < sizeof(dir_time) + 1)) {
421                 if (current_dir_time_ptr != nullptr) {
422                         const size_t bytes_consumed = current_dir_time_ptr - current_dir_time_block.data();
423                         current_dir_time_block.erase(current_dir_time_block.begin(), current_dir_time_block.begin() + bytes_consumed);
424                 }
425
426                 // See if we can get more data out without reading more.
427                 const size_t existing_data = current_dir_time_block.size();
428                 current_dir_time_block.resize(existing_data + 4096);
429
430                 ZSTD_outBuffer outbuf;
431                 outbuf.dst = current_dir_time_block.data() + existing_data;
432                 outbuf.size = 4096;
433                 outbuf.pos = 0;
434
435                 ZSTD_inBuffer inbuf;
436                 inbuf.src = compressed_dir_time.data();
437                 inbuf.size = compressed_dir_time.size();
438                 inbuf.pos = 0;
439
440                 int err = ZSTD_decompressStream(dir_time_ctx, &outbuf, &inbuf);
441                 if (err < 0) {
442                         if (conf_verbose) {
443                                 fprintf(stderr, "ZSTD_decompress(): %s\n", ZSTD_getErrorName(err));
444                         }
445                         error = true;
446                         return { "", not_a_dir };
447                 }
448                 compressed_dir_time.erase(compressed_dir_time.begin(), compressed_dir_time.begin() + inbuf.pos);
449                 current_dir_time_block.resize(existing_data + outbuf.pos);
450
451                 if (inbuf.pos == 0 && outbuf.pos == 0) {
452                         // No movement, we'll need to try to read more data.
453                         char buf[4096];
454                         size_t bytes_to_read = min<size_t>(
455                                 hdr.directory_data_offset_bytes + hdr.directory_data_length_bytes - compressed_dir_time_pos,
456                                 sizeof(buf));
457                         if (bytes_to_read == 0) {
458                                 error = true;
459                                 return { "", not_a_dir };
460                         }
461                         if (!try_complete_pread(fd, buf, bytes_to_read, compressed_dir_time_pos)) {
462                                 if (conf_verbose) {
463                                         perror("pread(dirtime)");
464                                 }
465                                 error = true;
466                                 return { "", not_a_dir };
467                         }
468                         compressed_dir_time_pos += bytes_to_read;
469                         compressed_dir_time.insert(compressed_dir_time.end(), buf, buf + bytes_to_read);
470
471                         // Next iteration will now try decompressing more.
472                 }
473
474                 current_dir_time_ptr = current_dir_time_block.data();
475                 current_dir_time_end = current_dir_time_block.data() + current_dir_time_block.size();
476         }
477
478         string filename = current_filename_ptr;
479         current_filename_ptr += filename.size() + 1;
480         if (current_filename_ptr == current_filename_end) {
481                 // End of this block.
482                 current_filename_ptr = nullptr;
483         }
484
485         if (*current_dir_time_ptr == 0) {
486                 ++current_dir_time_ptr;
487                 return { move(filename), not_a_dir };
488         } else {
489                 ++current_dir_time_ptr;
490                 dir_time dt;
491                 memcpy(&dt.sec, current_dir_time_ptr, sizeof(dt.sec));
492                 current_dir_time_ptr += sizeof(dt.sec);
493                 memcpy(&dt.nsec, current_dir_time_ptr, sizeof(dt.nsec));
494                 current_dir_time_ptr += sizeof(dt.nsec);
495                 return { move(filename), dt };
496         }
497 }
498
499 string ExistingDB::read_next_dictionary() const
500 {
501         if (hdr.next_zstd_dictionary_length_bytes == 0 || hdr.next_zstd_dictionary_length_bytes > 1048576) {
502                 return "";
503         }
504         string str;
505         str.resize(hdr.next_zstd_dictionary_length_bytes);
506         if (!try_complete_pread(fd, str.data(), hdr.next_zstd_dictionary_length_bytes, hdr.next_zstd_dictionary_offset_bytes)) {
507                 if (conf_verbose) {
508                         perror("pread(next_dictionary)");
509                 }
510                 return "";
511         }
512         return str;
513 }
514
515 // Scans the directory with absolute path “path”, which is opened as “fd”.
516 // Uses relative paths and openat() only, evading any issues with PATH_MAX
517 // and time-of-check-time-of-use race conditions. (mlocate's updatedb
518 // does a much more complicated dance with changing the current working
519 // directory, probably in the interest of portability to old platforms.)
520 // “parent_dev” must be the device of the parent directory of “path”.
521 //
522 // Takes ownership of fd.
523 int scan(const string &path, int fd, dev_t parent_dev, dir_time modified, dir_time db_modified, ExistingDB *existing_db, DatabaseReceiver *corpus, DictionaryBuilder *dict_builder)
524 {
525         if (conf_prune_bind_mounts && is_bind_mount(path.c_str())) {
526                 if (conf_debug_pruning) {
527                         fprintf(stderr, "Skipping `%s': bind mount\n", path.c_str());
528                 }
529                 close(fd);
530                 return 0;
531         }
532
533         // We read in the old directory no matter whether it is current or not,
534         // because even if we're not going to use it, we'll need the modification directory
535         // of any subdirectories.
536
537         // Skip over anything before this directory; it is stuff that we would have
538         // consumed earlier if we wanted it.
539         for (;;) {
540                 pair<string, dir_time> record = existing_db->read_next();
541                 if (record.first.empty()) {
542                         break;
543                 }
544                 if (dir_path_cmp(path, record.first) <= 0) {
545                         existing_db->unread(move(record));
546                         break;
547                 }
548         }
549
550         // Now read everything in this directory.
551         vector<entry> db_entries;
552         const string path_plus_slash = path.back() == '/' ? path : path + '/';
553         for (;;) {
554                 pair<string, dir_time> record = existing_db->read_next();
555                 if (record.first.empty()) {
556                         break;
557                 }
558
559                 if (record.first.rfind(path_plus_slash, 0) != 0) {
560                         // No longer starts with path, so we're in a different directory.
561                         existing_db->unread(move(record));
562                         break;
563                 }
564                 if (record.first.find_first_of('/', path_plus_slash.size()) != string::npos) {
565                         // Entered into a subdirectory of a subdirectory.
566                         // Due to our ordering, this also means we're done.
567                         existing_db->unread(move(record));
568                         break;
569                 }
570
571                 entry e;
572                 e.name = record.first.substr(path_plus_slash.size());
573                 e.is_directory = (record.second.sec >= 0);
574                 e.db_modified = record.second;
575                 db_entries.push_back(e);
576         }
577
578         DIR *dir = nullptr;
579         vector<entry> entries;
580         if (!existing_db->get_error() && db_modified.sec > 0 &&
581             modified.sec == db_modified.sec && modified.nsec == db_modified.nsec) {
582                 // Not changed since the last database, so we can replace the readdir()
583                 // by reading from the database. (We still need to open and stat everything,
584                 // though, but that happens in a later step.)
585                 entries = move(db_entries);
586                 if (conf_verbose) {
587                         for (const entry &e : entries) {
588                                 printf("%s/%s\n", path.c_str(), e.name.c_str());
589                         }
590                 }
591         } else {
592                 dir = fdopendir(fd);  // Takes over ownership of fd.
593                 if (dir == nullptr) {
594                         // fdopendir() wants to fstat() the fd to verify that it's indeed
595                         // a directory, which can seemingly fail on at least CIFS filesystems
596                         // if the server feels like it. We treat this as if we had an error
597                         // on opening it, ie., ignore the directory.
598                         close(fd);
599                         return 0;
600                 }
601
602                 dirent *de;
603                 while ((de = readdir(dir)) != nullptr) {
604                         if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
605                                 continue;
606                         }
607                         if (strlen(de->d_name) == 0) {
608                                 /* Unfortunately, this does happen, and mere assert() does not give
609                                    users enough information to complain to the right people. */
610                                 fprintf(stderr, "file system error: zero-length file name in directory %s", path.c_str());
611                                 continue;
612                         }
613
614                         entry e;
615                         e.name = de->d_name;
616                         if (de->d_type == DT_UNKNOWN) {
617                                 // Evidently some file systems, like older versions of XFS
618                                 // (mkfs.xfs -m crc=0 -n ftype=0), can return this,
619                                 // and we need a stat(). If we wanted to optimize for this,
620                                 // we could probably defer it to later (we're stat-ing directories
621                                 // when recursing), but this is rare, and not really worth it --
622                                 // the second stat() will be cached anyway.
623                                 struct stat buf;
624                                 if (fstatat(fd, de->d_name, &buf, AT_SYMLINK_NOFOLLOW) == 0 &&
625                                     S_ISDIR(buf.st_mode)) {
626                                         e.is_directory = true;
627                                 } else {
628                                         e.is_directory = false;
629                                 }
630                         } else {
631                                 e.is_directory = (de->d_type == DT_DIR);
632                         }
633
634                         if (conf_verbose) {
635                                 printf("%s/%s\n", path.c_str(), de->d_name);
636                         }
637                         entries.push_back(move(e));
638                 }
639
640                 sort(entries.begin(), entries.end(), [](const entry &a, const entry &b) {
641                         return a.name < b.name;
642                 });
643
644                 // Load directory modification times from the old database.
645                 auto db_it = db_entries.begin();
646                 for (entry &e : entries) {
647                         for (; db_it != db_entries.end(); ++db_it) {
648                                 if (e.name < db_it->name) {
649                                         break;
650                                 }
651                                 if (e.name == db_it->name) {
652                                         e.db_modified = db_it->db_modified;
653                                         break;
654                                 }
655                         }
656                 }
657         }
658
659         // For each entry, we want to add it to the database. but this includes the modification time
660         // for directories, which means we need to open and stat it at this point.
661         //
662         // This means we may need to have many directories open at the same time, but it seems to be
663         // the simplest (only?) way of being compatible with mlocate's notion of listing all contents
664         // of a given directory before recursing, without buffering even more information. Hopefully,
665         // we won't go out of file descriptors here (it could happen if someone has tens of thousands
666         // of subdirectories in a single directory); if so, the admin will need to raise the limit.
667         for (entry &e : entries) {
668                 if (!e.is_directory) {
669                         e.dt = not_a_dir;
670                         continue;
671                 }
672
673                 if (find(conf_prunenames.begin(), conf_prunenames.end(), e.name) != conf_prunenames.end()) {
674                         if (conf_debug_pruning) {
675                                 fprintf(stderr, "Skipping `%s': in prunenames\n", e.name.c_str());
676                         }
677                         continue;
678                 }
679                 if (string_list_contains_dir_path(&conf_prunepaths, &conf_prunepaths_index, (path_plus_slash + e.name).c_str())) {
680                         if (conf_debug_pruning) {
681                                 fprintf(stderr, "Skipping `%s/%s': in prunepaths\n", path.c_str(), e.name.c_str());
682                         }
683                         continue;
684                 }
685
686                 e.fd = opendir_noatime(fd, e.name.c_str());
687                 if (e.fd == -1) {
688                         if (errno == EMFILE || errno == ENFILE) {
689                                 // The admin probably wants to know about this.
690                                 perror((path_plus_slash + e.name).c_str());
691
692                                 rlimit rlim;
693                                 if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) {
694                                         fprintf(stderr, "Hint: Try `ulimit -n 131072' or similar.\n");
695                                 } else {
696                                         fprintf(stderr, "Hint: Try `ulimit -n %" PRIu64 " or similar (current limit is %" PRIu64 ").\n",
697                                                 static_cast<uint64_t>(rlim.rlim_cur * 2), static_cast<uint64_t>(rlim.rlim_cur));
698                                 }
699                                 exit(1);
700                         }
701                         continue;
702                 }
703
704                 struct stat buf;
705                 if (fstat(e.fd, &buf) != 0) {
706                         // It's possible that this is a filesystem that's excluded
707                         // (and the failure is e.g. because the network is down).
708                         // As a last-ditch effort, we try to check that before dying,
709                         // i.e., duplicate the check from further down.
710                         //
711                         // It would be better to be able to run filesystem_is_excluded()
712                         // for cheap on everything and just avoid the stat, but it seems
713                         // hard to do that without any kind of raciness.
714                         if (filesystem_is_excluded(path_plus_slash + e.name)) {
715                                 close(e.fd);
716                                 e.fd = -1;
717                                 continue;
718                         }
719
720                         perror((path_plus_slash + e.name).c_str());
721                         exit(1);
722                 }
723
724                 e.dev = buf.st_dev;
725                 if (buf.st_dev != parent_dev) {
726                         if (filesystem_is_excluded(path_plus_slash + e.name)) {
727                                 close(e.fd);
728                                 e.fd = -1;
729                                 continue;
730                         }
731                 }
732
733                 e.dt = get_dirtime_from_stat(buf);
734         }
735
736         // Actually add all the entries we figured out dates for above.
737         for (const entry &e : entries) {
738                 corpus->add_file(path_plus_slash + e.name, e.dt);
739                 dict_builder->add_file(path_plus_slash + e.name, e.dt);
740         }
741
742         // Now scan subdirectories.
743         for (const entry &e : entries) {
744                 if (e.is_directory && e.fd != -1) {
745                         int ret = scan(path_plus_slash + e.name, e.fd, e.dev, e.dt, e.db_modified, existing_db, corpus, dict_builder);
746                         if (ret == -1) {
747                                 // TODO: The unscanned file descriptors will leak, but it doesn't really matter,
748                                 // as we're about to exit.
749                                 closedir(dir);
750                                 return -1;
751                         }
752                 }
753         }
754
755         if (dir == nullptr) {
756                 close(fd);
757         } else {
758                 closedir(dir);
759         }
760         return 0;
761 }
762
763 int main(int argc, char **argv)
764 {
765         // We want to bump the file limit; do it if we can (usually we are root
766         // and can set whatever we want). 128k should be ample for most setups.
767         rlimit rlim;
768         if (getrlimit(RLIMIT_NOFILE, &rlim) != -1) {
769                 // Even root cannot increase rlim_cur beyond rlim_max,
770                 // so we need to try to increase rlim_max first.
771                 // Ignore errors, though.
772                 if (rlim.rlim_max < 131072) {
773                         rlim.rlim_max = 131072;
774                         setrlimit(RLIMIT_NOFILE, &rlim);
775                         getrlimit(RLIMIT_NOFILE, &rlim);
776                 }
777
778                 rlim_t wanted = std::max<rlim_t>(rlim.rlim_cur, 131072);
779                 rlim.rlim_cur = std::min<rlim_t>(wanted, rlim.rlim_max);
780                 setrlimit(RLIMIT_NOFILE, &rlim);  // Ignore errors.
781         }
782
783         conf_prepare(argc, argv);
784         if (conf_prune_bind_mounts) {
785                 bind_mount_init();
786         }
787
788         int fd = open(conf_output.c_str(), O_RDONLY);
789         ExistingDB existing_db(fd);
790
791         DictionaryBuilder dict_builder(/*blocks_to_keep=*/1000, conf_block_size);
792
793         gid_t owner = -1;
794         if (conf_check_visibility) {
795                 group *grp = getgrnam(GROUPNAME);
796                 if (grp == nullptr) {
797                         fprintf(stderr, "Unknown group %s\n", GROUPNAME);
798                         exit(1);
799                 }
800                 owner = grp->gr_gid;
801         }
802
803         DatabaseBuilder db(conf_output.c_str(), owner, conf_block_size, existing_db.read_next_dictionary(), conf_check_visibility);
804         db.set_conf_block(conf_block);
805         DatabaseReceiver *corpus = db.start_corpus(/*store_dir_times=*/true);
806
807         int root_fd = opendir_noatime(AT_FDCWD, conf_scan_root);
808         if (root_fd == -1) {
809                 perror(".");
810                 exit(1);
811         }
812
813         struct stat buf;
814         if (fstat(root_fd, &buf) == -1) {
815                 perror(".");
816                 exit(1);
817         }
818
819         scan(conf_scan_root, root_fd, buf.st_dev, get_dirtime_from_stat(buf), /*db_modified=*/unknown_dir_time, &existing_db, corpus, &dict_builder);
820
821         // It's too late to use the dictionary for the data we already compressed,
822         // unless we wanted to either scan the entire file system again (acceptable
823         // for plocate-build where it's cheap, less so for us), or uncompressing
824         // and recompressing. Instead, we store it for next time, assuming that the
825         // data changes fairly little from time to time.
826         string next_dictionary = dict_builder.train(1024);
827         db.set_next_dictionary(next_dictionary);
828         db.finish_corpus();
829
830         exit(EXIT_SUCCESS);
831 }