GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/nfs_maps_sqlite.cc
Date: 2026-04-26 02:35:59
Exec Total Coverage
Lines: 0 153 0.0%
Branches: 0 172 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
25 #include "statistics.h"
26 #include "util/concurrency.h"
27 #include "util/exception.h"
28 #include "util/logging.h"
29 #include "util/pointer.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 UniquePtr<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