GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/nfs_maps_leveldb.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 0 191 0.0%
Branches: 0 220 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * The NFS maps module maintains inode -- path relations. An inode that is
5 * issued once by an NFS exported file system might be asked for
6 * any time later by clients.
7 *
8 * In "NFS mode", cvmfs will issue inodes consecutively and reuse inodes
9 * based on path name. The inode --> path and path --> inode maps are
10 * handled by leveldb. This workaround is comparable to the Fuse "noforget"
11 * option, except that the mappings are persistent and thus consistent during
12 * cvmfs restarts. Also, leveldb allows for restricting the memory consumption.
13 *
14 * The maps are not accounted for by the cache quota.
15 */
16
17 #include "nfs_maps_leveldb.h"
18
19 #include <unistd.h>
20
21 #include <cassert>
22 #include <cstddef>
23 #include <memory>
24
25 #include "leveldb/cache.h"
26 #include "leveldb/db.h"
27 #include "leveldb/filter_policy.h"
28 #include "statistics.h"
29 #include "util/concurrency.h"
30 #include "util/exception.h"
31 #include "util/logging.h"
32 #include "util/posix.h"
33 #include "util/smalloc.h"
34
35 using namespace std; // NOLINT
36
37
38 ✗ NfsMapsLeveldb::ForkAwareEnv::ForkAwareEnv(NfsMapsLeveldb *maps)
39 ✗ : leveldb::EnvWrapper(leveldb::Env::Default()), maps_(maps) {
40 ✗ atomic_init32(&num_bg_threads_);
41 }
42
43
44 ✗ void NfsMapsLeveldb::ForkAwareEnv::StartThread(void (*f)(void *), void *a) {
45 ✗ if (maps_->spawned_) {
46 ✗ leveldb::Env::Default()->StartThread(f, a);
47 ✗ return;
48 }
49 ✗ PANIC(kLogDebug | kLogSyslogErr,
50 "single threaded leveldb::StartThread called");
51 // Unclear how to handle this because caller assumes that thread is started
52 }
53
54
55 ✗ void NfsMapsLeveldb::ForkAwareEnv::Schedule(void (*function)(void *),
56 void *arg) {
57 ✗ if (maps_->spawned_) {
58 ✗ leveldb::Env::Default()->Schedule(function, arg);
59 ✗ return;
60 }
61 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "single threaded leveldb::Schedule called");
62 ✗ FuncArg *funcarg = new FuncArg();
63 ✗ funcarg->function = function;
64 ✗ funcarg->arg = arg;
65 ✗ funcarg->env = this;
66 ✗ atomic_inc32(&num_bg_threads_);
67 pthread_t bg_thread;
68 ✗ int retval = pthread_create(&bg_thread, NULL, MainFakeThread, funcarg);
69 ✗ assert(retval == 0);
70 ✗ retval = pthread_detach(bg_thread);
71 ✗ assert(retval == 0);
72 }
73
74
75 ✗ void NfsMapsLeveldb::ForkAwareEnv::WaitForBGThreads() {
76 ✗ while (atomic_read32(&num_bg_threads_) > 0)
77 ✗ SafeSleepMs(100);
78 }
79
80
81 /**
82 * Leveldb's usleep might collide with the ALARM timer
83 */
84 ✗ void NfsMapsLeveldb::ForkAwareEnv::SleepForMicroseconds(int micros) {
85 ✗ SafeSleepMs(micros / 1000);
86 }
87
88
89 ✗ void *NfsMapsLeveldb::ForkAwareEnv::MainFakeThread(void *data) {
90 ✗ FuncArg *funcarg = reinterpret_cast<FuncArg *>(data);
91 ✗ funcarg->function(funcarg->arg);
92 ✗ atomic_dec32(&(funcarg->env->num_bg_threads_));
93 ✗ delete funcarg;
94 ✗ return NULL;
95 }
96
97
98 //------------------------------------------------------------------------------
99
100
101 ✗ NfsMapsLeveldb *NfsMapsLeveldb::Create(const string &leveldb_dir,
102 const uint64_t root_inode,
103 const bool rebuild,
104 perf::Statistics *statistics) {
105 ✗ assert(root_inode > 0);
106 ✗ std::unique_ptr<NfsMapsLeveldb> maps(new NfsMapsLeveldb());
107 ✗ maps->n_db_added_ = statistics->Register("nfs.leveldb.n_added",
108 "total number of issued inode");
109
110 ✗ maps->root_inode_ = root_inode;
111 ✗ maps->fork_aware_env_ = new ForkAwareEnv(maps.get());
112 ✗ leveldb::Status status;
113 ✗ leveldb::Options leveldb_options;
114 ✗ leveldb_options.create_if_missing = true;
115 ✗ leveldb_options.env = maps->fork_aware_env_;
116
117 // Remove previous database traces
118 ✗ if (rebuild) {
119 ✗ LogCvmfs(kLogNfsMaps, kLogSyslogWarn,
120 "rebuilding NFS maps, might result in stale entries");
121 ✗ const bool retval = RemoveTree(leveldb_dir + "/inode2path")
122 ✗ && RemoveTree(leveldb_dir + "/path2inode");
123 ✗ if (!retval) {
124 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "failed to remove previous databases");
125 ✗ return NULL;
126 }
127 }
128
129 // Open databases
130 ✗ maps->cache_inode2path_ = leveldb::NewLRUCache(32 * 1024 * 1024);
131 ✗ leveldb_options.block_cache = maps->cache_inode2path_;
132 ✗ maps->filter_inode2path_ = leveldb::NewBloomFilterPolicy(10);
133 ✗ leveldb_options.filter_policy = maps->filter_inode2path_;
134 ✗ status = leveldb::DB::Open(leveldb_options, leveldb_dir + "/inode2path",
135 ✗ &maps->db_inode2path_);
136 ✗ if (!status.ok()) {
137 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "failed to create inode2path db: %s",
138 ✗ status.ToString().c_str());
139 ✗ return NULL;
140 }
141 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "inode2path opened");
142
143 // Hashes and inodes, no compression here
144 ✗ leveldb_options.compression = leveldb::kNoCompression;
145 // Random order, small block size to not trash caches
146 ✗ leveldb_options.block_size = 512;
147 ✗ maps->cache_path2inode_ = leveldb::NewLRUCache(8 * 1024 * 1024);
148 ✗ leveldb_options.block_cache = maps->cache_path2inode_;
149 ✗ maps->filter_path2inode_ = leveldb::NewBloomFilterPolicy(10);
150 ✗ leveldb_options.filter_policy = maps->filter_path2inode_;
151 ✗ status = leveldb::DB::Open(leveldb_options, leveldb_dir + "/path2inode",
152 ✗ &maps->db_path2inode_);
153 ✗ if (!status.ok()) {
154 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "failed to create path2inode db: %s",
155 ✗ status.ToString().c_str());
156 ✗ return NULL;
157 }
158 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "path2inode opened");
159
160 // Fetch highest issued inode
161 ✗ maps->seq_ = maps->FindInode(shash::Md5(shash::AsciiPtr("?seq")));
162 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "Sequence number is %" PRIu64, maps->seq_);
163 ✗ if (maps->seq_ == 0) {
164 ✗ maps->seq_ = maps->root_inode_;
165 // Insert root inode
166 ✗ const PathString root_path;
167 ✗ maps->GetInode(root_path);
168 }
169
170 ✗ maps->fork_aware_env_->WaitForBGThreads();
171
172 ✗ return maps.release();
173 }
174
175 ✗ void NfsMapsLeveldb::SetInodeResidue(unsigned residue_class,
176 unsigned remainder) {
177 ✗ const MutexLockGuard lock_guard(lock_);
178 ✗ if (residue_class < 2) {
179 ✗ inode_residue_class_ = 1;
180 ✗ inode_remainder_ = 0;
181 } else {
182 ✗ inode_residue_class_ = residue_class;
183 ✗ inode_remainder_ = remainder % residue_class;
184 ✗ seq_ = ((seq_ / inode_residue_class_) + 1) * inode_residue_class_
185 ✗ + inode_remainder_;
186 }
187 }
188
189
190 ✗ uint64_t NfsMapsLeveldb::FindInode(const shash::Md5 &path) {
191 ✗ leveldb::Status status;
192 ✗ const leveldb::Slice key(reinterpret_cast<const char *>(path.digest),
193 ✗ path.GetDigestSize());
194 ✗ string result;
195
196 ✗ status = db_path2inode_->Get(leveldb::ReadOptions(), key, &result);
197 ✗ if (!status.ok() && !status.IsNotFound()) {
198 ✗ PANIC(kLogSyslogErr, "failed to read from path2inode db (path %s): %s",
199 path.ToString().c_str(), status.ToString().c_str());
200 }
201
202 ✗ if (status.IsNotFound()) {
203 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "path %s not found",
204 ✗ path.ToString().c_str());
205 ✗ return 0;
206 } else {
207 ✗ const uint64_t *inode = reinterpret_cast<const uint64_t *>(result.data());
208 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "path %s maps to inode %" PRIu64,
209 ✗ path.ToString().c_str(), *inode);
210 ✗ return *inode;
211 }
212 }
213
214
215 ✗ uint64_t NfsMapsLeveldb::GetInode(const PathString &path) {
216 ✗ const shash::Md5 md5_path(path.GetChars(), path.GetLength());
217 ✗ uint64_t inode = FindInode(md5_path);
218 ✗ if (inode != 0)
219 ✗ return inode;
220
221 ✗ const MutexLockGuard m(lock_);
222 // Search again to avoid race
223 ✗ inode = FindInode(md5_path);
224 ✗ if (inode != 0) {
225 ✗ return inode;
226 }
227
228 // Issue new inode
229 ✗ inode = seq_;
230 ✗ seq_ += inode_residue_class_;
231 ✗ PutPath2Inode(md5_path, inode);
232 ✗ PutInode2Path(inode, path);
233 ✗ perf::Inc(n_db_added_);
234 ✗ return inode;
235 }
236
237
238 /**
239 * Finds the path that belongs to an inode. This must be successful. The
240 * inode input comes from the file system, i.e. it must have been issued
241 * before.
242 * \return false if not found
243 */
244 ✗ bool NfsMapsLeveldb::GetPath(const uint64_t inode, PathString *path) {
245 ✗ leveldb::Status status;
246 const leveldb::Slice key(reinterpret_cast<const char *>(&inode),
247 ✗ sizeof(inode));
248 ✗ string result;
249
250 ✗ status = db_inode2path_->Get(leveldb::ReadOptions(), key, &result);
251 ✗ if (status.IsNotFound()) {
252 ✗ LogCvmfs(kLogNfsMaps, kLogDebug,
253 "failed to find inode %" PRIu64 " in NFS maps, returning ESTALE",
254 inode);
255 ✗ return false;
256 }
257 ✗ if (!status.ok()) {
258 ✗ PANIC(kLogSyslogErr,
259 "failed to read from inode2path db inode %" PRIu64 ": %s", inode,
260 status.ToString().c_str());
261 }
262
263 ✗ path->Assign(result.data(), result.length());
264 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "inode %" PRIu64 " maps to path %s", inode,
265 path->c_str());
266 ✗ return true;
267 }
268
269
270 ✗ string NfsMapsLeveldb::GetStatistics() {
271 ✗ string stats;
272
273 ✗ db_inode2path_->GetProperty(leveldb::Slice("leveldb.stats"), &stats);
274 ✗ stats += "inode --> path database:\n" + stats + "\n";
275
276 ✗ db_path2inode_->GetProperty(leveldb::Slice("leveldb.stats"), &stats);
277 ✗ stats += "path --> inode database:\n" + stats + "\n";
278
279 ✗ return stats;
280 }
281
282
283 ✗ NfsMapsLeveldb::NfsMapsLeveldb()
284 ✗ : db_inode2path_(NULL)
285 ✗ , db_path2inode_(NULL)
286 ✗ , cache_inode2path_(NULL)
287 ✗ , cache_path2inode_(NULL)
288 ✗ , filter_inode2path_(NULL)
289 ✗ , filter_path2inode_(NULL)
290 ✗ , fork_aware_env_(NULL)
291 ✗ , root_inode_(0)
292 ✗ , seq_(0)
293 ✗ , lock_(NULL)
294 ✗ , spawned_(false)
295 ✗ , inode_residue_class_(1)
296 ✗ , inode_remainder_(0)
297 ✗ , n_db_added_(NULL) {
298 ✗ lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
299 ✗ const int retval = pthread_mutex_init(lock_, NULL);
300 ✗ assert(retval == 0);
301 }
302
303
304 ✗ NfsMapsLeveldb::~NfsMapsLeveldb() {
305 ✗ PutPath2Inode(shash::Md5(shash::AsciiPtr("?seq")), seq_);
306
307 ✗ delete db_path2inode_;
308 ✗ delete cache_path2inode_;
309 ✗ delete filter_path2inode_;
310 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "path2inode closed");
311 ✗ delete db_inode2path_;
312 ✗ delete cache_inode2path_;
313 ✗ delete filter_inode2path_;
314 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "inode2path closed");
315 ✗ delete fork_aware_env_;
316 ✗ pthread_mutex_destroy(lock_);
317 ✗ free(lock_);
318 }
319
320
321 ✗ void NfsMapsLeveldb::PutInode2Path(const uint64_t inode,
322 const PathString &path) {
323 ✗ leveldb::Status status;
324 const leveldb::Slice key(reinterpret_cast<const char *>(&inode),
325 ✗ sizeof(inode));
326 ✗ const leveldb::Slice value(path.GetChars(), path.GetLength());
327
328 ✗ status = db_inode2path_->Put(leveldb::WriteOptions(), key, value);
329 ✗ if (!status.ok()) {
330 ✗ PANIC(kLogSyslogErr,
331 "failed to write inode2path entry (%" PRIu64 " --> %s): %s", inode,
332 path.c_str(), status.ToString().c_str());
333 }
334 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "stored inode %" PRIu64 " --> path %s",
335 inode, path.c_str());
336 }
337
338
339 ✗ void NfsMapsLeveldb::PutPath2Inode(const shash::Md5 &path,
340 const uint64_t inode) {
341 ✗ leveldb::Status status;
342 ✗ const leveldb::Slice key(reinterpret_cast<const char *>(path.digest),
343 ✗ path.GetDigestSize());
344 const leveldb::Slice value(reinterpret_cast<const char *>(&inode),
345 ✗ sizeof(inode));
346
347 ✗ status = db_path2inode_->Put(leveldb::WriteOptions(), key, value);
348 ✗ if (!status.ok()) {
349 ✗ PANIC(kLogSyslogErr,
350 "failed to write path2inode entry (%s --> %" PRIu64 "): %s",
351 path.ToString().c_str(), inode, status.ToString().c_str());
352 }
353 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "stored path %s --> inode %" PRIu64,
354 ✗ path.ToString().c_str(), inode);
355 }
356