]> git.sesse.net Git - plocate/blob - plocate.cpp
Initial checkin.
[plocate] / plocate.cpp
1 #include <stdio.h>
2 #include <string.h>
3 #include <algorithm>
4 #include <unordered_map>
5 #include <string>
6 #include <vector>
7 #include <chrono>
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <sys/mman.h>
11 #include <arpa/inet.h>
12 #include <endian.h>
13
14 #include "vp4.h"
15
16 #define P4NENC_BOUND(n) ((n+127)/128+(n+32)*sizeof(uint32_t))
17
18 using namespace std;
19 using namespace std::chrono;
20
21 #define dprintf(...)
22 //#define dprintf(...) fprintf(stderr, __VA_ARGS__);
23         
24 static inline uint32_t read_unigram(const string &s, size_t idx)
25 {
26         if (idx < s.size()) {
27                 return (unsigned char)s[idx];
28         } else {
29                 return 0;
30         }
31 }
32
33 static inline uint32_t read_trigram(const string &s, size_t start)
34 {
35         return read_unigram(s, start) |
36                 (read_unigram(s, start + 1) << 8) |
37                 (read_unigram(s, start + 2) << 16);
38 }
39
40 bool has_access(const char *filename, unordered_map<string, bool> *access_rx_cache)
41 {
42         const char *end = strchr(filename + 1, '/');
43         while (end != nullptr) {
44                 string parent_path(filename, end);
45                 auto it = access_rx_cache->find(parent_path);
46                 bool ok;
47                 if (it == access_rx_cache->end()) {
48                         ok = access(parent_path.c_str(), R_OK | X_OK) == 0;
49                         access_rx_cache->emplace(move(parent_path), ok);
50                 } else {
51                         ok = it->second;
52                 }
53                 if (!ok) {
54                         return false;
55                 }
56                 end = strchr(end + 1, '/');
57         }
58
59 #if 0
60         // Check for rx first in the cache; if that isn't true, check R_OK uncached.
61         // This is roughly the same thing as mlocate does.      
62         auto it = access_rx_cache->find(filename);
63         if (it != access_rx_cache->end() && it->second) {
64                 return true;
65         }
66
67         return access(filename, R_OK) == 0;
68 #endif
69         return true;
70 }
71
72 struct Trigram {
73         uint32_t trgm;
74         uint32_t num_docids;
75         uint64_t offset;
76 };
77
78 void do_search_file(const string &needle, const char *filename)
79 {
80         int fd = open(filename, O_RDONLY);
81         if (fd == -1) {
82                 perror(filename);
83                 exit(1);
84         }
85
86         // Drop privileges.
87         if (setgid(getgid()) != 0) {
88                 perror("setgid");
89                 exit(EXIT_FAILURE);
90         }
91
92         //steady_clock::time_point start = steady_clock::now();
93         if (access("/", R_OK | X_OK)) {
94                 // We can't find anything, no need to bother...
95                 return;
96         }
97
98         off_t len = lseek(fd, 0, SEEK_END);
99         if (len == -1) {
100                 perror("lseek");
101                 exit(1);
102         }
103         const char *data = (char *)mmap(nullptr, len, PROT_READ, MAP_SHARED, fd, /*offset=*/0);
104         if (data == MAP_FAILED) {
105                 perror("mmap");
106                 exit(1);
107         }
108         uint64_t num_trigrams = *(const uint64_t *)data;
109         uint64_t filename_index_offset = *(const uint64_t *)(data + sizeof(uint64_t));
110
111         const Trigram *trgm_begin = (Trigram *)(data + sizeof(uint64_t) * 2);
112         const Trigram *trgm_end = trgm_begin + num_trigrams;
113
114         vector<const Trigram *> trigrams;
115         for (size_t i = 0; i < needle.size() - 2; ++i) {
116                 uint32_t trgm = read_trigram(needle, i);
117                 const Trigram *trgmptr = lower_bound(trgm_begin, trgm_end, trgm, [](const Trigram &trgm, uint32_t t) {
118                         return trgm.trgm < t;
119                 });
120                 if (trgmptr == trgm_end || trgmptr->trgm != trgm) {
121                         dprintf("trigram %06x isn't found, we abort the search\n", trgm);
122                         munmap((void *)data, len);
123                         close(fd);
124                         return;
125                 }
126                 trigrams.push_back(trgmptr);
127         }
128         sort(trigrams.begin(), trigrams.end());
129         {
130                 auto last = unique(trigrams.begin(), trigrams.end());
131                 trigrams.erase(last, trigrams.end());
132         }
133         sort(trigrams.begin(), trigrams.end(), [&](const Trigram *a, const Trigram *b) {
134                 return a->num_docids < b->num_docids;
135         });
136
137         vector<uint32_t> in1, in2, out;
138         for (const Trigram *trgmptr : trigrams) {
139                 //uint32_t trgm = trgmptr->trgm;
140                 size_t num = trgmptr->num_docids;
141                 unsigned char *pldata = (unsigned char *)(data + trgmptr->offset);
142                 if (in1.empty()) {
143                         in1.resize(num + 128);
144                         p4nd1dec128v32((unsigned char *)pldata, num, &in1[0]);
145                         in1.resize(num);
146                         dprintf("trigram '%c%c%c' decoded to %zu entries\n", trgm & 0xff, (trgm >> 8) & 0xff, (trgm >> 16) & 0xff, num);
147                 } else {
148                         if (num > in1.size() * 100) {
149                                 dprintf("trigram '%c%c%c' has %zu entries, ignoring the rest (will weed out false positives later)\n",
150                                         trgm & 0xff, (trgm >> 8) & 0xff, (trgm >> 16) & 0xff, num);
151                                 break;
152                         }
153
154                         if (in2.size() < num + 128) {
155                                 in2.resize(num + 128);
156                         }
157                         p4nd1dec128v32((unsigned char *)pldata, num, &in2[0]);
158
159                         out.clear();
160                         set_intersection(in1.begin(), in1.end(), in2.begin(), in2.begin() + num, back_inserter(out));
161                         swap(in1, out);
162                         dprintf("trigram '%c%c%c' decoded to %zu entries, %zu left\n", trgm & 0xff, (trgm >> 8) & 0xff, (trgm >> 16) & 0xff, num, in1.size());
163                         if (in1.empty()) {
164                                 dprintf("no matches (intersection list is empty)\n");
165                                 break;
166                         }
167                 }
168         }
169         steady_clock::time_point end = steady_clock::now();
170
171         dprintf("Intersection took %.1f ms. Doing final verification and printing:\n",
172                 1e3 * duration<float>(end - start).count());
173
174         unordered_map<string, bool> access_rx_cache;
175
176         const uint64_t *filename_offsets = (const uint64_t *)(data + filename_index_offset);
177         int matched = 0;
178         for (uint32_t docid : in1) {
179                 const char *filename = (const char *)(data + filename_offsets[docid]);
180                 if (strstr(filename, needle.c_str()) == nullptr) {
181                         continue;
182                 }
183                 if (has_access(filename, &access_rx_cache)) {
184                         ++matched;
185                         printf("%s\n", filename);
186                 }
187         }
188         end = steady_clock::now();
189         dprintf("Done in %.1f ms, found %d matches.\n",
190                 1e3 * duration<float>(end - start).count(), matched);
191
192         munmap((void *)data, len);
193         close(fd);
194 }
195
196 int main(int argc, char **argv)
197 {
198         //do_search_file(argv[1], "all.trgm");
199         do_search_file(argv[1], "/var/lib/mlocate/plocate.db");
200 }