GCC Code Coverage Report


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