GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/nfs_maps_sqlite.cc
Date: 2026-10-04 02:40:33
Exec Total Coverage
Lines: 0 153 0.0%
Branches: 0 170 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 sqlite. This workaround is comparable to the Fuse "noforget"
11 * option, except that the mappings are persistent and thus consistent during
12 * cvmfs restarts. Also, sqlite allows for restricting the memory consumption.
13 *
14 * The maps are not accounted for by the cache quota.
15 */
16
17 #include "nfs_maps_sqlite.h"
18
19 #include <unistd.h>
20
21 #include <cassert>
22 #include <cstddef>
23 #include <cstdlib>
24 #include <memory>
25
26 #include "statistics.h"
27 #include "util/concurrency.h"
28 #include "util/exception.h"
29 #include "util/logging.h"
30 #include "util/posix.h"
31 #include "util/prng.h"
32 #include "util/smalloc.h"
33
34 using namespace std; // NOLINT
35
36
37 const char *NfsMapsSqlite::kSqlCreateTable = "CREATE TABLE IF NOT EXISTS "
38 "inodes (path TEXT PRIMARY KEY);";
39 const char *NfsMapsSqlite::
40 kSqlAddRoot = "INSERT INTO inodes (oid, path) VALUES (?, ?);";
41 const char *NfsMapsSqlite::kSqlAddInode = "INSERT INTO inodes VALUES (?);";
42 const char
43 *NfsMapsSqlite::kSqlGetInode = "SELECT rowid FROM inodes where path = ?;";
44 const char
45 *NfsMapsSqlite::kSqlGetPath = "SELECT path FROM inodes where rowid = ?;";
46
47
48 ✗ int NfsMapsSqlite::BusyHandler(void *data, int attempt) {
49 ✗ BusyHandlerInfo *handler_info = static_cast<BusyHandlerInfo *>(data);
50 // Reset the accumulated time if this is the start of a new request
51 ✗ if (attempt == 0)
52 ✗ handler_info->accumulated_ms = 0;
53 ✗ LogCvmfs(kLogNfsMaps, kLogDebug,
54 "busy handler, attempt %d, accumulated waiting time %u", attempt,
55 handler_info->accumulated_ms);
56 ✗ if (handler_info->accumulated_ms >= handler_info->kMaxWaitMs)
57 ✗ return 0;
58
59 ✗ const unsigned backoff_range_ms = 1 << attempt;
60 ✗ unsigned backoff_ms = handler_info->prng.Next(backoff_range_ms);
61 ✗ if (handler_info->accumulated_ms + backoff_ms > handler_info->kMaxWaitMs) {
62 ✗ backoff_ms = handler_info->kMaxWaitMs - handler_info->accumulated_ms;
63 }
64 ✗ if (backoff_ms > handler_info->kMaxBackoffMs) {
65 ✗ backoff_ms = handler_info->kMaxBackoffMs;
66 }
67
68 ✗ SafeSleepMs(backoff_ms);
69 ✗ handler_info->accumulated_ms += backoff_ms;
70 ✗ return 1;
71 }
72
73
74 ✗ NfsMapsSqlite *NfsMapsSqlite::Create(const string &db_dir,
75 const uint64_t root_inode,
76 const bool rebuild,
77 perf::Statistics *statistics) {
78 ✗ assert(root_inode > 0);
79 ✗ std::unique_ptr<NfsMapsSqlite> maps(new NfsMapsSqlite());
80 ✗ maps->n_db_added_ = statistics->Register("nfs.sqlite.n_added",
81 "total number of issued inode");
82 ✗ maps->n_db_seq_ = statistics->Register("nfs.sqlite.n_seq",
83 "last inode issued");
84 ✗ maps->n_db_path_found_ = statistics->Register("nfs.sqlite.n_path_hit",
85 "inode --> path hits");
86 ✗ maps->n_db_inode_found_ = statistics->Register("nfs.sqlite.n_inode_hit",
87 "path --> inode hits");
88
89 ✗ const string db_path = db_dir + "/inode_maps.db";
90
91 sqlite3_stmt *stmt;
92 ✗ if (rebuild) {
93 ✗ LogCvmfs(kLogNfsMaps, kLogDebug | kLogSyslogWarn,
94 "Ignoring rebuild flag as this may crash other cluster nodes.");
95 }
96 // We don't want the shared cache, we want minimal caching so sync is kept
97 ✗ int retval = sqlite3_enable_shared_cache(0);
98 ✗ assert(retval == SQLITE_OK);
99
100 ✗ retval = sqlite3_open_v2(
101 ✗ db_path.c_str(), &maps->db_,
102 SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
103 ✗ if (retval != SQLITE_OK) {
104 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "Failed to create inode_maps file (%s)",
105 db_path.c_str());
106 ✗ return NULL;
107 }
108 // Be prepared to wait for up to 1 minute for transactions to complete
109 // Being stuck for a long time is far more favorable than failing
110 // TODO(jblomer): another busy handler. This one conflicts with SIGALRM
111 ✗ retval = sqlite3_busy_handler(maps->db_, BusyHandler,
112 ✗ &maps->busy_handler_info_);
113 ✗ assert(retval == SQLITE_OK);
114
115 // Set-up the main inode table if it doesn't exist
116 ✗ retval = sqlite3_prepare_v2(maps->db_, kSqlCreateTable, -1, &stmt, NULL);
117 ✗ if (retval != SQLITE_OK) {
118 ✗ LogCvmfs(kLogNfsMaps, kLogDebug | kLogSyslogErr,
119 "Failed to prepare create table statement: %s",
120 ✗ sqlite3_errmsg(maps->db_));
121 ✗ return NULL;
122 }
123 ✗ if (sqlite3_step(stmt) != SQLITE_DONE) {
124 ✗ LogCvmfs(kLogNfsMaps, kLogSyslogErr,
125 "Failed to create main inode table: %s",
126 ✗ sqlite3_errmsg(maps->db_));
127 ✗ sqlite3_finalize(stmt);
128 ✗ return NULL;
129 }
130 ✗ sqlite3_finalize(stmt);
131
132 // Prepare lookup and add-inode statements
133 ✗ retval = sqlite3_prepare_v2(maps->db_, kSqlGetPath, -1, &maps->stmt_get_path_,
134 NULL);
135 ✗ assert(retval == SQLITE_OK);
136 ✗ retval = sqlite3_prepare_v2(maps->db_, kSqlGetInode, -1,
137 ✗ &maps->stmt_get_inode_, NULL);
138 ✗ assert(retval == SQLITE_OK);
139 ✗ retval = sqlite3_prepare_v2(maps->db_, kSqlAddInode, -1, &maps->stmt_add_,
140 NULL);
141 ✗ assert(retval == SQLITE_OK);
142
143 // Check the root inode exists, if not create it
144 ✗ const PathString rootpath("", 0);
145 ✗ if (!maps->FindInode(rootpath)) {
146 ✗ retval = sqlite3_prepare_v2(maps->db_, kSqlAddRoot, -1, &stmt, NULL);
147 ✗ assert(retval == SQLITE_OK);
148 ✗ retval = sqlite3_bind_int64(stmt, 1, root_inode);
149 ✗ assert(retval == SQLITE_OK);
150 ✗ retval = sqlite3_bind_text(stmt, 2, "", 0, SQLITE_TRANSIENT);
151 ✗ assert(retval == SQLITE_OK);
152 ✗ if (sqlite3_step(stmt) != SQLITE_DONE) {
153 ✗ PANIC(kLogDebug | kLogSyslogErr, "Failed to execute CreateRoot: %s",
154 sqlite3_errmsg(maps->db_));
155 }
156 ✗ sqlite3_finalize(stmt);
157 }
158
159 ✗ return maps.release();
160 }
161
162
163 /**
164 * Finds an inode by path
165 * \return inode number, 0 if path not found
166 */
167 ✗ uint64_t NfsMapsSqlite::FindInode(const PathString &path) {
168 int sqlite_state;
169 uint64_t inode;
170 ✗ sqlite_state = sqlite3_bind_text(stmt_get_inode_, 1, path.GetChars(),
171 ✗ path.GetLength(), SQLITE_TRANSIENT);
172 ✗ assert(sqlite_state == SQLITE_OK);
173 ✗ sqlite_state = sqlite3_step(stmt_get_inode_);
174 ✗ if (sqlite_state == SQLITE_DONE) {
175 // Path not found in DB
176 ✗ sqlite3_reset(stmt_get_inode_);
177 ✗ return 0;
178 }
179 ✗ if (sqlite_state != SQLITE_ROW) {
180 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "Error finding inode (%s): %s",
181 path.c_str(), sqlite3_errmsg(db_));
182 ✗ sqlite3_reset(stmt_get_inode_);
183 ✗ return 0;
184 }
185 ✗ inode = sqlite3_column_int64(stmt_get_inode_, 0);
186 ✗ sqlite3_reset(stmt_get_inode_);
187 ✗ return inode;
188 }
189
190
191 /**
192 * Adds a new inode by path
193 * \return New inode number, 0 on error
194 */
195 ✗ uint64_t NfsMapsSqlite::IssueInode(const PathString &path) {
196 int sqlite_state;
197 uint64_t inode;
198 ✗ sqlite_state = sqlite3_prepare_v2(db_, kSqlAddInode, -1, &stmt_add_, NULL);
199 ✗ assert(sqlite_state == SQLITE_OK);
200 ✗ sqlite_state = sqlite3_bind_text(stmt_add_, 1, path.GetChars(),
201 ✗ path.GetLength(), SQLITE_TRANSIENT);
202 ✗ if (sqlite_state != SQLITE_OK) {
203 ✗ LogCvmfs(kLogNfsMaps, kLogDebug, "Failed to bind path in IssueInode (%s)",
204 path.c_str());
205 ✗ sqlite3_reset(stmt_add_);
206 ✗ return 0;
207 }
208 ✗ sqlite_state = sqlite3_step(stmt_add_);
209 ✗ if (sqlite_state != SQLITE_DONE) {
210 ✗ LogCvmfs(kLogNfsMaps, kLogDebug,
211 "Failed to execute SQL for IssueInode (%s): %s", path.c_str(),
212 sqlite3_errmsg(db_));
213 ✗ sqlite3_reset(stmt_add_);
214 ✗ return 0;
215 }
216 ✗ inode = sqlite3_last_insert_rowid(db_);
217 ✗ sqlite3_reset(stmt_add_);
218 ✗ n_db_seq_->Set(inode);
219 ✗ perf::Inc(n_db_added_);
220 ✗ return inode;
221 }
222
223
224 ✗ uint64_t NfsMapsSqlite::RetryGetInode(const PathString &path, int attempt) {
225 ✗ if (attempt > 2) {
226 // We have to give up eventually
227 ✗ LogCvmfs(kLogNfsMaps, kLogSyslogErr, "Failed to find & create path (%s)",
228 path.c_str());
229 ✗ return 0;
230 }
231
232 uint64_t inode;
233 {
234 ✗ const MutexLockGuard m(lock_);
235 ✗ inode = FindInode(path);
236 ✗ if (inode) {
237 ✗ perf::Inc(n_db_path_found_);
238 ✗ return inode;
239 }
240 // Inode not found, issue a new one
241 ✗ inode = IssueInode(path);
242 }
243
244 ✗ if (!inode) {
245 ✗ inode = RetryGetInode(path, attempt + 1);
246 }
247 ✗ return inode;
248 }
249
250
251 /**
252 * Finds the inode for path or issues a new inode.
253 */
254 ✗ uint64_t NfsMapsSqlite::GetInode(const PathString &path) {
255 ✗ return RetryGetInode(path, 0);
256 }
257
258
259 /**
260 * Finds the path that belongs to an inode. This must be successful. The
261 * inode input comes from the file system, i.e. it must have been issued
262 * before.
263 * \return false if not found
264 */
265 ✗ bool NfsMapsSqlite::GetPath(const uint64_t inode, PathString *path) {
266 int sqlite_state;
267 ✗ const MutexLockGuard m(lock_);
268
269 ✗ sqlite_state = sqlite3_bind_int64(stmt_get_path_, 1, inode);
270 ✗ assert(sqlite_state == SQLITE_OK);
271 ✗ sqlite_state = sqlite3_step(stmt_get_path_);
272 ✗ if (sqlite_state == SQLITE_DONE) {
273 // Success, but inode not found!
274 ✗ sqlite3_reset(stmt_get_path_);
275 ✗ return false;
276 }
277 ✗ if (sqlite_state != SQLITE_ROW) {
278 ✗ PANIC(kLogSyslogErr, "Failed to execute SQL for GetPath (%" PRIu64 "): %s",
279 inode, sqlite3_errmsg(db_));
280 }
281 ✗ const char *raw_path = (const char *)sqlite3_column_text(stmt_get_path_, 0);
282 ✗ path->Assign(raw_path, strlen(raw_path));
283 ✗ sqlite3_reset(stmt_get_path_);
284 ✗ perf::Inc(n_db_inode_found_);
285 ✗ return true;
286 }
287
288
289 ✗ NfsMapsSqlite::NfsMapsSqlite()
290 ✗ : db_(NULL)
291 ✗ , stmt_get_path_(NULL)
292 ✗ , stmt_get_inode_(NULL)
293 ✗ , stmt_add_(NULL)
294 ✗ , lock_(NULL)
295 ✗ , n_db_seq_(NULL)
296 ✗ , n_db_added_(NULL)
297 ✗ , n_db_path_found_(NULL)
298 ✗ , n_db_inode_found_(NULL) {
299 ✗ lock_ = reinterpret_cast<pthread_mutex_t *>(smalloc(sizeof(pthread_mutex_t)));
300 ✗ const int retval = pthread_mutex_init(lock_, NULL);
301 ✗ assert(retval == 0);
302 }
303
304
305 ✗ NfsMapsSqlite::~NfsMapsSqlite() {
306 ✗ if (stmt_add_)
307 ✗ sqlite3_finalize(stmt_add_);
308 ✗ if (stmt_get_path_)
309 ✗ sqlite3_finalize(stmt_get_path_);
310 ✗ if (stmt_get_inode_)
311 ✗ sqlite3_finalize(stmt_get_inode_);
312 // Close the handles, it is explicitly OK to call close with NULL
313 ✗ sqlite3_close_v2(db_);
314 ✗ pthread_mutex_destroy(lock_);
315 ✗ free(lock_);
316 }
317