GCC Code Coverage Report


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