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