]> git.sesse.net Git - plocate/blob - access_rx_cache.cpp
Release plocate 1.1.22.
[plocate] / access_rx_cache.cpp
1 #include "access_rx_cache.h"
2
3 #include "io_uring_engine.h"
4
5 #include <stdlib.h>
6 #include <string.h>
7 #include <unistd.h>
8 #include <utility>
9
10 using namespace std;
11
12 void AccessRXCache::check_access(const char *filename, bool allow_async, function<void(bool)> cb)
13 {
14         if (!check_visibility) {
15                 cb(true);
16                 return;
17         }
18
19         lock_guard<mutex> lock(mu);
20         if (engine == nullptr || !engine->get_supports_stat()) {
21                 allow_async = false;
22         }
23
24         for (const char *end = strchr(filename + 1, '/'); end != nullptr; end = strchr(end + 1, '/')) {
25                 string parent_path(filename, end - filename);  // string_view from C++20.
26                 auto cache_it = cache.find(parent_path);
27                 if (cache_it != cache.end()) {
28                         // Found in the cache.
29                         if (!cache_it->second) {
30                                 cb(false);
31                                 return;
32                         }
33                         continue;
34                 }
35
36                 if (!allow_async) {
37                         bool ok = access(parent_path.c_str(), R_OK | X_OK) == 0;
38                         cache.emplace(parent_path, ok);
39                         if (!ok) {
40                                 cb(false);
41                                 return;
42                         }
43                         continue;
44                 }
45
46                 // We want to call access(), but it could block on I/O. io_uring doesn't support
47                 // access(), but we can do a dummy asynchonous statx() to populate the kernel's cache,
48                 // which nearly always makes the next access() instantaneous.
49
50                 // See if there's already a pending stat that matches this,
51                 // or is a subdirectory.
52                 auto it = pending_stats.lower_bound(parent_path);
53                 if (it != pending_stats.end() && it->first.size() >= parent_path.size() &&
54                     it->first.compare(0, parent_path.size(), parent_path) == 0) {
55                         it->second.emplace_back(PendingStat{ filename, move(cb) });
56                 } else {
57                         it = pending_stats.emplace(filename, vector<PendingStat>{}).first;
58                         engine->submit_stat(filename, [this, it, filename{ strdup(filename) }, cb{ move(cb) }] {
59                                 // The stat returned, so now do the actual access() calls.
60                                 // All of them should be in cache, so don't fire off new statx()
61                                 // calls during that check.
62                                 check_access(filename, /*allow_async=*/false, move(cb));
63                                 free(filename);
64
65                                 // Call all others that waited for the same stat() to finish.
66                                 // They may fire off new stat() calls if needed.
67                                 vector<PendingStat> pending = move(it->second);
68                                 pending_stats.erase(it);
69                                 for (PendingStat &ps : pending) {
70                                         check_access(ps.filename.c_str(), /*allow_async=*/true, move(ps.cb));
71                                 }
72                         });
73                 }
74                 return;  // The rest will happen in async context.
75         }
76
77         // Passed all checks.
78         cb(true);
79 }