]> git.sesse.net Git - plocate/blob - updatedb.cpp
Release plocate 1.1.7.
[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                         perror("fdopendir");
614                         exit(1);
615                 }
616
617                 dirent *de;
618                 while ((de = readdir(dir)) != nullptr) {
619                         if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) {
620                                 continue;
621                         }
622                         if (strlen(de->d_name) == 0) {
623                                 /* Unfortunately, this does happen, and mere assert() does not give
624                                    users enough information to complain to the right people. */
625                                 fprintf(stderr, "file system error: zero-length file name in directory %s", path.c_str());
626                                 continue;
627                         }
628
629                         entry e;
630                         e.name = de->d_name;
631                         if (de->d_type == DT_UNKNOWN) {
632                                 // Evidently some file systems, like older versions of XFS
633                                 // (mkfs.xfs -m crc=0 -n ftype=0), can return this,
634                                 // and we need a stat(). If we wanted to optimize for this,
635                                 // we could probably defer it to later (we're stat-ing directories
636                                 // when recursing), but this is rare, and not really worth it --
637                                 // the second stat() will be cached anyway.
638                                 struct stat buf;
639                                 if (fstatat(fd, de->d_name, &buf, AT_SYMLINK_NOFOLLOW) == 0 &&
640                                     S_ISDIR(buf.st_mode)) {
641                                         e.is_directory = true;
642                                 } else {
643                                         e.is_directory = false;
644                                 }
645                         } else {
646                                 e.is_directory = (de->d_type == DT_DIR);
647                         }
648
649                         if (conf_verbose) {
650                                 printf("%s/%s\n", path.c_str(), de->d_name);
651                         }
652                         entries.push_back(move(e));
653                 }
654
655                 sort(entries.begin(), entries.end(), [](const entry &a, const entry &b) {
656                         return a.name < b.name;
657                 });
658
659                 // Load directory modification times from the old database.
660                 auto db_it = db_entries.begin();
661                 for (entry &e : entries) {
662                         for (; db_it != db_entries.end(); ++db_it) {
663                                 if (e.name < db_it->name) {
664                                         break;
665                                 }
666                                 if (e.name == db_it->name) {
667                                         e.db_modified = db_it->db_modified;
668                                         break;
669                                 }
670                         }
671                 }
672         }
673
674         // For each entry, we want to add it to the database. but this includes the modification time
675         // for directories, which means we need to open and stat it at this point.
676         //
677         // This means we may need to have many directories open at the same time, but it seems to be
678         // the simplest (only?) way of being compatible with mlocate's notion of listing all contents
679         // of a given directory before recursing, without buffering even more information. Hopefully,
680         // we won't go out of file descriptors here (it could happen if someone has tens of thousands
681         // of subdirectories in a single directory); if so, the admin will need to raise the limit.
682         for (entry &e : entries) {
683                 if (!e.is_directory) {
684                         e.dt = not_a_dir;
685                         continue;
686                 }
687
688                 if (find(conf_prunenames.begin(), conf_prunenames.end(), e.name) != conf_prunenames.end()) {
689                         if (conf_debug_pruning) {
690                                 /* This is debugging output, don't mark anything for translation */
691                                 fprintf(stderr, "Skipping `%s': in prunenames\n", e.name.c_str());
692                         }
693                         continue;
694                 }
695
696                 e.fd = opendir_noatime(fd, e.name.c_str());
697                 if (e.fd == -1) {
698                         if (errno == EMFILE || errno == ENFILE) {
699                                 // The admin probably wants to know about this.
700                                 perror((path_plus_slash + e.name).c_str());
701
702                                 rlimit rlim;
703                                 if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) {
704                                         fprintf(stderr, "Hint: Try `ulimit -n 131072' or similar.\n");
705                                 } else {
706                                         fprintf(stderr, "Hint: Try `ulimit -n %lu' or similar (current limit is %lu).\n",
707                                                 rlim.rlim_cur * 2, rlim.rlim_cur);
708                                 }
709                                 exit(1);
710                         }
711                         continue;
712                 }
713
714                 struct stat buf;
715                 if (fstat(e.fd, &buf) != 0) {
716                         perror(path.c_str());
717                         exit(1);
718                 }
719
720                 e.dev = buf.st_dev;
721                 if (buf.st_dev != parent_dev) {
722                         if (filesystem_is_excluded((path_plus_slash + e.name).c_str())) {
723                                 close(e.fd);
724                                 e.fd = -1;
725                                 continue;
726                         }
727                 }
728
729                 e.dt = get_dirtime_from_stat(buf);
730         }
731
732         // Actually add all the entries we figured out dates for above.
733         for (const entry &e : entries) {
734                 corpus->add_file(path_plus_slash + e.name, e.dt);
735                 dict_builder->add_file(path_plus_slash + e.name, e.dt);
736         }
737
738         // Now scan subdirectories.
739         for (const entry &e : entries) {
740                 if (e.is_directory && e.fd != -1) {
741                         int ret = scan(path_plus_slash + e.name, e.fd, e.dev, e.dt, e.db_modified, existing_db, corpus, dict_builder);
742                         if (ret == -1) {
743                                 // TODO: The unscanned file descriptors will leak, but it doesn't really matter,
744                                 // as we're about to exit.
745                                 closedir(dir);
746                                 return -1;
747                         }
748                 }
749         }
750
751         if (dir == nullptr) {
752                 close(fd);
753         } else {
754                 closedir(dir);
755         }
756         return 0;
757 }
758
759 int main(int argc, char **argv)
760 {
761         // We want to bump the file limit; do it if we can (usually we are root
762         // and can set whatever we want). 128k should be ample for most setups.
763         rlimit rlim;
764         if (getrlimit(RLIMIT_NOFILE, &rlim) != -1) {
765                 // Even root cannot increase rlim_cur beyond rlim_max,
766                 // so we need to try to increase rlim_max first.
767                 // Ignore errors, though.
768                 if (rlim.rlim_max < 131072) {
769                         rlim.rlim_max = 131072;
770                         setrlimit(RLIMIT_NOFILE, &rlim);
771                         getrlimit(RLIMIT_NOFILE, &rlim);
772                 }
773
774                 rlim_t wanted = std::max<rlim_t>(rlim.rlim_cur, 131072);
775                 rlim.rlim_cur = std::min<rlim_t>(wanted, rlim.rlim_max);
776                 setrlimit(RLIMIT_NOFILE, &rlim);  // Ignore errors.
777         }
778
779         conf_prepare(argc, argv);
780         if (conf_prune_bind_mounts) {
781                 bind_mount_init(MOUNTINFO_PATH);
782         }
783
784         int fd = open(conf_output.c_str(), O_RDONLY);
785         ExistingDB existing_db(fd);
786
787         DictionaryBuilder dict_builder(/*blocks_to_keep=*/1000, conf_block_size);
788
789         gid_t owner = -1;
790         if (conf_check_visibility) {
791                 group *grp = getgrnam(GROUPNAME);
792                 if (grp == nullptr) {
793                         fprintf(stderr, "Unknown group %s\n", GROUPNAME);
794                         exit(1);
795                 }
796                 owner = grp->gr_gid;
797         }
798
799         DatabaseBuilder db(conf_output.c_str(), owner, conf_block_size, existing_db.read_next_dictionary(), conf_check_visibility);
800         db.set_conf_block(conf_block);
801         DatabaseReceiver *corpus = db.start_corpus(/*store_dir_times=*/true);
802
803         int root_fd = opendir_noatime(AT_FDCWD, conf_scan_root);
804         if (root_fd == -1) {
805                 perror(".");
806                 exit(1);
807         }
808
809         struct stat buf;
810         if (fstat(root_fd, &buf) == -1) {
811                 perror(".");
812                 exit(1);
813         }
814
815         scan(conf_scan_root, root_fd, buf.st_dev, get_dirtime_from_stat(buf), /*db_modified=*/unknown_dir_time, &existing_db, corpus, &dict_builder);
816
817         // It's too late to use the dictionary for the data we already compressed,
818         // unless we wanted to either scan the entire file system again (acceptable
819         // for plocate-build where it's cheap, less so for us), or uncompressing
820         // and recompressing. Instead, we store it for next time, assuming that the
821         // data changes fairly little from time to time.
822         string next_dictionary = dict_builder.train(1024);
823         db.set_next_dictionary(next_dictionary);
824         db.finish_corpus();
825
826         exit(EXIT_SUCCESS);
827 }