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