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